Browse Source

Merge pull request #3725 from artf/dev

Merge from dev
pull/3968/head
Artur Arseniev 5 years ago
committed by GitHub
parent
commit
46be8ca441
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      dist/css/grapes.min.css
  2. 4
      dist/grapes.min.js
  3. 2
      dist/grapes.min.js.map
  4. 8
      docs/.vuepress/config.js
  5. 85
      docs/api.js
  6. 4
      docs/api/assets.md
  7. 28
      docs/api/block.md
  8. 10
      docs/api/block_manager.md
  9. 144
      docs/api/canvas.md
  10. 6
      docs/api/component.md
  11. 83
      docs/api/css_rule.md
  12. 17
      docs/api/device.md
  13. 125
      docs/api/device_manager.md
  14. 247
      docs/api/editor.md
  15. 40
      docs/api/page.md
  16. 18
      docs/api/pages.md
  17. 102
      docs/api/parser.md
  18. 2
      docs/getting-started.md
  19. 6
      docs/modules/Assets.md
  20. 2
      package.json
  21. 47
      src/asset_manager/index.js
  22. 32
      src/asset_manager/view/AssetImageView.js
  23. 1
      src/asset_manager/view/AssetsView.js
  24. 25
      src/asset_manager/view/FileUploader.js
  25. 14
      src/block_manager/config/config.js
  26. 9
      src/block_manager/index.js
  27. 47
      src/block_manager/model/Block.js
  28. 23
      src/block_manager/view/BlockView.js
  29. 52
      src/block_manager/view/CategoryView.js
  30. 144
      src/canvas/index.js
  31. 15
      src/canvas/model/Canvas.js
  32. 5
      src/canvas/view/CanvasView.js
  33. 9
      src/canvas/view/FrameView.js
  34. 11
      src/canvas/view/FrameWrapView.js
  35. 14
      src/code_manager/model/CodeMirrorEditor.js
  36. 30
      src/code_manager/model/CssGenerator.js
  37. 10
      src/code_manager/model/HtmlGenerator.js
  38. 34
      src/code_manager/view/EditorView.js
  39. 5
      src/commands/view/CommandAbstract.js
  40. 29
      src/commands/view/ComponentDrag.js
  41. 20
      src/commands/view/OpenAssets.js
  42. 3
      src/commands/view/OpenTraitManager.js
  43. 15
      src/commands/view/SelectComponent.js
  44. 1
      src/common/Collection.js
  45. 1
      src/common/Model.js
  46. 2
      src/common/index.js
  47. 63
      src/common/module.js
  48. 11
      src/css_composer/index.js
  49. 126
      src/css_composer/model/CssRule.js
  50. 30
      src/device_manager/config/config.js
  51. 234
      src/device_manager/index.js
  52. 45
      src/device_manager/model/Device.js
  53. 14
      src/device_manager/model/Devices.js
  54. 62
      src/device_manager/view/DevicesView.js
  55. 3
      src/dom_components/config/config.js
  56. 2
      src/dom_components/index.js
  57. 3437
      src/dom_components/model/Component.js
  58. 3
      src/dom_components/model/ComponentFrame.js
  59. 17
      src/dom_components/model/ComponentImage.js
  60. 12
      src/dom_components/model/ComponentLabel.js
  61. 4
      src/dom_components/model/ComponentLink.js
  62. 6
      src/dom_components/model/ComponentMap.js
  63. 10
      src/dom_components/model/ComponentScript.js
  64. 18
      src/dom_components/model/ComponentSvg.js
  65. 9
      src/dom_components/model/ComponentSvgIn.js
  66. 17
      src/dom_components/model/ComponentTable.js
  67. 17
      src/dom_components/model/ComponentTableBody.js
  68. 15
      src/dom_components/model/ComponentTableCell.js
  69. 17
      src/dom_components/model/ComponentTableFoot.js
  70. 17
      src/dom_components/model/ComponentTableHead.js
  71. 7
      src/dom_components/model/ComponentTableRow.js
  72. 10
      src/dom_components/model/ComponentTextNode.js
  73. 24
      src/dom_components/model/ComponentVideo.js
  74. 14
      src/dom_components/view/ComponentImageView.js
  75. 59
      src/dom_components/view/ComponentScriptView.js
  76. 11
      src/dom_components/view/ComponentView.js
  77. 2
      src/dom_components/view/ComponentsView.js
  78. 28
      src/editor/config/config.js
  79. 98
      src/editor/index.js
  80. 105
      src/editor/model/Editor.js
  81. 3
      src/i18n/index.js
  82. 9
      src/index.js
  83. 5
      src/keymaps/index.js
  84. 13
      src/modal_dialog/config/config.js
  85. 17
      src/modal_dialog/index.js
  86. 24
      src/modal_dialog/model/Modal.js
  87. 47
      src/modal_dialog/view/ModalView.js
  88. 19
      src/navigator/index.js
  89. 18
      src/pages/index.js
  90. 23
      src/pages/model/Page.js
  91. 13
      src/panels/index.js
  92. 14
      src/parser/config/config.js
  93. 99
      src/parser/index.js
  94. 80
      src/parser/model/BrowserParserHtml.js
  95. 40
      src/parser/model/ParserHtml.js
  96. 3
      src/rich_text_editor/index.js
  97. 26
      src/selector_manager/index.js
  98. 10
      src/selector_manager/view/ClassTagsView.js
  99. 32
      src/storage_manager/index.js
  100. 52
      src/storage_manager/model/LocalStorage.js

2
dist/css/grapes.min.css

File diff suppressed because one or more lines are too long

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

8
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 subDivider = " ‍ ‍ ‍ ";
module.exports = {
title: 'GrapesJS',
@ -61,20 +62,25 @@ module.exports = {
['/api/canvas', 'Canvas'],
['/api/assets', 'Asset Manager'],
['/api/block_manager', 'Block Manager'],
['/api/block', `${subDivider}Block`],
['/api/commands', 'Commands'],
['/api/components', 'DOM Components'],
['/api/component', ' - Component'],
['/api/component', `${subDivider}Component`],
['/api/panels', 'Panels'],
['/api/pages', 'Pages'],
['/api/page', `${subDivider}Page`],
['/api/style_manager', 'Style Manager'],
['/api/storage_manager', 'Storage Manager'],
['/api/device_manager', 'Device Manager'],
['/api/device', `${subDivider}Device`],
['/api/selector_manager', 'Selector Manager'],
['/api/css_composer', 'CSS Composer'],
['/api/css_rule', `${subDivider}CssRule`],
['/api/modal_dialog', 'Modal'],
['/api/rich_text_editor', 'Rich Text Editor'],
['/api/keymaps', 'Keymaps'],
['/api/undo_manager', 'Undo Manager'],
['/api/parser', 'Parser'],
],
'/': [
'',

85
docs/api.js

@ -1,43 +1,56 @@
// This script uses documentation to generate API Reference files
const path = require('path');
const exec = require('child_process').exec;
const documentation = require('documentation');
const fs = require('fs');
const docRoot = __dirname;
const srcRoot = path.join(docRoot, '../src/');
const binRoot = path.join(docRoot, '../node_modules/.bin/');
const cmds = [
['editor/index.js', 'editor.md'],
['asset_manager/index.js', 'assets.md'],
['block_manager/index.js', 'block_manager.md'],
['commands/index.js', 'commands.md'],
['dom_components/index.js', 'components.md'],
['dom_components/model/Component.js', 'component.md'],
['panels/index.js', 'panels.md'],
['style_manager/index.js', 'style_manager.md'],
['storage_manager/index.js', 'storage_manager.md'],
['device_manager/index.js', 'device_manager.md'],
['selector_manager/index.js', 'selector_manager.md'],
['css_composer/index.js', 'css_composer.md'],
['modal_dialog/index.js', 'modal_dialog.md'],
['rich_text_editor/index.js', 'rich_text_editor.md'],
['keymaps/index.js', 'keymaps.md'],
['undo_manager/index.js', 'undo_manager.md'],
['canvas/index.js', 'canvas.md'],
['i18n/index.js', 'i18n.md'],
['pages/index.js', 'pages.md'],
].map(entry =>
`${binRoot}documentation build ${srcRoot}/${entry[0]} -o ${docRoot}/api/${entry[1]} -f md --shallow --markdown-toc false`)
.join(' && ');
console.log('Start API Reference generation...');
exec(cmds, (error, stdout, stderr) => {
if (error) {
console.error( 'Failed to update API Reference: ', error);
return;
}
const log = (...args) => console.log(...args);
stdout.trim().split('\n').forEach(function (line) {
console.info(line);
});
async function generateDocs () {
log('Start API Reference generation...');
console.log('API Reference generation done!');
});
await Promise.all([
['editor/index.js', 'editor.md'],
['asset_manager/index.js', 'assets.md'],
['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/model/Component.js', 'component.md'],
['panels/index.js', 'panels.md'],
['style_manager/index.js', 'style_manager.md'],
['storage_manager/index.js', 'storage_manager.md'],
['device_manager/index.js', 'device_manager.md'],
['device_manager/model/Device.js', 'device.md'],
['selector_manager/index.js', 'selector_manager.md'],
['css_composer/index.js', 'css_composer.md'],
['css_composer/model/CssRule.js', 'css_rule.md'],
['modal_dialog/index.js', 'modal_dialog.md'],
['rich_text_editor/index.js', 'rich_text_editor.md'],
['keymaps/index.js', 'keymaps.md'],
['undo_manager/index.js', 'undo_manager.md'],
['canvas/index.js', 'canvas.md'],
['i18n/index.js', 'i18n.md'],
['pages/index.js', 'pages.md'],
['pages/model/Page.js', 'page.md'],
['parser/index.js', 'parser.md'],
].map(async (file) =>
documentation.build([`${srcRoot}/${file[0]}`], { shallow: true })
.then(cm => documentation.formats.md(cm, /*{ markdownToc: true }*/))
.then(output => {
const res = output
.replace(/\*\*\\\[/g, '**[')
.replace(/\*\*\(\\\[/g, '**([')
.replace(/<\\\[/g, '<[')
.replace(/\| \\\[/g, '| [')
.replace('**Extends Model**', '');
fs.writeFileSync(`${docRoot}/api/${file[1]}`, res);
log('Created', file[1]);
})
));
log('API Reference generation done!');
};
generateDocs();

4
docs/api/assets.md

@ -160,8 +160,8 @@ Render assets
### Parameters
* `assets` **[array][16]** Assets to render, without the argument will render
all global assets
* `assts`
* `assets` **[array][16]** Assets to render, without the argument will render all global assets
### Examples

28
docs/api/block.md

@ -0,0 +1,28 @@
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
## Block
### Properties
* `label` **[String][1]** Block label, eg. `My block`
* `content` **([String][1] | [Object][2])** The content of the block. Might be an HTML string or a [Component Defintion][3]
* `media` **[String][1]?** HTML string for the media/icon of the block, eg. `<svg ...`, `<img ...`, etc.
* `category` **[String][1]?** Block category
* `activate` **[Boolean][4]?** If true, triggers an the `active` event on dropped component
* `select` **[Boolean][4]?** If true, the dropped component will be selected
* `resetId` **[Boolean][4]?** If true, all IDs of dropped components and their styles will be changed
* `disable` **[Boolean][4]?** Disable the block from being interacted
* `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
[1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[3]: /modules/Components.html#component-definition
[4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
[5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function

10
docs/api/block_manager.md

@ -28,6 +28,8 @@ const blockManager = editor.BlockManager;
* [getContainer][9]
* [render][10]
[Block]: block.html
## getConfig
Get configuration object
@ -49,7 +51,7 @@ Add new block to the collection.
* `opts.label` **[string][12]** Name of the block
* `opts.content` **[string][12]** HTML content
* `opts.category` **([string][12] | [Object][11])** Group the block inside a catgegory.
* `opts.category` **([string][12] | [Object][11])** Group the block inside a category.
You should pass objects with id property, eg:
{id: 'some-uid', label: 'My category'}
The string will be converted in:
@ -69,7 +71,7 @@ blockManager.add('h1-block', {
});
```
Returns **Block** Added block
Returns **[Block]** Added block
## get
@ -87,6 +89,8 @@ console.log(JSON.stringify(block));
// {label: 'Heading', content: '<h1>Put your ...', ...}
```
Returns **[Block]**
## getAll
Return all blocks
@ -123,7 +127,7 @@ const id = 'button';
blockManager.remove(id);
```
Returns **Block** Removed block
Returns **[Block]** Removed block
## getCategories

144
docs/api/canvas.md

@ -29,42 +29,52 @@ const canvas = editor.Canvas;
* [scrollTo][10]
* [setZoom][11]
* [getZoom][12]
* [getCoords][13]
* [setCoords][14]
[Component]: component.html
## getConfig
Get the configuration object
Returns **[Object][13]**
### Examples
```javascript
console.log(canvas.getConfig())
```
Returns **[Object][15]** Configuration object
## getElement
Get the canvas element
Returns **[HTMLElement][14]**
Returns **[HTMLElement][16]**
## getFrameEl
Get the iframe element of the canvas
Get the main frame element of the canvas
Returns **[HTMLIFrameElement][15]**
Returns **[HTMLIFrameElement][17]**
## getWindow
Get the window instance of the iframe element
Get the main frame window instance
Returns **[Window][16]**
Returns **[Window][18]**
## getDocument
Get the document of the iframe element
Get the main frame document element
Returns **HTMLDocument**
## getBody
Get the body of the iframe element
Get the main frame body element
Returns **[HTMLBodyElement][17]**
Returns **[HTMLBodyElement][19]**
## setCustomBadgeLabel
@ -72,7 +82,7 @@ Set custom badge naming strategy
### Parameters
* `f` **[Function][18]**
* `f` **[Function][20]**
### Examples
@ -86,13 +96,13 @@ canvas.setCustomBadgeLabel(function(component){
Get canvas rectangular data
Returns **[Object][13]**
Returns **[Object][15]**
## hasFocus
Check if the canvas is focused
Returns **[Boolean][19]**
Returns **[Boolean][21]**
## scrollTo
@ -103,10 +113,10 @@ passed to it. For instance, you can scroll smoothly by using
### Parameters
* `el` **([HTMLElement][14] | Component)**
* `opts` **[Object][13]** Options, same as options for `scrollIntoView` (optional, default `{}`)
* `el` **([HTMLElement][16] | [Component])**
* `opts` **[Object][15]** Options, same as options for `scrollIntoView` (optional, default `{}`)
* `opts.force` **[Boolean][19]** Force the scroll, even if the element is already visible (optional, default `false`)
* `opts.force` **[Boolean][21]** Force the scroll, even if the element is already visible (optional, default `false`)
### Examples
@ -120,19 +130,63 @@ canvas.scrollTo(selected, { force: true });
## setZoom
Set zoom value
Set canvas zoom value
### Parameters
* `value` **[Number][20]** The zoom value, from 0 to 100
* `value` **[Number][22]** The zoom value, from 0 to 100
### Examples
```javascript
canvas.setZoom(50); // set zoom to 50%
```
Returns **this**
## getZoom
Get zoom value
Get canvas zoom value
### Examples
```javascript
canvas.setZoom(50); // set zoom to 50%
const zoom = canvas.getZoom(); // 50
```
Returns **[Number][22]**
## setCoords
Set canvas position coordinates
Returns **[Number][20]**
### Parameters
* `x` **[Number][22]** Horizontal position
* `y` **[Number][22]** Vertical position
### Examples
```javascript
canvas.setCoords(100, 100);
```
Returns **this**
## getCoords
Get canvas position coordinates
### Examples
```javascript
canvas.setCoords(100, 100);
const coords = canvas.getCoords();
// { x: 100, y: 100 }
```
Returns **[Object][15]** Object containing coordinates
## addFrame
@ -140,27 +194,27 @@ Add new frame to the canvas
### Parameters
* `props` **[Object][13]** Frame properties (optional, default `{}`)
* `props` **[Object][15]** Frame properties (optional, default `{}`)
* `opts` (optional, default `{}`)
### Examples
```javascript
editor.Canvas.addFrame({
name: 'Mobile home page',
x: 100, // Position in canvas
y: 100,
width: 500, // Frame dimensions
height: 600,
// device: 'DEVICE-ID',
components: [
'<h1 class="testh">Title frame</h1>',
'<p class="testp">Paragraph frame</p>',
],
styles: `
.testh { color: red; }
.testp { color: blue; }
`,
canvas.addFrame({
name: 'Mobile home page',
x: 100, // Position in canvas
y: 100,
width: 500, // Frame dimensions
height: 600,
// device: 'DEVICE-ID',
components: [
'<h1 class="testh">Title frame</h1>',
'<p class="testp">Paragraph frame</p>',
],
styles: `
.testh { color: red; }
.testp { color: blue; }
`,
});
```
@ -190,18 +244,22 @@ Returns **Frame**
[12]: #getzoom
[13]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[13]: #getcoords
[14]: #setcoords
[15]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[14]: https://developer.mozilla.org/docs/Web/HTML/Element
[16]: https://developer.mozilla.org/docs/Web/HTML/Element
[15]: https://developer.mozilla.org/docs/Web/API/HTMLIFrameElement
[17]: https://developer.mozilla.org/docs/Web/API/HTMLIFrameElement
[16]: https://developer.mozilla.org/docs/Web/API/Window
[18]: https://developer.mozilla.org/docs/Web/API/Window
[17]: https://developer.mozilla.org/docs/Web/HTML/Element/body
[19]: https://developer.mozilla.org/docs/Web/HTML/Element/body
[18]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function
[20]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function
[19]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
[21]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
[20]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
[22]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number

6
docs/api/component.md

@ -2,6 +2,8 @@
## Component
**Extends Model.extend(Styleable)**
The Component object represents a single node of our template structure, so when you update its properties the changes are
immediately reflected on the canvas and in the code to export (indeed, when you ask to export the code we just go through all
the tree of nodes).
@ -277,6 +279,10 @@ Returns **this**
Get the style of the component
#### Parameters
* `opts` (optional, default `{}`)
Returns **[Object][2]**
### setStyle

83
docs/api/css_rule.md

@ -0,0 +1,83 @@
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
## getAtRule
Return 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)'
});
cssRule.getAtRule(); // "@media (min-width: 500px)"
```
Returns **[String][1]**
## selectorsToString
Return selectors of the rule as a string
### Parameters
* `opts` **[Object][2]?** Options (optional, default `{}`)
* `opts.skipState` **[Boolean][3]?** Skip state from the result
### Examples
```javascript
const cssRule = editor.Css.setRule('.class1:hover', { color: 'red' });
cssRule.selectorsToString(); // ".class1:hover"
cssRule.selectorsToString({ skipState: true }); // ".class1"
```
Returns **[String][1]**
## getDeclaration
Get declaration block (without the at-rule statement)
### Parameters
* `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;}"
```
Returns **[String][1]**
## toCSS
Return the CSS string of the rule
### Parameters
* `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;}}"
```
Returns **[String][1]** CSS string
[1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean

17
docs/api/device.md

@ -0,0 +1,17 @@
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
## Device
### Properties
* `name` **[String][1]?** Device type, eg. `Mobile`
* `width` **[String][1]?** Width to set for the editor iframe, eg. '900px'
* `height` **[String][1]?** Height to set for the editor iframe, eg. '600px'
* `widthMedia` **[String][1]?** The width which will be used in media queries, If empty the width will be used
* `priority` **[Number][2]?** Setup the order of media queries
[1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number

125
docs/api/device_manager.md

@ -1,6 +1,6 @@
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
## DeviceManager
## Devices
You can customize the initial state of the module from the editor initialization, by passing the following [Configuration Object][1]
@ -15,67 +15,138 @@ const editor = grapesjs.init({
Once the editor is instantiated you can use its API. Before using these methods you should get the module from the instance
```js
const deviceManager = editor.DeviceManager;
const deviceManager = editor.Devices;
```
## Available Events
* `device:add` - Added new device. The [Device] is passed as an argument to the callback
* `device:remove` - Device removed. The [Device] is passed as an argument to the callback
* `device:select` - New device selected. The newly selected [Device] and the previous one, are passed as arguments to the callback
* `device:update` - Device updated. The updated [Device] and the object containing changes are passed as arguments to the callback
* `device` - Catch-all event for all the events mentioned above. An object containing all the available data about the triggered event is passed as an argument to the callback
## Methods
* [add][2]
* [get][3]
* [getAll][4]
* [getDevices][4]
* [remove][5]
* [select][6]
* [getSelected][7]
[Device]: device.html
## add
Add new device to the collection. URLs are supposed to be unique
Add new device
### Parameters
* `id` **[String][5]** Device id
* `width` **[String][5]** Width of the device
* `opts` **[Object][6]?** Custom options (optional, default `{}`)
* `props` **[Object][8]** Device properties
* `options` (optional, default `{}`)
### Examples
```javascript
deviceManager.add('tablet', '900px');
deviceManager.add('tablet2', '900px', {
height: '300px',
// At first, GrapesJS tries to localize the name by device id.
// In case is not found, the `name` property is used (or `id` if name is missing)
const device1 = deviceManager.add({
// Without an explicit ID, the `name` will be taken. In case of missing `name`, a random ID will be created.
id: 'tablet',
name: 'Tablet',
width: '900px', // This width will be applied on the canvas frame and for the CSS media
});
const device2 = deviceManager.add({
id: 'tablet2',
name: 'Tablet 2',
widthMedia: '810px', // the width that will be used for the CSS media
width: '800px', // This width will be applied on the canvas frame
widthMedia: '810px', // This width that will be used for the CSS media
height: '600px', // Height will be applied on the canvas frame
});
```
Returns **Device** Added device
Returns **[Device]** Added device
## get
Return device by name
Return device by ID
### Parameters
* `name` **[string][5]** Name of the device
* `id` **[String][9]** ID of the device
### Examples
```javascript
var device = deviceManager.get('Tablet');
const device = deviceManager.get('Tablet');
console.log(JSON.stringify(device));
// {name: 'Tablet', width: '900px'}
```
## getAll
Returns **([Device] | null)**
## remove
Remove device
### Parameters
* `device` **([String][9] | [Device])** Device or device id
* `opts` (optional, default `{}`)
### Examples
```javascript
const removed = deviceManager.remove('device-id');
// or by passing the Device
const device = deviceManager.get('device-id');
deviceManager.remove(device);
```
Returns **[Device]** Removed device
## getDevices
Return all devices
### Examples
```javascript
var devices = deviceManager.getAll();
const devices = deviceManager.getDevices();
console.log(JSON.stringify(devices));
// [{name: 'Desktop', width: ''}, ...]
```
Returns **Collection**
Returns **[Array][10]<[Device]>**
## select
Change the selected device. This will update the frame in the canvas
### Parameters
* `device` **([String][9] | [Device])** Device or device id
* `opts` (optional, default `{}`)
### Examples
```javascript
deviceManager.select('some-id');
// or by passing the page
const device = deviceManager.get('some-id');
deviceManager.select(device);
```
## getSelected
Get the selected device
### Examples
```javascript
const selected = deviceManager.getSelected();
```
Returns **[Device]**
[1]: https://github.com/artf/grapesjs/blob/master/src/device_manager/config/config.js
@ -83,8 +154,16 @@ Returns **Collection**
[3]: #get
[4]: #getAll
[4]: #getdevices
[5]: #remove
[6]: #select
[7]: #getselected
[8]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[9]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[10]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array

247
docs/api/editor.md

@ -113,11 +113,6 @@ By changing `result.content` you're able to customize what is dropped
* `modal:open` - Modal is opened
* `modal:close` - Modal is closed
### Parser
* `parse:html` - On HTML parse, an object containing the input and the output of the parser is passed as an argument
* `parse:css` - On CSS parse, an object containing the input and the output of the parser is passed as an argument
### Commands
* `run:{commandName}` - Triggered when some command is called to run (eg. editor.runCommand('preview'))
@ -128,9 +123,17 @@ By changing `result.content` you're able to customize what is dropped
* `run` - Triggered on run of any command. The id and the result are passed as arguments to the callback
* `stop` - Triggered on stop of any command. The id and the result are passed as arguments to the callback
### Devices
Check the [Devices][2] module.
### Parser
Check the [Parser][3] module.
### Pages
Check the [Pages][2] module.
Check the [Pages][4] module.
### General
@ -146,7 +149,7 @@ Returns configuration object
### Parameters
* `prop` **[string][3]?** Property name
* `prop` **[string][5]?** Property name
Returns **any** Returns the configuration object or
the value of the specified property
@ -157,11 +160,12 @@ Returns HTML built inside canvas
### Parameters
* `opts` **[Object][4]** Options (optional, default `{}`)
* `opts` **[Object][6]** Options (optional, default `{}`)
* `opts.cleanId` **[Boolean][5]** Remove unnecessary IDs (eg. those created automatically) (optional, default `false`)
* `opts.component` **Component?** Return the HTML of a specific Component
* `opts.cleanId` **[Boolean][7]** Remove unnecessary IDs (eg. those created automatically) (optional, default `false`)
Returns **[string][3]** HTML string
Returns **[string][5]** HTML string
## getCss
@ -169,17 +173,25 @@ Returns CSS built inside canvas
### Parameters
* `opts` **[Object][4]** Options (optional, default `{}`)
* `opts` **[Object][6]** Options (optional, default `{}`)
* `opts.avoidProtected` **[Boolean][5]** Don't include protected CSS (optional, default `false`)
* `opts.component` **Component?** Return the CSS of a specific Component
* `opts.json` **[Boolean][7]** Return an array of CssRules instead of the CSS string (optional, default `false`)
* `opts.avoidProtected` **[Boolean][7]** Don't include protected CSS (optional, default `false`)
Returns **[string][3]** CSS string
Returns **([String][5] | [Array][8]\<CssRule>)** CSS string or array of CssRules
## getJs
Returns JS of all components
Returns **[string][3]** JS string
### Parameters
* `opts` **[Object][6]** Options (optional, default `{}`)
* `opts.component` **Component?** Get the JS of a specific component
Returns **[String][5]** JS string
## getComponents
@ -199,8 +211,8 @@ Set components inside editor's canvas. This method overrides actual components
### Parameters
* `components` **([Array][6]<[Object][4]> | [Object][4] | [string][3])** HTML string or components model
* `opt` **[Object][4]** the options object to be used by the \[setComponents][em#setComponents][7] method (optional, default `{}`)
* `components` **([Array][8]<[Object][6]> | [Object][6] | [string][5])** HTML string or components model
* `opt` **[Object][6]** the options object to be used by the \[setComponents][em#setComponents][9] method (optional, default `{}`)
### Examples
@ -222,10 +234,10 @@ Add components
### Parameters
* `components` **([Array][6]<[Object][4]> | [Object][4] | [string][3])** HTML string or components model
* `opts` **[Object][4]** Options
* `components` **([Array][8]<[Object][6]> | [Object][6] | [string][5])** HTML string or components model
* `opts` **[Object][6]** Options
* `opts.avoidUpdateStyle` **[Boolean][5]** If the HTML string contains styles,
* `opts.avoidUpdateStyle` **[Boolean][7]** If the HTML string contains styles,
by default, they will be created and, if already exist, updated. When this option
is true, styles already created will not be updated. (optional, default `false`)
@ -241,13 +253,13 @@ editor.addComponents({
});
```
Returns **[Array][6]\<Component>**
Returns **[Array][8]\<Component>**
## getStyle
Returns style in JSON format object
Returns **[Object][4]**
Returns **[Object][6]**
## setStyle
@ -255,8 +267,8 @@ Set style inside editor's canvas. This method overrides actual style
### Parameters
* `style` **([Array][6]<[Object][4]> | [Object][4] | [string][3])** CSS string or style model
* `opt` **[Object][4]** the options object to be used by the \[setStyle][em#setStyle][8] method (optional, default `{}`)
* `style` **([Array][8]<[Object][6]> | [Object][6] | [string][5])** CSS string or style model
* `opt` (optional, default `{}`)
### Examples
@ -264,13 +276,30 @@ Set style inside editor's canvas. This method overrides actual style
editor.setStyle('.cls{color: red}');
//or
editor.setStyle({
selectors: ['cls']
selectors: ['cls'],
style: { color: 'red' }
});
```
Returns **this**
## addStyle
Add styles to the editor
### Parameters
* `style` **([Array][8]<[Object][6]> | [Object][6] | [string][5])** CSS string or style model
* `opts` (optional, default `{}`)
### Examples
```javascript
editor.addStyle('.cls{color: red}');
```
Returns **[Array][8]\<CssRule>** Array of created CssRule instances
## getSelected
Returns the last selected component, if there is one
@ -281,7 +310,7 @@ Returns **Model**
Returns an array of all selected components
Returns **[Array][6]**
Returns **[Array][8]**
## getSelectedToStyle
@ -299,10 +328,10 @@ Select a component
### Parameters
* `el` **(Component | [HTMLElement][9])** Component to select
* `opts` **[Object][4]?** Options
* `el` **(Component | [HTMLElement][10])** Component to select
* `opts` **[Object][6]?** Options
* `opts.scroll` **[Boolean][5]?** Scroll canvas to the selected element
* `opts.scroll` **[Boolean][7]?** Scroll canvas to the selected element
### Examples
@ -321,7 +350,7 @@ Add component to selection
### Parameters
* `el` **(Component | [HTMLElement][9] | [Array][6])** Component to select
* `el` **(Component | [HTMLElement][10] | [Array][8])** Component to select
### Examples
@ -337,7 +366,7 @@ Remove component from selection
### Parameters
* `el` **(Component | [HTMLElement][9] | [Array][6])** Component to select
* `el` **(Component | [HTMLElement][10] | [Array][8])** Component to select
### Examples
@ -353,7 +382,7 @@ Toggle component selection
### Parameters
* `el` **(Component | [HTMLElement][9] | [Array][6])** Component to select
* `el` **(Component | [HTMLElement][10] | [Array][8])** Component to select
### Examples
@ -363,6 +392,21 @@ editor.selectToggle(model);
Returns **this**
## getEditing
Returns, if active, the Component enabled in rich text editing mode.
### Examples
```javascript
const textComp = editor.getEditing();
if (textComp) {
console.log('HTML: ', textComp.toHTML());
}
```
Returns **(Component | null)**
## setDevice
Set device to the editor. If the device exists it will
@ -370,7 +414,7 @@ change the canvas to the proper width
### Parameters
* `name` **[string][3]** Name of the device
* `name` **[string][5]** Name of the device
### Examples
@ -392,7 +436,7 @@ console.log(device);
// 'Tablet'
```
Returns **[string][3]** Device name
Returns **[string][5]** Device name
## runCommand
@ -400,8 +444,8 @@ Execute command
### Parameters
* `id` **[string][3]** Command ID
* `options` **[Object][4]** Custom options (optional, default `{}`)
* `id` **[string][5]** Command ID
* `options` **[Object][6]** Custom options (optional, default `{}`)
### Examples
@ -417,8 +461,8 @@ Stop the command if stop method was provided
### Parameters
* `id` **[string][3]** Command ID
* `options` **[Object][4]** Custom options (optional, default `{}`)
* `id` **[string][5]** Command ID
* `options` **[Object][6]** Custom options (optional, default `{}`)
### Examples
@ -434,9 +478,22 @@ Store data to the current storage
### Parameters
* `clb` **[Function][10]** Callback function
* `clb` **[Function][11]** Callback function
Returns **[Object][4]** Stored data
Returns **[Object][6]** Stored data
## storeData
Get the JSON data object, which could be stored and loaded back with `editor.loadData(json)`
### Examples
```javascript
console.log(editor.storeData());
// { pages: [...], styles: [...], ... }
```
Returns **[Object][6]**
## load
@ -444,23 +501,39 @@ Load data from the current storage
### Parameters
* `clb` **[Function][10]** Callback function
* `clb` **[Function][11]** Callback function
Returns **[Object][4]** Stored data
Returns **[Object][6]** Stored data
## loadData
Load data from the JSON data object
### Parameters
* `data` **[Object][6]** Data to load
### Examples
```javascript
editor.loadData({ pages: [...], styles: [...], ... })
```
Returns **[Object][6]** Loaded object
## getContainer
Returns container element. The one which was indicated as 'container'
on init method
Returns **[HTMLElement][9]**
Returns **[HTMLElement][10]**
## getDirtyCount
Return the count of changes made to the content and not yet stored.
This count resets at any `store()`
Returns **[number][11]**
Returns **[number][12]**
## refresh
@ -473,9 +546,9 @@ refresh you'll get misleading position of tools
### Parameters
* `opts`
* `options` **[Object][4]?** Options
* `options` **[Object][6]?** Options
* `options.tools` **[Boolean][5]** Update the position of tools (eg. rich text editor, component highlighter, etc.) (optional, default `false`)
* `options.tools` **[Boolean][7]** Update the position of tools (eg. rich text editor, component highlighter, etc.) (optional, default `false`)
## setCustomRte
@ -483,7 +556,7 @@ Replace the built-in Rich Text Editor with a custom one.
### Parameters
* `obj` **[Object][4]** Custom RTE Interface
* `obj` **[Object][6]** Custom RTE Interface
### Examples
@ -523,7 +596,7 @@ custom parser, pass `null` as the argument
### Parameters
* `parser` **([Function][10] | null)** Parser function
* `parser` **([Function][11] | null)** Parser function
### Examples
@ -545,11 +618,11 @@ Returns **this**
## setDragMode
Change the global drag mode of components.
To get more about this feature read: [https://github.com/artf/grapesjs/issues/1936][12]
To get more about this feature read: [https://github.com/artf/grapesjs/issues/1936][13]
### Parameters
* `value` **[String][3]** Drag mode, options: 'absolute' | 'translate'
* `value` **[String][5]** Drag mode, options: 'absolute' | 'translate'
Returns **this**
@ -560,10 +633,10 @@ Trigger event log message
### Parameters
* `msg` **any** Message to log
* `opts` **[Object][4]** Custom options (optional, default `{}`)
* `opts` **[Object][6]** Custom options (optional, default `{}`)
* `opts.ns` **[String][3]** Namespace of the log (eg. to use in plugins) (optional, default `''`)
* `opts.level` **[String][3]** Level of the log, `debug`, `info`, `warning`, `error` (optional, default `'debug'`)
* `opts.ns` **[String][5]** Namespace of the log (eg. to use in plugins) (optional, default `''`)
* `opts.level` **[String][5]** Level of the log, `debug`, `info`, `warning`, `error` (optional, default `'debug'`)
### Examples
@ -585,11 +658,11 @@ Translate label
### Parameters
* `args` **...any**
* `key` **[String][3]** Label to translate
* `opts` **[Object][4]?** Options for the translation
* `key` **[String][5]** Label to translate
* `opts` **[Object][6]?** Options for the translation
* `opts.params` **[Object][4]?** Params for the translation
* `opts.noWarn` **[Boolean][5]?** Avoid warnings in case of missing resources
* `opts.params` **[Object][6]?** Params for the translation
* `opts.noWarn` **[Boolean][7]?** Avoid warnings in case of missing resources
### Examples
@ -601,7 +674,7 @@ editor.t('msg2', { params: { test: 'hello' } });
editor.t('msg2', { params: { test: 'hello' }, l: 'it' });
```
Returns **[String][3]**
Returns **[String][5]**
## on
@ -609,8 +682,8 @@ Attach event
### Parameters
* `event` **[string][3]** Event name
* `callback` **[Function][10]** Callback function
* `event` **[string][5]** Event name
* `callback` **[Function][11]** Callback function
Returns **this**
@ -620,8 +693,8 @@ Attach event and detach it after the first run
### Parameters
* `event` **[string][3]** Event name
* `callback` **[Function][10]** Callback function
* `event` **[string][5]** Event name
* `callback` **[Function][11]** Callback function
Returns **this**
@ -631,8 +704,8 @@ Detach event
### Parameters
* `event` **[string][3]** Event name
* `callback` **[Function][10]** Callback function
* `event` **[string][5]** Event name
* `callback` **[Function][11]** Callback function
Returns **this**
@ -642,7 +715,7 @@ Trigger event
### Parameters
* `event` **[string][3]** Event to trigger
* `event` **[string][5]** Event to trigger
Returns **this**
@ -654,28 +727,50 @@ Destroy the editor
Render editor
Returns **[HTMLElement][9]**
Returns **[HTMLElement][10]**
## html
Print safe HTML by using ES6 tagged template strings.
### Parameters
* `literals` **[Array][8]<[String][5]>**
* `substs` **[Array][8]<[String][5]>**
### Examples
```javascript
const unsafeStr = '<script>....</script>';
const safeStr = '<b>Hello</b>';
// Use `$${var}` to avoid escaping
const strHtml = editor.html`Escaped ${unsafeStr}, unescaped $${safeStr}`;
```
Returns **[String][5]**
[1]: https://github.com/artf/grapesjs/blob/master/src/editor/config/config.js
[2]: /api/pages.html
[2]: /api/device_manager.html
[3]: /api/parser.html
[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[4]: /api/pages.html
[4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
[6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
[7]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
[7]: em#setComponents
[8]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
[8]: em#setStyle
[9]: em#setComponents
[9]: https://developer.mozilla.org/docs/Web/HTML/Element
[10]: https://developer.mozilla.org/docs/Web/HTML/Element
[10]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function
[11]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function
[11]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
[12]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
[12]: https://github.com/artf/grapesjs/issues/1936
[13]: https://github.com/artf/grapesjs/issues/1936

40
docs/api/page.md

@ -0,0 +1,40 @@
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
## getAllFrames
Get all frames
### Examples
```javascript
const arrayOfFrames = page.getAllFrames();
```
Returns **[Array][1]\<Frame>**
## getMainFrame
Get the first frame of the page (identified always as the main one)
### Examples
```javascript
const mainFrame = page.getMainFrame();
```
Returns **Frame**
## getMainComponent
Get the root component (usually is the `wrapper` component) from the main frame
### Examples
```javascript
const rootComponent = page.getMainComponent();
console.log(rootComponent.toHTML());
```
Returns **Component**
[1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array

18
docs/api/pages.md

@ -43,6 +43,8 @@ const pageManager = editor.Pages;
* [select][6]
* [getSelected][7]
[Page]: page.html
## add
Add new page
@ -62,7 +64,7 @@ const newPage = pageManager.add({
});
```
Returns **Page**
Returns **[Page]**
## remove
@ -70,7 +72,7 @@ Remove page
### Parameters
* `page` **([String][9] | Page)** Page or page id
* `page` **([String][9] | [Page])** Page or page id
* `opts` (optional, default `{}`)
### Examples
@ -82,7 +84,7 @@ const somePage = pageManager.get('page-id');
pageManager.remove(somePage);
```
Returns **Page**
Returns **[Page]**
## get
@ -98,7 +100,7 @@ Get page by id
const somePage = pageManager.get('page-id');
```
Returns **Page**
Returns **[Page]**
## getMain
@ -110,7 +112,7 @@ Get main page (the first one available)
const mainPage = pageManager.getMain();
```
Returns **Page**
Returns **[Page]**
## getAll
@ -122,7 +124,7 @@ Get all pages
const arrayOfPages = pageManager.getAll();
```
Returns **[Array][10]\<Page>**
Returns **[Array][10]<[Page]>**
## select
@ -130,7 +132,7 @@ Change the selected page. This will switch the page rendered in canvas
### Parameters
* `page` **([String][9] | Page)** Page or page id
* `page` **([String][9] | [Page])** Page or page id
* `opts` (optional, default `{}`)
### Examples
@ -154,7 +156,7 @@ Get the selected page
const selectedPage = pageManager.getSelected();
```
Returns **Page**
Returns **[Page]**
[1]: #add

102
docs/api/parser.md

@ -0,0 +1,102 @@
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
## Parser
You can customize the initial state of the module from the editor initialization, by passing the following [Configuration Object][1]
```js
const editor = grapesjs.init({
parser: {
// options
}
})
```
Once the editor is instantiated you can use its API. Before using these methods you should get the module from the instance
```js
const { Parser } = editor;
```
## Available Events
* `parse:html` - On HTML parse, an object containing the input and the output of the parser is passed as an argument
* `parse:css` - On CSS parse, an object containing the input and the output of the parser is passed as an argument
## Methods
* [getConfig][2]
* [parseHtml][3]
* [parseCss][4]
## getConfig
Get the configuration object
### Examples
```javascript
console.log(Parser.getConfig())
```
Returns **[Object][5]** Configuration object
## parseHtml
Parse HTML string and return the object containing the Component Definition
### Parameters
* `input` **[String][6]** HTML string to parse
* `options` **[Object][5]?** Options (optional, default `{}`)
* `options.htmlType` **[String][6]?** [HTML mime type][7] to parse
### Examples
```javascript
const resHtml = Parser.parseHtml(`<table><div>Hi</div></table>`, {
htmlType: 'text/html', // default
});
// By using the `text/html`, this will fix automatically all the HTML syntax issues
// Indeed the final representation, in this case, will be `<div>Hi</div><table></table>`
const resXml = Parser.parseHtml(`<table><div>Hi</div></table>`, {
htmlType: 'application/xml',
});
// This will preserve the original format as, from the XML point of view, is a valid format
```
Returns **[Object][5]** Object containing the result `{ html: ..., css: ... }`
## parseCss
Parse CSS string and return an array of valid definition objects for CSSRules
### Parameters
* `input` **[String][6]** CSS string to parse
### Examples
```javascript
const res = Parser.parseCss('.cls { color: red }');
// [{ ... }]
```
Returns **[Array][8]<[Object][5]>** Array containing the result
[1]: https://github.com/artf/grapesjs/blob/master/src/parser/config/config.js
[2]: #getconfig
[3]: #parsehtml
[4]: #parsecss
[5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[7]: https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString#Argument02
[8]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array

2
docs/getting-started.md

@ -117,7 +117,7 @@ If you want to learn more about blocks we suggest to read its dedicated article:
Technically, once you drop your HTML block inside the canvas each element of the content is transformed into a GrapesJS Component. A GrapesJS Component is an object containing information about how the element is rendered in the canvas (managed in the View) and how it might look its final code (created by the properties in the Model). Generally, all Model properties are reflected in the View. Therefore, if you add a new attribute to the model, it will be available in the export code (which we will learn more about later), and the element you see in the canvas will be updated with new attributes.
This isn't totally out of the ordinary, but the unique thing about Components that you can create a totally decoupled View. This means you can show the user whatever you desire regardless of what is in the Model. For example, by dragging a placeholder text you can fetch and show instead a dynamic content. If you want to learn more about Custom Components, you should check out [Component Manager Module](modules/Components.html).
GrapesJS comes with a few [built-in Components](modules/Components.html#built-in-components) that enable different features once rendered in the canvas. For example, by double clicking on an image component you will see the default [Asset Manager](modules/Assets.html), which you can customize or integrate you own. By double clicking on the text component you're able to edit it via the built-in Rich Text Editor, which is also customization and [replaceable](guides/Replace-Rich-Text-Editor.html).
GrapesJS comes with a few [built-in Components](modules/Components.html#built-in-component-types) that enable different features once rendered in the canvas. For example, by double clicking on an image component you will see the default [Asset Manager](modules/Assets.html), which you can customize or integrate you own. By double clicking on the text component you're able to edit it via the built-in Rich Text Editor, which is also customization and [replaceable](guides/Replace-Rich-Text-Editor.html).
As we have seen before you can create Blocks directly as Components
```js

6
docs/modules/Assets.md

@ -140,14 +140,16 @@ const editor = grapesjs.init({
type: 'image',
src: 'http://placehold.it/350x250/459ba8/fff/image2.jpg',
height: 350,
width: 250
width: 250,
name: 'displayName'
},
{
// As the 'image' is the base type of assets, omitting it will
// be set as `image` by default
src: 'http://placehold.it/350x250/79c267/fff/image3.jpg',
height: 350,
width: 250
width: 250,
name: 'displayName'
},
],
}

2
package.json

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

47
src/asset_manager/index.js

@ -37,7 +37,7 @@ import FileUpload from './view/FileUploader';
export default () => {
let c = {};
let assets, am, fu;
let assets, assetsVis, am, fu;
return {
/**
@ -79,15 +79,7 @@ export default () => {
// Global assets collection
assets = new Assets([]);
const obj = {
// Collection visible in asset manager
collection: new Assets([]),
globalCollection: assets,
config: c
};
fu = new FileUpload(obj);
obj.fu = fu;
am = new AssetsView(obj);
assetsVis = new Assets([]);
// Setup the sync between the global and public collections
assets.listenTo(assets, 'add', model => {
@ -159,7 +151,7 @@ export default () => {
* @return {Collection}
*/
getAllVisible() {
return am.collection;
return assetsVis;
},
/**
@ -236,8 +228,7 @@ export default () => {
/**
* Render assets
* @param {array} assets Assets to render, without the argument will render
* all global assets
* @param {array} assets Assets to render, without the argument will render all global assets
* @return {HTMLElement}
* @example
* // Render all assets
@ -249,14 +240,22 @@ export default () => {
* asset => asset.get('category') == 'cats'
* ));
*/
render(assets) {
const toRender = assets || this.getAll().models;
if (!am.rendered) {
render(assts) {
const toRender = assts || this.getAll().models;
if (!am) {
const obj = {
collection: assetsVis, // Collection visible in asset manager
globalCollection: assets,
config: c
};
fu = new FileUpload(obj);
obj.fu = fu;
am = new AssetsView(obj);
am.render();
}
am.collection.reset(toRender);
assetsVis.reset(toRender);
return this.getContainer();
},
@ -310,7 +309,7 @@ export default () => {
},
postRender(editorView) {
c.dropzone && fu.initDropzone(editorView);
c.dropzone && fu && fu.initDropzone(editorView);
},
/**
@ -319,7 +318,7 @@ export default () => {
* @private
* */
setTarget(m) {
am.collection.target = m;
assetsVis.target = m;
},
/**
@ -328,7 +327,7 @@ export default () => {
* @private
* */
onSelect(f) {
am.collection.onSelect = f;
assetsVis.onSelect = f;
},
/**
@ -351,9 +350,9 @@ export default () => {
destroy() {
assets.reset();
fu.collection.reset();
fu.remove();
am.remove();
assetsVis.reset();
fu && fu.remove();
am && am.remove();
[assets, am, fu].forEach(i => (i = null));
c = {};
}

32
src/asset_manager/view/AssetImageView.js

@ -1,5 +1,6 @@
import { isFunction } from 'underscore';
import AssetView from './AssetView';
import html from 'utils/html';
export default AssetView.extend({
events: {
@ -9,24 +10,23 @@ export default AssetView.extend({
},
getPreview() {
const pfx = this.pfx;
const src = this.model.get('src');
return `
const { pfx, ppfx, model } = this;
const src = model.get('src');
return html`
<div class="${pfx}preview" style="background-image: url('${src}');"></div>
<div class="${pfx}preview-bg ${this.ppfx}checker-bg"></div>
<div class="${pfx}preview-bg ${ppfx}checker-bg"></div>
`;
},
getInfo() {
const pfx = this.pfx;
const model = this.model;
const { pfx, model } = this;
let name = model.get('name');
let width = model.get('width');
let height = model.get('height');
let unit = model.get('unitDim');
let dim = width && height ? `${width}x${height}${unit}` : '';
name = name || model.getFilename();
return `
return html`
<div class="${pfx}name">${name}</div>
<div class="${pfx}dimensions">${dim}</div>
`;
@ -42,15 +42,16 @@ export default AssetView.extend({
* @private
* */
onClick() {
var onClick = this.config.onClick;
var model = this.model;
this.collection.trigger('deselectAll');
this.$el.addClass(this.pfx + 'highlight');
const { model, pfx } = this;
const { onClick } = this.config;
const coll = this.collection;
coll.trigger('deselectAll');
this.$el.addClass(pfx + 'highlight');
if (isFunction(onClick)) {
onClick(model);
} else {
this.updateTarget(this.collection.target);
this.updateTarget(coll.target);
}
},
@ -60,16 +61,15 @@ export default AssetView.extend({
* */
onDblClick() {
const { em, model } = this;
const onDblClick = this.config.onDblClick;
const { onDblClick } = this.config;
const { target, onSelect } = this.collection;
if (isFunction(onDblClick)) {
onDblClick(model);
} else {
this.updateTarget(this.collection.target);
this.updateTarget(target);
em && em.get('Modal').close();
}
var onSelect = this.collection.onSelect;
isFunction(onSelect) && onSelect(model);
},

1
src/asset_manager/view/AssetsView.js

@ -182,7 +182,6 @@ export default Backbone.View.extend({
this.$el.append(fuRendered).append(this.template(this));
this.el.className = `${this.ppfx}asset-manager`;
this.renderAssets();
this.rendered = 1;
return this;
}
});

25
src/asset_manager/view/FileUploader.js

@ -1,16 +1,25 @@
import { template } from 'underscore';
import Backbone from 'backbone';
import fetch from 'utils/fetch';
import html from 'utils/html';
export default Backbone.View.extend(
{
template: template(`
<form>
<div id="<%= pfx %>title"><%= title %></div>
<input type="file" id="<%= uploadId %>" name="file" accept="*/*" <%= disabled ? 'disabled' : '' %> <%= multiUpload ? 'multiple' : '' %>/>
<div style="clear:both;"></div>
</form>
`),
template({ pfx, title, uploadId, disabled, multiUpload }) {
return html`
<form>
<div id="${pfx}title">${title}</div>
<input
type="file"
id="${uploadId}"
name="file"
accept="*/*"
${disabled ? 'disabled' : ''}
${multiUpload ? 'multiple' : ''}
/>
<div style="clear:both;"></div>
</form>
`;
},
events: {},

14
src/block_manager/config/config.js

@ -3,8 +3,18 @@ export default {
// With the empty value, nothing will be rendered
appendTo: '',
// Append blocks to canvas on click
appendOnClick: 0,
// Append blocks to canvas on click.
// With the `true` value, it will try to append the block to the selected component.
// If there is no selected component, the block will be appened to the wrapper.
// You can also pass a function to this option, use it as a catch-all for all block
// clicks and implement a custom logic for each block.
// appendOnClick: (block, editor) => {
// if (block.get('id') === 'some-id')
// editor.getSelected().append(block.get('content'))
// else
// editor.getWrapper().append(block.get('content'))
// }
appendOnClick: false,
blocks: []
};

9
src/block_manager/index.js

@ -23,6 +23,8 @@
* * [getContainer](#getcontainer)
* * [render](#render)
*
* [Block]: block.html
*
* @module BlockManager
*/
import { isElement } from 'underscore';
@ -120,13 +122,13 @@ export default () => {
* @param {Object} opts Options
* @param {string} opts.label Name of the block
* @param {string} opts.content HTML content
* @param {string|Object} opts.category Group the block inside a catgegory.
* @param {string|Object} opts.category Group the block inside a category.
* You should pass objects with id property, eg:
* {id: 'some-uid', label: 'My category'}
* The string will be converted in:
* 'someid' => {id: 'someid', label: 'someid'}
* @param {Object} [opts.attributes={}] Block attributes
* @return {Block} Added block
* @returns {[Block]} Added block
* @example
* blockManager.add('h1-block', {
* label: 'Heading',
@ -146,6 +148,7 @@ export default () => {
/**
* Return the block by id
* @param {string} id Block id
* @returns {[Block]}
* @example
* const block = blockManager.get('h1-block');
* console.log(JSON.stringify(block));
@ -178,7 +181,7 @@ export default () => {
/**
* Remove a block by id
* @param {string} id Block id
* @return {Block} Removed block
* @returns {[Block]} Removed block
* @example
* // Id of the block which need to be removed
* const id = 'button';

47
src/block_manager/model/Block.js

@ -1,21 +1,30 @@
import Backbone from 'backbone';
import { Model } from 'common';
export default Backbone.Model.extend({
defaults: {
// If true, triggers an 'active' event on dropped component
activate: 0,
// If true, the dropped component will be selected
select: 0,
// If true, all IDs of dropped component and its style will be changed
resetId: 0,
// Block label
label: '',
// Disable the drag of the block
disable: 0,
// HTML string for the media of the block, eg. SVG icon, image, etc.
media: '',
content: '',
category: '',
attributes: {}
/**
* @property {String} label Block label, eg. `My block`
* @property {String|Object} content The content of the block. Might be an HTML string or a [Component Defintion](/modules/Components.html#component-definition)
* @property {String} [media=''] HTML string for the media/icon of the block, eg. `<svg ...`, `<img ...`, etc.
* @property {String} [category=''] Block category
* @property {Boolean} [activate=false] If true, triggers an the `active` event on dropped component
* @property {Boolean} [select=false] If true, the dropped component will be selected
* @property {Boolean} [resetId=false] If true, all IDs of dropped components and their styles will be changed
* @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
*/
export default class Block extends Model {
defaults() {
return {
label: '',
content: '',
media: '',
category: '',
activate: false,
select: false,
resetId: false,
disable: false,
onClick: null,
attributes: {}
};
}
});
}

23
src/block_manager/view/BlockView.js

@ -1,5 +1,5 @@
import Backbone from 'backbone';
import { isObject } from 'underscore';
import { isFunction, isObject } from 'underscore';
import { on, off, hasDnd } from 'utils/mixins';
export default Backbone.View.extend({
@ -21,9 +21,15 @@ export default Backbone.View.extend({
this.listenTo(model, 'change', this.render);
},
handleClick() {
handleClick(ev) {
const { config, model, em } = this;
if (!config.appendOnClick) return;
const onClick = model.get('onClick') || config.appendOnClick;
em.trigger('block:click', model, ev);
if (!onClick) {
return;
} else if (isFunction(onClick)) {
return onClick(model, em.getEditor(), { event: ev });
}
const sorter = config.getSorter();
const content = model.get('content');
const selected = em.getSelected();
@ -99,14 +105,15 @@ export default Backbone.View.extend({
if (result) {
const oldKey = 'activeOnRender';
const oldActive = result.get && result.get(oldKey);
const toActive = model.get('activate') || oldActive;
if (model.get('activate') || oldActive) {
result.trigger('active');
result.unset(oldKey);
if (model.get('select') || toActive) {
em.setSelected(result);
}
if (model.get('select')) {
em.setSelected(result);
if (toActive) {
result.trigger('active');
result.unset(oldKey);
}
if (model.get('resetId')) {

52
src/block_manager/view/CategoryView.js

@ -1,20 +1,20 @@
import { template } from 'underscore';
import Backbone from 'backbone';
import { View } from 'backbone';
import html from 'utils/html';
export default Backbone.View.extend({
template: template(`
<div class="<%= pfx %>title">
<i class="<%= pfx %>caret-icon"></i>
<%= label %>
</div>
<div class="<%= pfx %>blocks-c"></div>
`),
events: {},
export default class CategoryView extends View {
template({ pfx, label }) {
return html`
<div class="${pfx}title">
<i class="${pfx}caret-icon"></i>
${label}
</div>
<div class="${pfx}blocks-c"></div>
`;
}
attributes() {
return this.model.get('attributes');
},
}
initialize(o = {}, config = {}) {
this.config = config;
@ -26,33 +26,34 @@ export default Backbone.View.extend({
this.iconClass = `${pfx}caret-icon`;
this.activeClass = `${pfx}open`;
this.className = `${pfx}block-category`;
this.events = {};
this.events[`click .${pfx}title`] = 'toggle';
this.listenTo(this.model, 'change:open', this.updateVisibility);
this.delegateEvents();
this.model.view = this;
},
}
updateVisibility() {
if (this.model.get('open')) this.open();
else this.close();
},
}
open() {
this.$el.addClass(this.activeClass);
this.getIconEl().className = `${this.iconClass} ${this.caretD}`;
this.getBlocksEl().style.display = '';
},
}
close() {
this.$el.removeClass(this.activeClass);
this.getIconEl().className = `${this.iconClass} ${this.caretR}`;
this.getBlocksEl().style.display = 'none';
},
}
toggle() {
var model = this.model;
model.set('open', !model.get('open'));
},
}
getIconEl() {
if (!this.iconEl) {
@ -60,7 +61,7 @@ export default Backbone.View.extend({
}
return this.iconEl;
},
}
getBlocksEl() {
if (!this.blocksEl) {
@ -68,24 +69,21 @@ export default Backbone.View.extend({
}
return this.blocksEl;
},
}
append(el) {
this.getBlocksEl().appendChild(el);
},
}
render() {
const { em, el, $el, model } = this;
const { em, el, $el, model, pfx } = this;
const label =
em.t(`blockManager.categories.${model.id}`) || model.get('label');
el.innerHTML = this.template({
pfx: this.pfx,
label
});
el.innerHTML = this.template({ pfx, label });
$el.addClass(this.className);
$el.css({ order: model.get('order') });
this.updateVisibility();
return this;
}
});
}

144
src/canvas/index.js

@ -25,6 +25,10 @@
* * [scrollTo](#scrollto)
* * [setZoom](#setzoom)
* * [getZoom](#getzoom)
* * [getCoords](#getcoords)
* * [setCoords](#setcoords)
*
* [Component]: component.html
*
* @module Canvas
*/
@ -48,11 +52,6 @@ export default () => {
return CanvasView;
},
/**
* Name of the module
* @type {String}
* @private
*/
name: 'Canvas',
/**
@ -80,10 +79,6 @@ export default () => {
onLoad() {
this.model.init();
CanvasView = new canvasView({
model: canvas,
config: c
});
},
getModel() {
@ -92,7 +87,9 @@ export default () => {
/**
* Get the configuration object
* @return {Object}
* @returns {Object} Configuration object
* @example
* console.log(canvas.getConfig())
*/
getConfig() {
return c;
@ -100,7 +97,7 @@ export default () => {
/**
* Get the canvas element
* @return {HTMLElement}
* @returns {HTMLElement}
*/
getElement() {
return CanvasView.el;
@ -111,11 +108,11 @@ export default () => {
},
/**
* Get the iframe element of the canvas
* @return {HTMLIFrameElement}
* Get the main frame element of the canvas
* @returns {HTMLIFrameElement}
*/
getFrameEl() {
const { frame } = CanvasView;
const { frame } = CanvasView || {};
return frame && frame.el;
},
@ -124,16 +121,16 @@ export default () => {
},
/**
* Get the window instance of the iframe element
* @return {Window}
* Get the main frame window instance
* @returns {Window}
*/
getWindow() {
return this.getFrameEl().contentWindow;
},
/**
* Get the document of the iframe element
* @return {HTMLDocument}
* Get the main frame document element
* @returns {HTMLDocument}
*/
getDocument() {
const frame = this.getFrameEl();
@ -141,7 +138,7 @@ export default () => {
},
/**
* Get the body of the iframe element
* Get the main frame body element
* @return {HTMLBodyElement}
*/
getBody() {
@ -163,7 +160,7 @@ export default () => {
/**
* Returns element containing all global canvas tools
* @return {HTMLElement}
* @returns {HTMLElement}
* @private
*/
getGlobalToolsEl() {
@ -172,7 +169,7 @@ export default () => {
/**
* Returns element containing all canvas tools
* @return {HTMLElement}
* @returns {HTMLElement}
* @private
*/
getToolsEl(compView) {
@ -181,7 +178,7 @@ export default () => {
/**
* Returns highlighter element
* @return {HTMLElement}
* @returns {HTMLElement}
* @private
*/
getHighlighter(compView) {
@ -190,7 +187,7 @@ export default () => {
/**
* Returns badge element
* @return {HTMLElement}
* @returns {HTMLElement}
* @private
*/
getBadgeEl(compView) {
@ -199,7 +196,7 @@ export default () => {
/**
* Returns placer element
* @return {HTMLElement}
* @returns {HTMLElement}
* @private
*/
getPlacerEl() {
@ -208,7 +205,7 @@ export default () => {
/**
* Returns ghost element
* @return {HTMLElement}
* @returns {HTMLElement}
* @private
*/
getGhostEl() {
@ -217,7 +214,7 @@ export default () => {
/**
* Returns toolbar element
* @return {HTMLElement}
* @returns {HTMLElement}
* @private
*/
getToolbarEl() {
@ -226,7 +223,7 @@ export default () => {
/**
* Returns resizer element
* @return {HTMLElement}
* @returns {HTMLElement}
* @private
*/
getResizerEl() {
@ -235,7 +232,7 @@ export default () => {
/**
* Returns offset viewer element
* @return {HTMLElement}
* @returns {HTMLElement}
* @private
*/
getOffsetViewerEl(compView) {
@ -248,24 +245,25 @@ export default () => {
/**
* Returns fixed offset viewer element
* @return {HTMLElement}
* @returns {HTMLElement}
* @private
*/
getFixedOffsetViewerEl() {
return CanvasView.fixedOffsetEl;
},
/**
* Render canvas
* @private
* */
render() {
CanvasView && CanvasView.remove();
CanvasView = new canvasView({
model: canvas,
config: c
});
return CanvasView.render().el;
},
/**
* Get frame position
* @return {Object}
* @returns {Object}
* @private
*/
getOffset() {
@ -280,7 +278,7 @@ export default () => {
/**
* Get the offset of the passed component element
* @param {HTMLElement} el
* @return {Object}
* @returns {Object}
* @private
*/
offset(el) {
@ -302,7 +300,7 @@ export default () => {
/**
* Get element position relative to the canvas
* @param {HTMLElement} el
* @return {Object}
* @returns {Object}
* @private
*/
getElementPos(el, opts) {
@ -312,7 +310,7 @@ export default () => {
/**
* Returns element's offsets like margins and paddings
* @param {HTMLElement} el
* @return {Object}
* @returns {Object}
* @private
*/
getElementOffsets(el) {
@ -512,7 +510,7 @@ export default () => {
/**
* Check if the canvas is focused
* @return {Boolean}
* @returns {Boolean}
*/
hasFocus() {
return this.getDocument().hasFocus();
@ -540,7 +538,7 @@ export default () => {
* executed via `scrollIntoView` API and options of this method are
* passed to it. For instance, you can scroll smoothly by using
* `{ behavior: 'smooth' }`.
* @param {HTMLElement|Component} el
* @param {HTMLElement|[Component]} el
* @param {Object} [opts={}] Options, same as options for `scrollIntoView`
* @param {Boolean} [opts.force=false] Force the scroll, even if the element is already visible
* @example
@ -575,9 +573,11 @@ export default () => {
},
/**
* Set zoom value
* Set canvas zoom value
* @param {Number} value The zoom value, from 0 to 100
* @returns {this}
* @example
* canvas.setZoom(50); // set zoom to 50%
*/
setZoom(value) {
canvas.set('zoom', parseFloat(value));
@ -585,13 +585,42 @@ export default () => {
},
/**
* Get zoom value
* Get canvas zoom value
* @returns {Number}
* @example
* canvas.setZoom(50); // set zoom to 50%
* const zoom = canvas.getZoom(); // 50
*/
getZoom() {
return parseFloat(canvas.get('zoom'));
},
/**
* Set canvas position coordinates
* @param {Number} x Horizontal position
* @param {Number} y Vertical position
* @returns {this}
* @example
* canvas.setCoords(100, 100);
*/
setCoords(x, y) {
canvas.set({ x: parseFloat(x), y: parseFloat(y) });
return this;
},
/**
* Get canvas position coordinates
* @returns {Object} Object containing coordinates
* @example
* canvas.setCoords(100, 100);
* const coords = canvas.getCoords();
* // { x: 100, y: 100 }
*/
getCoords() {
const { x, y } = canvas.attributes;
return { x, y };
},
getZoomDecimal() {
return this.getZoom() / 100;
},
@ -615,23 +644,22 @@ export default () => {
* @param {Object} props Frame properties
* @returns {Frame}
* @example
*
editor.Canvas.addFrame({
name: 'Mobile home page',
x: 100, // Position in canvas
y: 100,
width: 500, // Frame dimensions
height: 600,
// device: 'DEVICE-ID',
components: [
'<h1 class="testh">Title frame</h1>',
'<p class="testp">Paragraph frame</p>',
],
styles: `
.testh { color: red; }
.testp { color: blue; }
`,
});
* canvas.addFrame({
* name: 'Mobile home page',
* x: 100, // Position in canvas
* y: 100,
* width: 500, // Frame dimensions
* height: 600,
* // device: 'DEVICE-ID',
* components: [
* '<h1 class="testh">Title frame</h1>',
* '<p class="testp">Paragraph frame</p>',
* ],
* styles: `
* .testh { color: red; }
* .testp { color: blue; }
* `,
* });
*/
addFrame(props = {}, opts = {}) {
return canvas.get('frames').add(

15
src/canvas/model/Canvas.js

@ -28,13 +28,10 @@ export default class Canvas extends Model {
init() {
const { em } = this;
this.set(
'frames',
em
.get('PageManager')
.getMain()
.getFrames()
);
const mainPage = em.get('PageManager').getMain();
const frame = mainPage.getMainFrame();
this.set('frames', mainPage.getFrames());
this.updateDevice({ frame });
}
_pageUpdated(page, prev) {
@ -45,10 +42,10 @@ export default class Canvas extends Model {
this.set('frames', page.getFrames());
}
updateDevice() {
updateDevice(opts = {}) {
const { em } = this;
const device = em.getDeviceModel();
const model = em.getCurrentFrameModel();
const model = opts.frame || em.getCurrentFrameModel();
if (model && device) {
const { width, height } = device.attributes;

5
src/canvas/view/CanvasView.js

@ -75,8 +75,6 @@ export default Backbone.View.extend({
remove() {
this.frames.remove();
const frm = this.model.get('frames');
frm.remove(frm.models);
this.frames = {};
Backbone.View.prototype.remove.apply(this, arguments);
this.toggleListeners();
@ -201,7 +199,8 @@ export default Backbone.View.extend({
getFrameOffset(el) {
if (!this.frmOff || el) {
const frame = this.frame.el;
const frEl = el ? el.ownerDocument.defaultView.frameElement : frame;
const winEl = el && el.ownerDocument.defaultView;
const frEl = winEl ? winEl.frameElement : frame;
this.frmOff = this.offset(frEl || frame);
}
return this.frmOff;

9
src/canvas/view/FrameView.js

@ -30,6 +30,7 @@ export default Backbone.View.extend({
'_emitUpdate'
);
const { model, el } = this;
this.tools = {};
this.config = {
...(o.config || {}),
frameView: this
@ -163,18 +164,20 @@ export default Backbone.View.extend({
},
_getTool(name) {
const { tools } = this;
const toolsEl = this.getToolsEl();
if (!this[name]) {
this[name] = toolsEl.querySelector(name);
if (!tools[name]) {
tools[name] = toolsEl.querySelector(name);
}
return this[name];
return tools[name];
},
remove() {
const wrp = this.wrapper;
this._toggleEffects();
this.tools = {};
wrp && wrp.remove();
Backbone.View.prototype.remove.apply(this, arguments);
},

11
src/canvas/view/FrameWrapView.js

@ -71,9 +71,14 @@ export default Backbone.View.extend({
ev && this.dragger.start(ev);
},
remove(opts) {
this.frame.remove(opts);
__clear(opts) {
const { frame } = this;
frame && frame.remove(opts);
removeEl(this.elTools);
},
remove(opts) {
this.__clear(opts);
Backbone.View.prototype.remove.apply(this, arguments);
['frame', 'dragger', 'cv', 'em', 'canvas', 'elTools'].forEach(
i => (this[i] = 0)
@ -164,7 +169,7 @@ export default Backbone.View.extend({
render() {
const { frame, $el, ppfx, cv, model, el } = this;
const { onRender } = model.attributes;
frame && frame.remove();
this.__clear();
this.__handleSize();
frame.render();
$el

14
src/code_manager/model/CodeMirrorEditor.js

@ -1,9 +1,15 @@
import { bindAll } from 'underscore';
import Backbone from 'backbone';
import CodeMirror from 'codemirror/lib/codemirror';
import 'codemirror/mode/htmlmixed/htmlmixed';
import 'codemirror/mode/css/css';
import 'codemirror-formatting';
import { hasWin } from 'utils/mixins';
let CodeMirror;
if (hasWin()) {
CodeMirror = require('codemirror/lib/codemirror');
require('codemirror/mode/htmlmixed/htmlmixed');
require('codemirror/mode/css/css');
require('codemirror-formatting');
}
export default Backbone.Model.extend({
CodeMirror,

30
src/code_manager/model/CssGenerator.js

@ -40,23 +40,26 @@ export default Backbone.Model.extend({
},
build(model, opts = {}) {
const cssc = opts.cssc;
const { json } = opts;
const em = opts.em || '';
const cssc = opts.cssc || (em && em.get('CssComposer'));
this.em = em;
this.compCls = [];
this.ids = [];
var code = this.buildFromModel(model, opts);
this.model = model;
const codeJson = [];
let code = model ? this.buildFromModel(model, opts) : '';
const clearStyles =
isUndefined(opts.clearStyles) && em
? em.getConfig('clearStyles')
: opts.clearStyles;
if (cssc) {
const rules = cssc.getAll();
const rules = opts.rules || cssc.getAll();
const atRules = {};
const dump = [];
rules.each(rule => {
rules.forEach(rule => {
const atRule = rule.getAtRule();
if (atRule) {
@ -69,7 +72,13 @@ export default Backbone.Model.extend({
return;
}
code += this.buildFromRule(rule, dump, opts);
const res = this.buildFromRule(rule, dump, opts);
if (json) {
codeJson.push(res);
} else {
code += res;
}
});
this.sortMediaObject(atRules).forEach(item => {
@ -85,6 +94,8 @@ export default Backbone.Model.extend({
} else {
rulesStr += ruleStr;
}
json && codeJson.push(ruleStr);
});
if (rulesStr) {
@ -92,10 +103,10 @@ export default Backbone.Model.extend({
}
});
em && clearStyles && rules.remove(dump);
em && clearStyles && rules.remove && rules.remove(dump);
}
return code;
return json ? codeJson.filter(r => r) : code;
},
/**
@ -105,6 +116,7 @@ export default Backbone.Model.extend({
*/
buildFromRule(rule, dump, opts = {}) {
let result = '';
const { model } = this;
const selectorStrNoAdd = rule.selectorsToString({ skipAdd: 1 });
const selectorsAdd = rule.get('selectorsAdd');
const singleAtRule = rule.get('singleAtRule');
@ -122,9 +134,9 @@ export default Backbone.Model.extend({
}
});
if ((selectorStrNoAdd && found) || selectorsAdd || singleAtRule) {
if ((selectorStrNoAdd && found) || selectorsAdd || singleAtRule || !model) {
const block = rule.getDeclaration({ body: 1 });
block && (result += block);
block && (opts.json ? (result = rule) : (result += block));
} else {
dump.push(rule);
}

10
src/code_manager/model/HtmlGenerator.js

@ -1,6 +1,6 @@
import Backbone from 'backbone';
import { Model } from 'backbone';
export default Backbone.Model.extend({
export default class HTMLGenerator extends Model {
build(model, opts = {}) {
const models = model.components();
const htmlOpts = {};
@ -36,16 +36,16 @@ export default Backbone.Model.extend({
if (opts.exportWrapper) {
return model.toHTML({
...htmlOpts,
...(opts.wrapperIsBody && { tag: 'body' })
...(opts.wrapperIsBody && model.is('wrapper') && { tag: 'body' })
});
}
return this.buildModels(models, htmlOpts);
},
}
buildModels(models, opts = {}) {
let code = '';
models.forEach(mod => (code += mod.toHTML(opts)));
return code;
}
});
}

34
src/code_manager/view/EditorView.js

@ -1,24 +1,28 @@
import { template } from 'underscore';
import Backbone from 'backbone';
import html from 'utils/html';
import { View } from 'backbone';
export default Backbone.View.extend({
template: template(`
<div class="<%= pfx %>editor" id="<%= pfx %><%= codeName %>">
<div id="<%= pfx %>title"><%= label %></div>
<div id="<%= pfx %>code"></div>
</div>`),
export default class EditorView extends View {
template({ pfx, codeName, label }) {
return html`
<div class="${pfx}editor" id="${pfx}${codeName}">
<div id="${pfx}title">${label}</div>
<div id="${pfx}code"></div>
</div>
`;
}
initialize(o) {
this.config = o.config || {};
this.pfx = this.config.stylePrefix;
},
}
render() {
var obj = this.model.toJSON();
obj.pfx = this.pfx;
this.$el.html(this.template(obj));
this.$el.attr('class', this.pfx + 'editor-c');
this.$el.find('#' + this.pfx + 'code').append(this.model.get('input'));
const { model, pfx, $el } = this;
const obj = model.toJSON();
obj.pfx = pfx;
$el.html(this.template(obj));
$el.attr('class', `${pfx}editor-c`);
$el.find(`#${pfx}code`).append(model.get('input'));
return this;
}
});
}

5
src/commands/view/CommandAbstract.js

@ -1,7 +1,6 @@
import Backbone from 'backbone';
const $ = Backbone.$;
import { Model } from 'backbone';
export default Backbone.View.extend({
export default Model.extend({
/**
* Initialize method that can't be removed
* @param {Object} o Options

29
src/commands/view/ComponentDrag.js

@ -269,34 +269,41 @@ export default {
x = this.getTranslate(transform);
y = this.getTranslate(transform, 'y');
} else {
x = parseFloat(left);
y = parseFloat(top);
x = parseFloat(left || 0);
y = parseFloat(top || 0);
}
return { x, y };
},
setPosition({ x, y, end, position, width, height }) {
const { target, isTran } = this;
const { target, isTran, em } = this;
const unit = 'px';
const en = !end ? 1 : ''; // this will trigger the final change
const left = `${x}${unit}`;
const top = `${y}${unit}`;
let styleUp = {};
if (isTran) {
let transform = target.getStyle()['transform'] || '';
transform = this.setTranslate(transform, 'x', left);
transform = this.setTranslate(transform, 'y', top);
return target.addStyle({ transform, en }, { avoidStore: !end });
styleUp = { transform, en };
target.addStyle(styleUp, { avoidStore: !end });
} else {
const adds = { position, width, height };
const style = { left, top, en };
keys(adds).forEach(add => {
const prop = adds[add];
if (prop) style[add] = prop;
});
styleUp = style;
target.addStyle(styleUp, { avoidStore: !end });
}
const adds = { position, width, height };
const style = { left, top, en };
keys(adds).forEach(add => {
const prop = adds[add];
if (prop) style[add] = prop;
});
target.addStyle(style, { avoidStore: !end });
// Update StyleManager properties
em.getSelected() &&
keys(styleUp).forEach(i => em.trigger(`update:component:style:${i}`));
},
_getDragData() {

20
src/commands/view/OpenAssets.js

@ -3,7 +3,6 @@ export default {
const modal = editor.Modal;
const am = editor.AssetManager;
const config = am.getConfig();
const amContainer = am.getContainer();
const title = opts.modalTitle || editor.t('assetManager.modalTitle') || '';
const types = opts.types;
const accept = opts.accept;
@ -14,35 +13,24 @@ export default {
am.onSelect(opts.onSelect);
if (!this.rendered || types) {
let assets = am.getAll().filter(i => 1);
let assets = am.getAll().filter(i => i);
if (types && types.length) {
assets = assets.filter(a => types.indexOf(a.get('type')) !== -1);
}
am.render(assets);
this.rendered = 1;
this.rendered = am.getContainer();
}
if (accept) {
const uploadEl = amContainer.querySelector(
const uploadEl = this.rendered.querySelector(
`input#${config.stylePrefix}uploadFile`
);
uploadEl && uploadEl.setAttribute('accept', accept);
}
modal
.open({
title,
content: amContainer
})
.getModel()
.once('change:open', () => editor.stopCommand(this.id));
return this;
},
stop(editor) {
editor.Modal.close();
modal.open({ title, content: this.rendered });
return this;
}
};

3
src/commands/view/OpenTraitManager.js

@ -13,7 +13,6 @@ export default {
var panelC;
if (!this.$cn) {
var tmView = tm.getTraitsViewer();
var confTm = tm.getConfig();
this.$cn = $('<div></div>');
this.$cn2 = $('<div></div>');
@ -27,7 +26,7 @@ export default {
this.$cn2.append(
`<div class="${pfx}traits-label">${em.t('traitManager.label')}</div>`
);
this.$cn2.append(tmView.render().el);
this.$cn2.append(tm.render());
var panels = editor.Panels;
if (!panels.getPanel('views-container'))

15
src/commands/view/SelectComponent.js

@ -5,7 +5,8 @@ import {
off,
getUnitFromValue,
isTaggableNode,
getViewEl
getViewEl,
hasWin
} from 'utils/mixins';
import { isVisible, isDoc } from 'utils/dom';
import ToolbarView from 'dom_components/view/ToolbarView';
@ -335,6 +336,7 @@ export default {
* @private
* */
updateBadge(el, pos, opts = {}) {
const { canvas } = this;
const model = $(el).data('model');
if (!model || !model.get('badgable')) return;
const badge = this.getBadge(opts);
@ -355,9 +357,12 @@ export default {
const un = 'px';
const bStyle = badge.style;
bStyle.display = 'block';
const badgeH = badge ? badge.offsetHeight : 0;
const posTop = 0 - badgeH;
const top = opts.topOff - badgeH < 0 ? -opts.topOff : posTop;
const targetToElem = canvas.getTargetToElementFixed(el, badge, {
pos: pos
});
const top = targetToElem.top; //opts.topOff - badgeH < 0 ? -opts.topOff : posTop;
const left = opts.leftOff < 0 ? -opts.leftOff : 0;
bStyle.top = top + un;
@ -746,11 +751,13 @@ export default {
},
run(editor) {
if (!hasWin()) return;
this.editor = editor && editor.get('Editor');
this.enable();
},
stop(ed, sender, opts = {}) {
if (!hasWin()) return;
const { em, editor } = this;
this.onHovered(); // force to hide toolbar
this.stopSelectComponent();

1
src/common/Collection.js

@ -0,0 +1 @@
export { Collection as default } from 'backbone';

1
src/common/Model.js

@ -0,0 +1 @@
export { Model as default } from 'backbone';

2
src/common/index.js

@ -0,0 +1,2 @@
export { default as Model } from './Model';
export { default as Collection } from './Collection';

63
src/common/module.js

@ -0,0 +1,63 @@
import { isString } from 'underscore';
import { createId } from 'utils/mixins';
export default {
getConfig(name) {
const res = this.config || {};
return name ? res[name] : res;
},
getAll() {
return this.all || [];
},
getAllMap() {
return this.getAll().reduce((acc, i) => {
acc[i.get('id')] = i;
return acc;
}, {});
},
__initListen() {
const { all, em, events } = this;
all &&
em &&
all
.on('add', (m, c, o) => em.trigger(events.add, m, o))
.on('remove', (m, c, o) => em.trigger(events.remove, m, o))
.on('change', (p, c) =>
em.trigger(events.update, p, p.changedAttributes(), c)
)
.on('all', this.__catchAllEvent, this);
},
__remove(model, opts = {}) {
const { em } = this;
const md = isString(model) ? this.get(model) : model;
const rm = () => {
md && this.all.remove(md, opts);
return md;
};
!opts.silent && em && em.trigger(this.events.removeBefore, md, rm, opts);
return !opts.abort && rm();
},
__catchAllEvent(event, model, coll, opts) {
const { em, events } = this;
const options = opts || coll;
em && events.all && em.trigger(events.all, { event, model, options });
},
_createId(len = 16) {
const all = this.getAll();
const ln = all.length + len;
const allMap = this.getAllMap();
let id;
do {
id = createId(ln);
} while (allMap[id]);
return id;
}
};

11
src/css_composer/index.js

@ -86,10 +86,6 @@ export default () => {
em = c.em;
rules = new CssRules([], c);
rulesView = new CssRulesView({
collection: rules,
config: c
});
return this;
},
@ -479,13 +475,18 @@ export default () => {
* @private
*/
render() {
rulesView && rulesView.remove();
rulesView = new CssRulesView({
collection: rules,
config: c
});
return rulesView.render().el;
},
destroy() {
rules.reset();
rules.stopListening();
rulesView.remove();
rulesView && rulesView.remove();
[em, rules, rulesView].forEach(i => (i = null));
c = {};
}

126
src/css_composer/model/CssRule.js

@ -1,46 +1,48 @@
import { map } from 'underscore';
import Backbone from 'backbone';
import { Model } from 'backbone';
import Styleable from 'domain_abstract/model/Styleable';
import { isEmpty, forEach, isString } from 'underscore';
import Selectors from 'selector_manager/model/Selectors';
import { isEmptyObj } from 'utils/mixins';
import { isEmptyObj, hasWin } from 'utils/mixins';
const { CSS } = window;
const { CSS } = hasWin() ? window : {};
export default Backbone.Model.extend(Styleable).extend({
defaults: {
// Css selectors
selectors: [],
export default class CssRule extends Model.extend(Styleable) {
defaults() {
return {
// Css selectors
selectors: [],
// Additional string css selectors
selectorsAdd: '',
// Additional string css selectors
selectorsAdd: '',
// Css properties style
style: {},
// Css properties style
style: {},
// On which device width this rule should be rendered, eg. @media (max-width: 1000px)
mediaText: '',
// On which device width this rule should be rendered, eg. @media (max-width: 1000px)
mediaText: '',
// State of the rule, eg: hover | pressed | focused
state: '',
// State of the rule, eg: hover | pressed | focused
state: '',
// Indicates if the rule is stylable
stylable: true,
// Indicates if the rule is stylable
stylable: true,
// Type of at-rule, eg. 'media', 'font-face', etc.
atRuleType: '',
// Type of at-rule, eg. 'media', 'font-face', etc.
atRuleType: '',
// This particolar property is used only on at-rules, like 'page' or
// 'font-face', where the block containes only style declarations
singleAtRule: 0,
// This particolar property is used only on at-rules, like 'page' or
// 'font-face', where the block containes only style declarations
singleAtRule: 0,
// If true, sets '!important' on all properties
// You can use an array to specify properties to set important
// Used in view
important: 0,
// If true, sets '!important' on all properties
// You can use an array to specify properties to set important
// Used in view
important: 0,
_undo: true
},
_undo: true
};
}
initialize(c, opt = {}) {
this.config = c || {};
@ -48,20 +50,20 @@ export default Backbone.Model.extend(Styleable).extend({
this.em = opt.em;
this.ensureSelectors();
this.on('change', this.__onChange);
},
}
__onChange(m, opts) {
const { em } = this;
const changed = this.changedAttributes();
!isEmptyObj(changed) && em && em.changesUp(opts);
},
}
clone() {
const opts = { ...this.opt };
const attr = { ...this.attributes };
attr.selectors = this.get('selectors').map(s => s.clone());
return new this.constructor(attr, opts);
},
}
ensureSelectors(m, c, opts) {
const { em } = this;
@ -83,11 +85,17 @@ export default Backbone.Model.extend(Styleable).extend({
this.set('selectors', sels, opts);
this.listenTo(...toListen);
},
}
/**
* Returns an at-rule statement if possible, eg. '@media (...)', '@keyframes'
* @return {string}
* Return the at-rule statement when exists, eg. '@media (...)', '@keyframes'
* @returns {String}
* @example
* const cssRule = editor.Css.setRule('.class1', { color: 'red' }, {
* atRuleType: 'media',
* atRuleParams: '(min-width: 500px)'
* });
* cssRule.getAtRule(); // "@media (min-width: 500px)"
*/
getAtRule() {
const type = this.get('atRuleType');
@ -96,11 +104,17 @@ export default Backbone.Model.extend(Styleable).extend({
const typeStr = type ? `@${type}` : condition ? '@media' : '';
return typeStr + (condition && typeStr ? ` ${condition}` : '');
},
}
/**
* Return selectors fo the rule as a string
* @return {string}
* Return selectors of the rule as a string
* @param {Object} [opts] Options
* @param {Boolean} [opts.skipState] Skip state from the result
* @returns {String}
* @example
* const cssRule = editor.Css.setRule('.class1:hover', { color: 'red' });
* cssRule.selectorsToString(); // ".class1:hover"
* cssRule.selectorsToString({ skipState: true }); // ".class1"
*/
selectorsToString(opts = {}) {
const result = [];
@ -118,12 +132,18 @@ export default Backbone.Model.extend(Styleable).extend({
selectors && result.push(`${selectors}${stateStr}`);
addSelector && !opts.skipAdd && result.push(addSelector);
return result.join(', ');
},
}
/**
* Get declaration block
* @param {Object} [opts={}] Options
* @return {string}
* Get declaration block (without the at-rule statement)
* @param {Object} [opts={}] Options (same as in `selectorsToString`)
* @returns {String}
* @example
* const cssRule = editor.Css.setRule('.class1', { color: 'red' }, {
* atRuleType: 'media',
* atRuleParams: '(min-width: 500px)'
* });
* cssRule.getDeclaration() // ".class1{color:red;}"
*/
getDeclaration(opts = {}) {
let result = '';
@ -136,12 +156,18 @@ export default Backbone.Model.extend(Styleable).extend({
}
return result;
},
}
/**
* Returns CSS string of the rule
* @param {Object} [opts={}] Options
* @return {string}
* Return the CSS string of the rule
* @param {Object} [opts={}] Options (same as in `getDeclaration`)
* @return {String} CSS string
* @example
* const cssRule = editor.Css.setRule('.class1', { color: 'red' }, {
* atRuleType: 'media',
* atRuleParams: '(min-width: 500px)'
* });
* cssRule.toCSS() // "@media (min-width: 500px){.class1{color:red;}}"
*/
toCSS(opts = {}) {
let result = '';
@ -154,13 +180,13 @@ export default Backbone.Model.extend(Styleable).extend({
}
return result;
},
}
toJSON(...args) {
const obj = Backbone.Model.prototype.toJSON.apply(this, args);
const obj = Model.prototype.toJSON.apply(this, args);
if (this.em.getConfig('avoidDefaults')) {
const defaults = this.defaults;
const defaults = this.defaults();
forEach(defaults, (value, key) => {
if (obj[key] === value) {
@ -173,7 +199,7 @@ export default Backbone.Model.extend(Styleable).extend({
}
return obj;
},
}
/**
* Compare the actual model with parameters
@ -216,4 +242,4 @@ export default Backbone.Model.extend(Styleable).extend({
return true;
}
});
}

30
src/device_manager/config/config.js

@ -1,3 +1,31 @@
export default {
devices: []
// The device `id` to select on start, if not indicated, the first available from `devices` will be used.
default: null,
// Default devices
devices: [
{
id: 'desktop',
name: 'Desktop',
width: ''
},
{
id: 'tablet',
name: 'Tablet',
width: '768px',
widthMedia: '992px'
},
{
id: 'mobileLandscape',
name: 'Mobile landscape',
width: '568px',
widthMedia: '768px'
},
{
id: 'mobilePortrait',
name: 'Mobile portrait',
width: '320px',
widthMedia: '480px'
}
]
};

234
src/device_manager/index.js

@ -11,125 +11,221 @@
* Once the editor is instantiated you can use its API. Before using these methods you should get the module from the instance
*
* ```js
* const deviceManager = editor.DeviceManager;
* const deviceManager = editor.Devices;
* ```
* ## Available Events
* * `device:add` - Added new device. The [Device] is passed as an argument to the callback
* * `device:remove` - Device removed. The [Device] is passed as an argument to the callback
* * `device:select` - New device selected. The newly selected [Device] and the previous one, are passed as arguments to the callback
* * `device:update` - Device updated. The updated [Device] and the object containing changes are passed as arguments to the callback
* * `device` - Catch-all event for all the events mentioned above. An object containing all the available data about the triggered event is passed as an argument to the callback
*
* ## Methods
* * [add](#add)
* * [get](#get)
* * [getAll](#getAll)
* * [getDevices](#getdevices)
* * [remove](#remove)
* * [select](#select)
* * [getSelected](#getselected)
*
* @module DeviceManager
* [Device]: device.html
*
* @module Devices
*/
import { isString } from 'underscore';
import Module from 'common/module';
import defaults from './config/config';
import Device from './model/Device';
import Devices from './model/Devices';
import DevicesView from './view/DevicesView';
export const evAll = 'device';
export const evPfx = `${evAll}:`;
export const evSelect = `${evPfx}select`;
export const evSelectBefore = `${evSelect}:before`;
export const evUpdate = `${evPfx}update`;
export const evAdd = `${evPfx}add`;
export const evAddBefore = `${evAdd}:before`;
export const evRemove = `${evPfx}remove`;
export const evRemoveBefore = `${evRemove}:before`;
const chnSel = 'change:device';
export default () => {
var c = {};
var devices, view;
let c = {};
let devices;
let view;
return {
/**
* Name of the module
* @type {String}
* @private
*/
...Module,
name: 'DeviceManager',
/**
* Initialize module. Automatically called with a new instance of the editor
* @param {Object} config Configurations
* @param {Array<Object>} [config.devices=[]] Default devices
* @example
* ...
* {
* devices: [
* {name: 'Desktop', width: ''}
* {name: 'Tablet', width: '991px'}
* ],
* }
* ...
* @return {this}
* @private
*/
init(config) {
c = config || {};
for (var name in defaults) {
if (!(name in c)) c[name] = defaults[name];
}
Device,
Devices,
events: {
all: evAll,
select: evSelect,
// selectBefore: evSelectBefore,
update: evUpdate,
add: evAdd,
// addBefore: evAddBefore,
remove: evRemove,
removeBefore: evRemoveBefore
},
init(config = {}) {
c = { ...defaults, ...config };
const { em } = c;
devices = new Devices();
(c.devices || []).forEach(dv => this.add(dv.id || dv.name, dv.width, dv));
view = new DevicesView({
collection: devices,
config: c
});
c.devices.forEach(dv => this.add(dv));
this.em = em;
this.all = devices;
this.select(c.default || devices.at(0));
this.__initListen();
em.on(chnSel, this._onSelect, this);
return this;
},
_onSelect(m, deviceId, opts) {
const { em, events } = this;
const prevId = m.previous('device');
const newDevice = this.get(deviceId);
const ev = events.select;
em.trigger(ev, newDevice, this.get(prevId));
this.__catchAllEvent(ev, newDevice, opts);
},
/**
* Add new device to the collection. URLs are supposed to be unique
* @param {String} id Device id
* @param {String} width Width of the device
* @param {Object} [opts] Custom options
* @returns {Device} Added device
* Add new device
* @param {Object} props Device properties
* @returns {[Device]} Added device
* @example
* deviceManager.add('tablet', '900px');
* deviceManager.add('tablet2', '900px', {
* height: '300px',
* // At first, GrapesJS tries to localize the name by device id.
* // In case is not found, the `name` property is used (or `id` if name is missing)
* const device1 = deviceManager.add({
* // Without an explicit ID, the `name` will be taken. In case of missing `name`, a random ID will be created.
* id: 'tablet',
* name: 'Tablet',
* width: '900px', // This width will be applied on the canvas frame and for the CSS media
* });
* const device2 = deviceManager.add({
* id: 'tablet2',
* name: 'Tablet 2',
* widthMedia: '810px', // the width that will be used for the CSS media
* width: '800px', // This width will be applied on the canvas frame
* widthMedia: '810px', // This width that will be used for the CSS media
* height: '600px', // Height will be applied on the canvas frame
* });
*/
add(id, width, opts = {}) {
const obj = {
...opts,
id,
name: opts.name || id,
width: width
};
return devices.add(obj);
add(props, options = {}) {
let result;
let opts = options;
// Support old API
if (isString(props)) {
const width = options;
opts = arguments[2] || {};
result = {
...opts,
id: props,
name: opts.name || props,
width
};
} else {
result = props;
}
if (!result.id) {
result.id = result.name || this._createId();
}
return devices.add(result, opts);
},
/**
* Return device by name
* @param {string} name Name of the device
* Return device by ID
* @param {String} id ID of the device
* @returns {[Device]|null}
* @example
* var device = deviceManager.get('Tablet');
* const device = deviceManager.get('Tablet');
* console.log(JSON.stringify(device));
* // {name: 'Tablet', width: '900px'}
*/
get(name) {
return devices.get(name);
get(id) {
// Support old API
const byName = this.getAll().filter(d => d.get('name') === id)[0];
return byName || devices.get(id) || null;
},
/**
* Remove device
* @param {String|[Device]} device Device or device id
* @returns {[Device]} Removed device
* @example
* const removed = deviceManager.remove('device-id');
* // or by passing the Device
* const device = deviceManager.get('device-id');
* deviceManager.remove(device);
*/
remove(device, opts = {}) {
return this.__remove(device, opts);
},
/**
* Return all devices
* @return {Collection}
* @returns {Array<[Device]>}
* @example
* var devices = deviceManager.getAll();
* const devices = deviceManager.getDevices();
* console.log(JSON.stringify(devices));
* // [{name: 'Desktop', width: ''}, ...]
*/
getAll() {
return devices;
getDevices() {
return devices.models;
},
/**
* Change the selected device. This will update the frame in the canvas
* @param {String|[Device]} device Device or device id
* @example
* deviceManager.select('some-id');
* // or by passing the page
* const device = deviceManager.get('some-id');
* deviceManager.select(device);
*/
select(device, opts = {}) {
const md = isString(device) ? this.get(device) : device;
md && this.em.set('device', md.get('id'), opts);
return this;
},
/**
* Render devices
* @return {string} HTML string
* @private
* Get the selected device
* @returns {[Device]}
* @example
* const selected = deviceManager.getSelected();
*/
getSelected() {
return this.get(this.em.get('device'));
},
getAll() {
return devices;
},
render() {
view && view.remove();
view = new DevicesView({
collection: devices,
config: c
});
return view.render().el;
},
destroy() {
devices.reset();
devices.stopListening();
view.remove();
devices.reset();
view && view.remove();
[devices, view].forEach(i => (i = null));
c = {};
}

45
src/device_manager/model/Device.js

@ -1,24 +1,23 @@
import Backbone from 'backbone';
export default Backbone.Model.extend({
idAttribute: 'name',
defaults: {
name: '',
// Width to set for the editor iframe
width: null,
// Height to set for the editor iframe
height: '',
// The width which will be used in media queries,
// If empty the width will be used
widthMedia: null,
// Setup the order of media queries
priority: null
},
import { Model } from 'backbone';
/**
* @typedef Device
* @property {String} [name=''] Device type, eg. `Mobile`
* @property {String} [width] Width to set for the editor iframe, eg. '900px'
* @property {String} [height=''] Height to set for the editor iframe, eg. '600px'
* @property {String} [widthMedia=''] The width which will be used in media queries, If empty the width will be used
* @property {Number} [priority=null] Setup the order of media queries
*/
export default class Device extends Model {
defaults() {
return {
name: '',
width: null,
height: '',
widthMedia: null,
priority: null
};
}
initialize() {
this.get('widthMedia') === null &&
@ -28,11 +27,11 @@ export default Backbone.Model.extend({
this.set('priority', parseFloat(this.get('widthMedia')) || 0);
const toCheck = ['width', 'height', 'widthMedia'];
toCheck.forEach(prop => this.checkUnit(prop));
},
}
checkUnit(prop) {
const pr = this.get(prop) || '';
const noUnit = (parseFloat(pr) || 0).toString() === pr.toString();
noUnit && this.set(prop, `${pr}px`);
}
});
}

14
src/device_manager/model/Devices.js

@ -1,15 +1,15 @@
import Backbone from 'backbone';
import { Collection } from 'backbone';
import Device from './Device';
export default Backbone.Collection.extend({
model: Device,
comparator: (left, right) => {
export default class Devices extends Collection {
comparator(left, right) {
const max = Number.MAX_VALUE;
return (right.get('priority') || max) - (left.get('priority') || max);
},
}
getSorted() {
return this.sort();
}
});
}
Devices.prototype.model = Device;

62
src/device_manager/view/DevicesView.js

@ -1,22 +1,27 @@
import { template } from 'underscore';
import Backbone from 'backbone';
import { View } from 'backbone';
import html from 'utils/html';
export default Backbone.View.extend({
template: template(`
<div class="<%= ppfx %>device-label"><%= deviceLabel %></div>
<div class="<%= ppfx %>field <%= ppfx %>select">
<span id="<%= ppfx %>input-holder">
<select class="<%= ppfx %>devices"></select>
</span>
<div class="<%= ppfx %>sel-arrow">
<div class="<%= ppfx %>d-s-arrow"></div>
export default class DevicesView extends View {
template({ ppfx, label }) {
return html`
<div class="${ppfx}device-label">${label}</div>
<div class="${ppfx}field ${ppfx}select">
<span id="${ppfx}input-holder">
<select class="${ppfx}devices"></select>
</span>
<div class="${ppfx}sel-arrow">
<div class="${ppfx}d-s-arrow"></div>
</div>
</div>
</div>
<button style="display:none" class="<%= ppfx %>add-trasp">+</button>`),
<button style="display:none" class="${ppfx}add-trasp">+</button>
`;
}
events: {
change: 'updateDevice'
},
events() {
return {
change: 'updateDevice'
};
}
initialize(o) {
this.config = o.config || {};
@ -25,14 +30,14 @@ export default Backbone.View.extend({
this.events['click .' + this.ppfx + 'add-trasp'] = this.startAdd;
this.listenTo(this.em, 'change:device', this.updateSelect);
this.delegateEvents();
},
}
/**
* Start adding new device
* @return {[type]} [description]
* @private
*/
startAdd() {},
startAdd() {}
/**
* Update device of the editor
@ -45,7 +50,7 @@ export default Backbone.View.extend({
var val = devEl ? devEl.val() : '';
em.set('device', val);
}
},
}
/**
* Update select value on device update
@ -56,10 +61,10 @@ export default Backbone.View.extend({
var devEl = this.devicesEl;
if (em && em.getDeviceModel && devEl) {
var device = em.getDeviceModel();
var name = device ? device.get('name') : '';
var name = device ? device.get('id') : '';
devEl.val(name);
}
},
}
/**
* Return devices options
@ -73,23 +78,20 @@ export default Backbone.View.extend({
collection.each(device => {
const { name, id } = device.attributes;
const label = (em && em.t && em.t(`deviceManager.devices.${id}`)) || name;
result += `<option value="${name}">${label}</option>`;
result += `<option value="${id || name}">${label}</option>`;
});
return result;
},
}
render() {
const { em, ppfx, $el, el } = this;
$el.html(
this.template({
ppfx,
deviceLabel: em && em.t && em.t('deviceManager.device')
})
);
const label = em && em.t && em.t('deviceManager.device');
$el.html(this.template({ ppfx, label }));
this.devicesEl = $el.find(`.${ppfx}devices`);
this.devicesEl.append(this.getOptions());
this.devicesEl.val(em.get('device'));
el.className = `${ppfx}devices-c`;
return this;
}
});
}

3
src/dom_components/config/config.js

@ -35,7 +35,8 @@ export default {
*/
processor: 0,
// List of void elements
// List of HTML void elements
// https://www.w3.org/TR/2011/WD-html-markup-20110113/syntax.html#void-elements
voidElements: [
'area',
'base',

2
src/dom_components/index.js

@ -526,7 +526,7 @@ export default () => {
...model,
...getExtendedObj(extendFn, model, modelToExt),
defaults: {
...modelToExt.prototype.defaults,
...(result(modelToExt.prototype, 'defaults') || {}),
...(result(model, 'defaults') || {})
}
},

3437
src/dom_components/model/Component.js

File diff suppressed because it is too large

3
src/dom_components/model/ComponentFrame.js

@ -1,4 +1,5 @@
import Component from './Component';
import { toLowerCase } from 'utils/mixins';
const type = 'iframe';
@ -17,6 +18,6 @@ export default Component.extend(
}
},
{
isComponent: el => el.tagName === 'IFRAME'
isComponent: el => toLowerCase(el.tagName) === type
}
);

17
src/dom_components/model/ComponentImage.js

@ -1,5 +1,6 @@
import { result } from 'underscore';
import Component from './Component';
import { toLowerCase } from 'utils/mixins';
const svgAttrs =
'xmlns="http://www.w3.org/2000/svg" width="100" viewBox="0 0 24 24" style="fill: rgba(0,0,0,0.15); transform: scale(0.75)"';
@ -138,20 +139,6 @@ export default Component.extend(
}
},
{
/**
* Detect if the passed element is a valid component.
* In case the element is valid an object abstracted
* from the element will be returned
* @param {HTMLElement}
* @return {Object}
* @private
*/
isComponent(el) {
var result = '';
if (el.tagName == 'IMG') {
result = { type: 'image' };
}
return result;
}
isComponent: el => toLowerCase(el.tagName) === 'img'
}
);

12
src/dom_components/model/ComponentLabel.js

@ -1,18 +1,18 @@
import Component from './ComponentText';
import { toLowerCase } from 'utils/mixins';
const type = 'label';
export default Component.extend(
{
defaults: {
...Component.prototype.defaults,
tagName: 'label',
type,
tagName: type,
traits: ['id', 'title', 'for']
}
},
{
isComponent(el) {
if (el.tagName == 'LABEL') {
return { type: 'label' };
}
}
isComponent: el => toLowerCase(el.tagName) === type
}
);

4
src/dom_components/model/ComponentLink.js

@ -1,4 +1,5 @@
import Component from './ComponentText';
import { toLowerCase } from 'utils/mixins';
export default Component.extend(
{
@ -23,9 +24,8 @@ export default Component.extend(
{
isComponent(el) {
let result;
let avoidEdit;
if (el.tagName == 'A') {
if (toLowerCase(el.tagName) === 'a') {
result = {
type: 'link',
editable: 0

6
src/dom_components/model/ComponentMap.js

@ -1,5 +1,6 @@
import Component from './ComponentImage';
import OComponent from './Component';
import { toLowerCase } from 'utils/mixins';
export default Component.extend(
{
@ -100,7 +101,10 @@ export default Component.extend(
*/
isComponent(el) {
var result = '';
if (el.tagName == 'IFRAME' && /maps\.google\.com/.test(el.src)) {
if (
toLowerCase(el.tagName) == 'iframe' &&
/maps\.google\.com/.test(el.src)
) {
result = { type: 'map', src: el.src };
}
return result;

10
src/dom_components/model/ComponentScript.js

@ -1,10 +1,14 @@
import Component from './Component';
import { toLowerCase } from 'utils/mixins';
const type = 'script';
export default Component.extend(
{
defaults: {
...Component.prototype.defaults,
type: 'script',
type,
tagName: type,
droppable: false,
draggable: false,
layerable: false
@ -12,8 +16,8 @@ export default Component.extend(
},
{
isComponent(el) {
if (el.tagName == 'SCRIPT') {
var result = { type: 'script' };
if (toLowerCase(el.tagName) == type) {
const result = { type };
if (el.src) {
result.src = el.src;

18
src/dom_components/model/ComponentSvg.js

@ -1,11 +1,16 @@
import Component from './Component';
import { toLowerCase } from 'utils/mixins';
const type = 'svg';
export default Component.extend(
{
defaults: {
...Component.prototype.defaults,
resizable: { ratioDefault: 1 },
highlightable: 0
type,
tagName: type,
highlightable: 0,
resizable: { ratioDefault: 1 }
},
getName() {
@ -16,13 +21,6 @@ export default Component.extend(
}
},
{
isComponent(el) {
if (SVGElement && el instanceof SVGElement) {
return {
tagName: el.tagName,
type: 'svg'
};
}
}
isComponent: el => toLowerCase(el.tagName) === type
}
);

9
src/dom_components/model/ComponentSvgIn.js

@ -13,13 +13,6 @@ export default Component.extend(
}
},
{
isComponent(el) {
if (Component.isComponent(el) && el.tagName.toLowerCase() !== 'svg') {
return {
tagName: el.tagName,
type: 'svg-in'
};
}
}
isComponent: (el, opts = {}) => !!opts.inSvg
}
);

17
src/dom_components/model/ComponentTable.js

@ -1,11 +1,14 @@
import Component from './Component';
import { toLowerCase } from 'utils/mixins';
const type = 'table';
export default Component.extend(
{
defaults: {
...Component.prototype.defaults,
type: 'table',
tagName: 'table',
type,
tagName: type,
droppable: ['tbody', 'thead', 'tfoot']
},
@ -16,14 +19,6 @@ export default Component.extend(
}
},
{
isComponent(el) {
let result = '';
if (el.tagName == 'TABLE') {
result = { type: 'table' };
}
return result;
}
isComponent: el => toLowerCase(el.tagName) === type
}
);

17
src/dom_components/model/ComponentTableBody.js

@ -1,11 +1,14 @@
import Component from './Component';
import { toLowerCase } from 'utils/mixins';
const type = 'tbody';
export default Component.extend(
{
defaults: {
...Component.prototype.defaults,
type: 'tbody',
tagName: 'tbody',
type,
tagName: type,
draggable: ['table'],
droppable: ['tr'],
columns: 1,
@ -45,14 +48,6 @@ export default Component.extend(
}
},
{
isComponent(el) {
let result = '';
if (el.tagName == 'TBODY') {
result = { type: 'tbody' };
}
return result;
}
isComponent: el => toLowerCase(el.tagName) === type
}
);

15
src/dom_components/model/ComponentTableCell.js

@ -1,4 +1,5 @@
import Component from './Component';
import { toLowerCase } from 'utils/mixins';
export default Component.extend(
{
@ -10,18 +11,6 @@ export default Component.extend(
}
},
{
isComponent(el) {
let result = '';
const tag = el.tagName;
if (tag == 'TD' || tag == 'TH') {
result = {
type: 'cell',
tagName: tag.toLowerCase()
};
}
return result;
}
isComponent: el => ['td', 'th'].indexOf(toLowerCase(el.tagName)) >= 0
}
);

17
src/dom_components/model/ComponentTableFoot.js

@ -1,22 +1,17 @@
import ComponentTableBody from './ComponentTableBody';
import { toLowerCase } from 'utils/mixins';
const type = 'tfoot';
export default ComponentTableBody.extend(
{
defaults: {
...ComponentTableBody.prototype.defaults,
type: 'tfoot',
tagName: 'tfoot'
type,
tagName: type
}
},
{
isComponent(el) {
let result = '';
if (el.tagName == 'TFOOT') {
result = { type: 'tfoot' };
}
return result;
}
isComponent: el => toLowerCase(el.tagName) === type
}
);

17
src/dom_components/model/ComponentTableHead.js

@ -1,22 +1,17 @@
import ComponentTableBody from './ComponentTableBody';
import { toLowerCase } from 'utils/mixins';
const type = 'thead';
export default ComponentTableBody.extend(
{
defaults: {
...ComponentTableBody.prototype.defaults,
type: 'thead',
tagName: 'thead'
type,
tagName: type
}
},
{
isComponent(el) {
let result = '';
if (el.tagName == 'THEAD') {
result = { type: 'thead' };
}
return result;
}
isComponent: el => toLowerCase(el.tagName) === type
}
);

7
src/dom_components/model/ComponentTableRow.js

@ -1,15 +1,18 @@
import Component from './Component';
import { toLowerCase } from 'utils/mixins';
const tagName = 'tr';
export default Component.extend(
{
defaults: {
...Component.prototype.defaults,
tagName: 'tr',
tagName,
draggable: ['thead', 'tbody', 'tfoot'],
droppable: ['th', 'td']
}
},
{
isComponent: el => el.tagName == 'TR' && true
isComponent: el => toLowerCase(el.tagName) === tagName
}
);

10
src/dom_components/model/ComponentTextNode.js

@ -1,4 +1,5 @@
import Component from './Component';
import { escape } from 'utils/mixins';
export default Component.extend(
{
@ -12,12 +13,9 @@ export default Component.extend(
},
toHTML() {
return this.get('content')
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
const parent = this.parent();
const cnt = this.get('content');
return parent && parent.is('script') ? cnt : escape(cnt);
}
},
{

24
src/dom_components/model/ComponentVideo.js

@ -1,5 +1,7 @@
import Component from './ComponentImage';
import { toLowerCase } from 'utils/mixins';
const type = 'video';
const yt = 'yt';
const vi = 'vi';
const ytnc = 'ytnc';
@ -8,8 +10,8 @@ export default Component.extend(
{
defaults: {
...Component.prototype.defaults,
type: 'video',
tagName: 'video',
type,
tagName: type,
videoId: '',
void: 0,
provider: 'so', // on change of provider, traits are switched
@ -337,14 +339,18 @@ export default Component.extend(
* @private
*/
isComponent(el) {
var result = '';
var isYtProv = /youtube\.com\/embed/.test(el.src);
var isYtncProv = /youtube-nocookie\.com\/embed/.test(el.src);
var isViProv = /player\.vimeo\.com\/video/.test(el.src);
var isExtProv = isYtProv || isYtncProv || isViProv;
if (el.tagName == 'VIDEO' || (el.tagName == 'IFRAME' && isExtProv)) {
let result = '';
const { tagName, src } = el;
const isYtProv = /youtube\.com\/embed/.test(src);
const isYtncProv = /youtube-nocookie\.com\/embed/.test(src);
const isViProv = /player\.vimeo\.com\/video/.test(src);
const isExtProv = isYtProv || isYtncProv || isViProv;
if (
toLowerCase(tagName) == type ||
(toLowerCase(tagName) == 'iframe' && isExtProv)
) {
result = { type: 'video' };
if (el.src) result.src = el.src;
if (src) result.src = src;
if (isExtProv) {
if (isYtProv) result.provider = yt;
else if (isYtncProv) result.provider = ytnc;

14
src/dom_components/view/ComponentImageView.js

@ -62,17 +62,17 @@ export default ComponentView.extend({
* */
onActive(ev) {
ev && ev.stopPropagation();
var em = this.opts.config.em;
var editor = em ? em.get('Editor') : '';
const { em, model } = this;
const ed = em && em.get('Editor');
if (editor && this.model.get('editable')) {
editor.runCommand('open-assets', {
target: this.model,
if (ed && model.get('editable')) {
ed.runCommand('open-assets', {
target: model,
types: ['image'],
accept: 'image/*',
onSelect() {
editor.Modal.close();
editor.AssetManager.setTarget(null);
ed.Modal.close();
ed.AssetManager.setTarget(null);
}
});
}

59
src/dom_components/view/ComponentScriptView.js

@ -1,4 +1,3 @@
import Backbone from 'backbone';
import ComponentView from './ComponentImageView';
export default ComponentView.extend({
@ -7,45 +6,33 @@ export default ComponentView.extend({
events: {},
render() {
var model = this.model;
var src = model.get('src');
var em = this.em;
var scriptCount = em && em.get('scriptCount') ? em.get('scriptCount') : 0;
var content = '';
const { model, em } = this;
const src = model.get('src');
const scrCnt = em && em.get('scriptCount');
const scriptCount = scrCnt ? scrCnt : 0;
let content = '';
// If it's an external script
if (src) {
var onload = model.get('onload');
var svar = 'script' + scriptCount;
var svarNext = 'script' + (scriptCount + 1);
content =
'var ' +
svar +
" = document.createElement('script');\n" +
svar +
'.onload = function(){\n' +
(onload ? onload + '();\n' : '') +
'typeof ' +
svarNext +
"Start == 'function' && " +
svarNext +
'Start();\n' +
'};\n' +
svar +
".src = '" +
src +
"';\n" +
'function ' +
svar +
'Start() { document.body.appendChild(' +
svar +
'); };\n' +
(!scriptCount ? svar + 'Start();' : '');
if (em) {
em.set('scriptCount', scriptCount + 1);
}
const onload = model.get('onload');
const svar = `script${scriptCount}`;
const svarNext = `script${scriptCount + 1}`;
const svarFn = `${svar}Start`;
const svarNextFn = `${svarNext}Start`;
// Load multiple external scripts in the correct order
content = `
var ${svar} = document.createElement('script');
${svar}.onload = function() {
${onload ? `${onload}();\n` : ''}
typeof ${svarNextFn} == 'function' && ${svarNextFn}();
};
${svar}.src = '${src}';
function ${svarFn}() { document.body.appendChild(${svar}); };
${!scriptCount ? `${svarFn}();` : ''}
`;
em && em.set('scriptCount', scriptCount + 1);
} else {
content = model.get('content');
content = model.__innerHTML();
}
this.el.innerHTML = content;

11
src/dom_components/view/ComponentView.js

@ -108,13 +108,6 @@ export default Backbone.View.extend({
views.splice(views.indexOf(view), 1);
view.removed(view._clbObj());
view.$el.data({ model: '', collection: '', view: '' });
delete view.model;
delete view.$el;
delete view.el.__gjsv;
delete view.childrenView;
delete view.scriptContainer;
delete view.opts;
// delete view.el;
return view;
},
@ -300,7 +293,7 @@ export default Backbone.View.extend({
* */
updateAttributes() {
const attrs = [];
const { model, $el, el, config } = this;
const { model, $el, el } = this;
const { highlightable, textable, type } = model.attributes;
const defaultAttr = {
@ -318,6 +311,7 @@ export default Backbone.View.extend({
// Remove all current attributes
each(el.attributes, attr => attrs.push(attr.nodeName));
attrs.forEach(attr => $el.removeAttr(attr));
this.updateStyle();
const attr = {
...defaultAttr,
...model.getAttributes()
@ -327,7 +321,6 @@ export default Backbone.View.extend({
keys(attr).forEach(key => attr[key] === false && delete attr[key]);
$el.attr(attr);
this.updateStyle();
},
/**

2
src/dom_components/view/ComponentsView.js

@ -63,7 +63,7 @@ export default Backbone.View.extend({
const sameFrameView = frameView.model && model.getView(frameView.model);
const dt =
opts.componentTypes || (em && em.get('DomComponents').getTypes());
const type = model.get('type');
const type = model.get('type') || 'default';
let viewObject = this.compView;
for (let it = 0; it < dt.length; it++) {

28
src/editor/config/config.js

@ -212,33 +212,7 @@ export default {
selectorManager: {},
//Configurations for Device Manager
deviceManager: {
devices: [
{
id: 'desktop',
name: 'Desktop',
width: ''
},
{
id: 'tablet',
name: 'Tablet',
width: '768px',
widthMedia: '992px'
},
{
id: 'mobileLandscape',
name: 'Mobile landscape',
width: '568px',
widthMedia: '768px'
},
{
id: 'mobilePortrait',
name: 'Mobile portrait',
width: '320px',
widthMedia: '480px'
}
]
},
deviceManager: {},
//Configurations for Style Manager
styleManager: {

98
src/editor/index.js

@ -88,9 +88,6 @@
* ### Modal
* * `modal:open` - Modal is opened
* * `modal:close` - Modal is closed
* ### Parser
* * `parse:html` - On HTML parse, an object containing the input and the output of the parser is passed as an argument
* * `parse:css` - On CSS parse, an object containing the input and the output of the parser is passed as an argument
* ### Commands
* * `run:{commandName}` - Triggered when some command is called to run (eg. editor.runCommand('preview'))
* * `stop:{commandName}` - Triggered when some command is called to stop (eg. editor.stopCommand('preview'))
@ -99,6 +96,10 @@
* * `abort:{commandName}` - Triggered when the command execution is aborted (`editor.on(`run:preview:before`, opts => opts.abort = 1);`)
* * `run` - Triggered on run of any command. The id and the result are passed as arguments to the callback
* * `stop` - Triggered on stop of any command. The id and the result are passed as arguments to the callback
* ### Devices
* Check the [Devices](/api/device_manager.html) module.
* ### Parser
* Check the [Parser](/api/parser.html) module.
* ### Pages
* Check the [Pages](/api/pages.html) module.
* ### General
@ -114,6 +115,7 @@ import $ from 'cash-dom';
import defaults from './config/config';
import EditorModel from './model/Editor';
import EditorView from './view/EditorView';
import html from 'utils/html';
export default (config = {}) => {
const c = {
@ -122,11 +124,8 @@ export default (config = {}) => {
};
c.pStylePrefix = c.stylePrefix;
var em = new EditorModel(c);
var editorView = new EditorView({
model: em,
config: c
});
let em = new EditorModel(c);
let editorView;
return {
$,
@ -212,6 +211,7 @@ export default (config = {}) => {
/**
* Returns HTML built inside canvas
* @param {Object} [opts={}] Options
* @param {Component} [opts.component] Return the HTML of a specific Component
* @param {Boolean} [opts.cleanId=false] Remove unnecessary IDs (eg. those created automatically)
* @returns {string} HTML string
*/
@ -222,8 +222,10 @@ export default (config = {}) => {
/**
* Returns CSS built inside canvas
* @param {Object} [opts={}] Options
* @param {Component} [opts.component] Return the CSS of a specific Component
* @param {Boolean} [opts.json=false] Return an array of CssRules instead of the CSS string
* @param {Boolean} [opts.avoidProtected=false] Don't include protected CSS
* @returns {string} CSS string
* @returns {String|Array<CssRule>} CSS string or array of CssRules
*/
getCss(opts) {
return em.getCss(opts);
@ -232,8 +234,8 @@ export default (config = {}) => {
/**
* Returns JS of all components
* @param {Object} [opts={}] Options
* @param {Component} [opts.component] Get the JS of a particular component
* @returns {string} JS string
* @param {Component} [opts.component] Get the JS of a specific component
* @returns {String} JS string
*/
getJs(opts) {
return em.getJs(opts);
@ -306,13 +308,12 @@ export default (config = {}) => {
/**
* Set style inside editor's canvas. This method overrides actual style
* @param {Array<Object>|Object|string} style CSS string or style model
* @param {Object} opt the options object to be used by the [setStyle]{@link em#setStyle} method
* @return {this}
* @example
* editor.setStyle('.cls{color: red}');
* //or
* editor.setStyle({
* selectors: ['cls']
* selectors: ['cls'],
* style: { color: 'red' }
* });
*/
@ -321,6 +322,17 @@ export default (config = {}) => {
return this;
},
/**
* Add styles to the editor
* @param {Array<Object>|Object|string} style CSS string or style model
* @returns {Array<CssRule>} Array of created CssRule instances
* @example
* editor.addStyle('.cls{color: red}');
*/
addStyle(style, opts = {}) {
return em.addStyle(style, opts);
},
/**
* Returns the last selected component, if there is one
* @return {Model}
@ -406,6 +418,19 @@ export default (config = {}) => {
return this;
},
/**
* Returns, if active, the Component enabled in rich text editing mode.
* @returns {Component|null}
* @example
* const textComp = editor.getEditing();
* if (textComp) {
* console.log('HTML: ', textComp.toHTML());
* }
*/
getEditing() {
return em.getEditing();
},
/**
* Set device to the editor. If the device exists it will
* change the canvas to the proper width
@ -464,6 +489,17 @@ export default (config = {}) => {
return em.store(clb);
},
/**
* Get the JSON data object, which could be stored and loaded back with `editor.loadData(json)`
* @returns {Object}
* @example
* console.log(editor.storeData());
* // { pages: [...], styles: [...], ... }
*/
storeData() {
return em.storeData();
},
/**
* Load data from the current storage
* @param {Function} clb Callback function
@ -473,6 +509,17 @@ export default (config = {}) => {
return em.load(clb);
},
/**
* Load data from the JSON data object
* @param {Object} data Data to load
* @return {Object} Loaded object
* @example
* editor.loadData({ pages: [...], styles: [...], ... })
*/
loadData(data) {
return em.loadData(data);
},
/**
* Returns container element. The one which was indicated as 'container'
* on init method
@ -666,7 +713,7 @@ export default (config = {}) => {
* @private
*/
getEl() {
return editorView.el;
return editorView && editorView.el;
},
/**
@ -683,8 +730,25 @@ export default (config = {}) => {
* @return {HTMLElement}
*/
render() {
editorView.render();
return editorView.el;
}
editorView && editorView.remove();
editorView = new EditorView({
model: em,
config: c
});
return editorView.render().el;
},
/**
* Print safe HTML by using ES6 tagged template strings.
* @param {Array<String>} literals
* @param {Array<String>} substs
* @returns {String}
* @example
* const unsafeStr = '<script>....</script>';
* const safeStr = '<b>Hello</b>';
* // Use `$${var}` to avoid escaping
* const strHtml = editor.html`Escaped ${unsafeStr}, unescaped $${safeStr}`;
*/
html
};
};

105
src/editor/model/Editor.js

@ -10,7 +10,7 @@ import {
import $ from 'cash-dom';
import Backbone from 'backbone';
import Extender from 'utils/extender';
import { getModel } from 'utils/mixins';
import { getModel, hasWin } from 'utils/mixins';
import Selected from './Selected';
Backbone.$ = $;
@ -484,6 +484,17 @@ export default Backbone.Model.extend({
return this;
},
/**
* Add styles to the editor
* @param {Array<Object>|Object|string} style CSS string or style model
* @returns {Array<CssRule>}
* @private
*/
addStyle(style, opts = {}) {
const res = this.getStyle().add(style, opts);
return isArray(res) ? res : [res];
},
/**
* Returns rules/style model from the editor's canvas
* @return {Rules}
@ -518,14 +529,12 @@ export default Backbone.Model.extend({
* @private
*/
getHtml(opts = {}) {
const config = this.config;
const { optsHtml } = config;
const exportWrapper = config.exportWrapper;
const wrapperIsBody = config.wrapperIsBody;
const { config } = this;
const { optsHtml, exportWrapper, wrapperIsBody } = config;
const js = config.jsInHtml ? this.getJs(opts) : '';
var wrp = opts.component || this.get('DomComponents').getComponent();
var html = wrp
? this.get('CodeManager').getCode(wrp, 'html', {
const cmp = opts.component || this.get('DomComponents').getComponent();
let html = cmp
? this.get('CodeManager').getCode(cmp, 'html', {
exportWrapper,
wrapperIsBody,
...optsHtml,
@ -544,8 +553,7 @@ export default Backbone.Model.extend({
*/
getCss(opts = {}) {
const config = this.config;
const { optsCss } = config;
const wrapperIsBody = config.wrapperIsBody;
const { optsCss, wrapperIsBody } = config;
const avoidProt = opts.avoidProtected;
const keepUnusedStyles = !isUndefined(opts.keepUnusedStyles)
? opts.keepUnusedStyles
@ -553,16 +561,16 @@ export default Backbone.Model.extend({
const cssc = this.get('CssComposer');
const wrp = opts.component || this.get('DomComponents').getComponent();
const protCss = !avoidProt ? config.protectedCss : '';
return wrp
? protCss +
this.get('CodeManager').getCode(wrp, 'css', {
cssc,
wrapperIsBody,
keepUnusedStyles,
...optsCss
})
: '';
const css =
wrp &&
this.get('CodeManager').getCode(wrp, 'css', {
cssc,
wrapperIsBody,
keepUnusedStyles,
...optsCss,
...opts
});
return wrp ? (opts.json ? css : protCss + css) : '';
},
/**
@ -586,16 +594,10 @@ export default Backbone.Model.extend({
* @private
*/
store(clb) {
var sm = this.get('StorageManager');
var store = {};
const sm = this.get('StorageManager');
if (!sm) return;
// Fetch what to store
this.get('storables').forEach(m => {
var obj = m.store(1);
for (var el in obj) store[el] = obj[el];
});
const store = this.storeData();
sm.store(store, res => {
clb && clb(res, store);
this.set('changesCount', 0);
@ -605,6 +607,18 @@ export default Backbone.Model.extend({
return store;
},
storeData() {
let result = {};
// Sync content if there is an active RTE
const editingCmp = this.getEditing();
editingCmp && editingCmp.trigger('sync:content', { noCount: true });
this.get('storables').forEach(m => {
result = { ...result, ...m.store(1) };
});
return result;
},
/**
* Load data from the current storage
* @param {Function} clb Callback function
@ -612,14 +626,23 @@ export default Backbone.Model.extend({
*/
load(clb = null) {
this.getCacheLoad(1, res => {
this.get('storables').forEach(module => {
module.load(res);
module.postLoad && module.postLoad(this);
});
this.loadData(res);
clb && clb(res);
});
},
loadData(data = {}) {
const sm = this.get('StorageManager');
const result = sm.__clearKeys(data);
this.get('storables').forEach(module => {
module.load(result);
module.postLoad && module.postLoad(this);
});
return result;
},
/**
* Returns cached load
* @param {Boolean} force Force to reload
@ -679,7 +702,7 @@ export default Backbone.Model.extend({
stopDefault(opts = {}) {
const commands = this.get('Commands');
const command = commands.get(this.config.defaultCommand);
if (!command) return;
if (!command || !this.defaultRunning) return;
command.stop(this, this, opts);
this.defaultRunning = 0;
},
@ -775,7 +798,7 @@ export default Backbone.Model.extend({
* Destroy editor
*/
destroyAll() {
const { config } = this;
const { config, view } = this;
const editor = this.getEditor();
const { editors = [] } = config.grapesjs || {};
this.stopDefault();
@ -783,7 +806,7 @@ export default Backbone.Model.extend({
.slice()
.reverse()
.forEach(mod => mod.destroy());
this.view.remove();
view && view.remove();
this.stopListening();
this.clear({ silent: true });
this.destroyed = 1;
@ -791,9 +814,15 @@ export default Backbone.Model.extend({
i => (this[i] = {})
);
editors.splice(editors.indexOf(editor), 1);
$(config.el)
.empty()
.attr(this.attrsOrig);
hasWin() &&
$(config.el)
.empty()
.attr(this.attrsOrig);
},
getEditing() {
const res = this.get('editing');
return (res && res.model) || null;
},
setEditing(value) {

3
src/i18n/index.js

@ -27,6 +27,7 @@
* @module I18n
*/
import { isUndefined, isString } from 'underscore';
import { hasWin } from 'utils/mixins';
import config from './config';
const isObj = el => !Array.isArray(el) && el !== null && typeof el === 'object';
@ -210,7 +211,7 @@ export default () => {
},
_localLang() {
const nav = window.navigator || {};
const nav = (hasWin() && window.navigator) || {};
const lang = nav.language || nav.userLanguage;
return lang ? lang.split('-')[0] : 'en';
},

9
src/index.js

@ -36,6 +36,7 @@ export default {
* @param {Boolean} [config.autorender=true] If true, auto-render the content
* @param {Array} [config.plugins=[]] Array of plugins to execute on start
* @param {Object} [config.pluginsOpts={}] Custom options for plugins
* @param {Boolean} [config.headless=false] Init headless editor
* @return {Editor} Editor instance
* @example
* var editor = grapesjs.init({
@ -45,10 +46,12 @@ export default {
* })
*/
init(config = {}) {
const { headless } = config;
const els = config.container;
if (!els) throw new Error("'container' is required");
if (!els && !headless) throw new Error("'container' is required");
config = { ...defaultConfig, ...config, grapesjs: this };
config.el = isElement(els) ? els : document.querySelector(els);
config.el =
!headless && (isElement(els) ? els : document.querySelector(els));
const editor = new Editor(config).init();
const em = editor.getModel();
@ -79,7 +82,7 @@ export default {
// A plugin might have extended/added some custom type so this
// is a good point to load stuff like components, css rules, etc.
em.loadOnStart();
config.autorender && editor.render();
config.autorender && !headless && editor.render();
editors.push(editor);
return editor;

5
src/keymaps/index.js

@ -32,7 +32,10 @@
*/
import { isString } from 'underscore';
import keymaster from 'keymaster';
import { hasWin } from 'utils/mixins';
import keymaster from 'utils/keymaster';
hasWin() && keymaster.init(window);
export default () => {
let em;

13
src/modal_dialog/config/config.js

@ -5,5 +5,16 @@ export default {
content: '',
backdrop: true
backdrop: true,
/**
* Extend ModalView object (view/ModalView.js)
* @example
* extend: {
* template() {
* return '<div>...New modal template...</div>';
* },
* },
*/
extend: {}
};

17
src/modal_dialog/index.js

@ -69,10 +69,6 @@ export default () => {
model = new ModalM(c);
model.on('change:open', (m, enb) => triggerEvent(enb, em));
modal = new ModalView({
model,
config: c
});
return this;
},
@ -93,7 +89,8 @@ export default () => {
open(opts = {}) {
opts.title && this.setTitle(opts.title);
opts.content && this.setContent(opts.content);
modal.show(opts);
model.open();
modal && modal.updateAttr(opts.attributes);
return this;
},
@ -102,7 +99,7 @@ export default () => {
* @return {this}
*/
close() {
modal.hide();
model.close();
return this;
},
@ -201,11 +198,17 @@ export default () => {
* @private
*/
render() {
const View = ModalView.extend(c.extend);
modal && modal.remove();
modal = new View({
model,
config: c
});
return modal.render().$el;
},
destroy() {
modal.remove();
modal && modal.remove();
[c, model, modal].forEach(i => (i = {}));
this.em = {};
}

24
src/modal_dialog/model/Modal.js

@ -1,9 +1,19 @@
import Backbone from 'backbone';
import { Model } from 'backbone';
export default Backbone.Model.extend({
defaults: {
title: '',
content: '',
open: false
export default class Modal extends Model {
defaults() {
return {
title: '',
content: '',
open: false
};
}
});
open() {
this.set('open', true);
}
close() {
this.set('open', false);
}
}

47
src/modal_dialog/view/ModalView.js

@ -1,6 +1,6 @@
import Backbone from 'backbone';
import { View } from 'backbone';
export default Backbone.View.extend({
export default class ModalView extends View {
template({ pfx, ppfx, content, title }) {
return `<div class="${pfx}dialog ${ppfx}one-bg ${ppfx}two-color">
<div class="${pfx}header">
@ -13,12 +13,14 @@ export default Backbone.View.extend({
</div>
</div>
<div class="${pfx}collector" style="display: none"></div>`;
},
}
events: {
click: 'onClick',
'click [data-close-modal]': 'hide'
},
events() {
return {
click: 'onClick',
'click [data-close-modal]': 'hide'
};
}
initialize(o) {
const model = this.model;
@ -30,12 +32,12 @@ export default Backbone.View.extend({
this.listenTo(model, 'change:open', this.updateOpen);
this.listenTo(model, 'change:title', this.updateTitle);
this.listenTo(model, 'change:content', this.updateContent);
},
}
onClick(e) {
const bkd = this.config.backdrop;
bkd && e.target === this.el && this.hide();
},
}
/**
* Returns collector element
@ -46,7 +48,7 @@ export default Backbone.View.extend({
if (!this.$collector)
this.$collector = this.$el.find('.' + this.pfx + 'collector');
return this.$collector;
},
}
/**
* Returns content element
@ -61,7 +63,7 @@ export default Backbone.View.extend({
}
return this.$content;
},
}
/**
* Returns title element
@ -71,7 +73,7 @@ export default Backbone.View.extend({
getTitle() {
if (!this.$title) this.$title = this.$el.find('.' + this.pfx + 'title');
return this.$title.get(0);
},
}
/**
* Update content
@ -84,7 +86,7 @@ export default Backbone.View.extend({
const body = this.model.get('content');
children.length && coll.append(children);
content.empty().append(body);
},
}
/**
* Update title
@ -93,7 +95,7 @@ export default Backbone.View.extend({
updateTitle() {
var title = this.getTitle();
if (title) title.innerHTML = this.model.get('title');
},
}
/**
* Update open
@ -101,24 +103,23 @@ export default Backbone.View.extend({
* */
updateOpen() {
this.el.style.display = this.model.get('open') ? '' : 'none';
},
}
/**
* Hide modal
* @private
* */
hide() {
this.model.set('open', 0);
},
this.model.close();
}
/**
* Show modal
* @private
* */
show(opts = {}) {
this.model.set('open', 1);
this.updateAttr(opts.attributes);
},
show() {
this.model.open();
}
updateAttr(attr) {
const { pfx, $el, el } = this;
@ -128,7 +129,7 @@ export default Backbone.View.extend({
...(attr || {}),
class: `${pfx}container ${(attr && attr.class) || ''}`.trim()
});
},
}
render() {
const el = this.$el;
@ -140,4 +141,4 @@ export default Backbone.View.extend({
this.updateOpen();
return this;
}
});
}

19
src/navigator/index.js

@ -22,14 +22,6 @@ export default () => {
},
onLoad() {
const ItemView = View.extend(config.extend);
layers = new ItemView({
ItemView,
level: 0,
config,
opened: config.opened || {},
model: em.get('DomComponents').getWrapper()
});
em && em.on('component:selected', this.componentChanged);
this.componentChanged();
},
@ -60,7 +52,7 @@ export default () => {
* @return {Component}
*/
getRoot() {
return layers.model;
return layers && layers.model;
},
/**
@ -96,6 +88,15 @@ export default () => {
},
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;
},

18
src/pages/index.js

@ -37,6 +37,8 @@
* * [select](#select)
* * [getSelected](#getselected)
*
* [Page]: page.html
*
* @module Pages
*/
@ -141,7 +143,7 @@ export default () => {
* Add new page
* @param {Object} props Page properties
* @param {Object} [opts] Options
* @returns {Page}
* @returns {[Page]}
* @example
* const newPage = pageManager.add({
* id: 'new-page-id', // without an explicit ID, a random one will be created
@ -163,8 +165,8 @@ export default () => {
/**
* Remove page
* @param {String|Page} page Page or page id
* @returns {Page}
* @param {String|[Page]} page Page or page id
* @returns {[Page]}
* @example
* const removedPage = pageManager.remove('page-id');
* // or by passing the page
@ -185,7 +187,7 @@ export default () => {
/**
* Get page by id
* @param {String} id Page id
* @returns {Page}
* @returns {[Page]}
* @example
* const somePage = pageManager.get('page-id');
*/
@ -195,7 +197,7 @@ export default () => {
/**
* Get main page (the first one available)
* @returns {Page}
* @returns {[Page]}
* @example
* const mainPage = pageManager.getMain();
*/
@ -206,7 +208,7 @@ export default () => {
/**
* Get all pages
* @returns {Array<Page>}
* @returns {Array<[Page]>}
* @example
* const arrayOfPages = pageManager.getAll();
*/
@ -223,7 +225,7 @@ export default () => {
/**
* Change the selected page. This will switch the page rendered in canvas
* @param {String|Page} page Page or page id
* @param {String|[Page]} page Page or page id
* @returns {this}
* @example
* pageManager.select('page-id');
@ -242,7 +244,7 @@ export default () => {
/**
* Get the selected page
* @returns {Page}
* @returns {[Page]}
* @example
* const selectedPage = pageManager.getSelected();
*/

23
src/pages/model/Page.js

@ -36,10 +36,33 @@ export default class Page extends Model {
return this.get('frames');
}
/**
* Get all frames
* @returns {Array<Frame>}
* @example
* const arrayOfFrames = page.getAllFrames();
*/
getAllFrames() {
return this.getFrames().models || [];
}
/**
* Get the first frame of the page (identified always as the main one)
* @returns {Frame}
* @example
* const mainFrame = page.getMainFrame();
*/
getMainFrame() {
return this.getFrames().at(0);
}
/**
* Get the root component (usually is the `wrapper` component) from the main frame
* @returns {Component}
* @example
* const rootComponent = page.getMainComponent();
* console.log(rootComponent.toHTML());
*/
getMainComponent() {
const frame = this.getMainFrame();
return frame && frame.getComponent();

13
src/panels/index.js

@ -57,10 +57,6 @@ export default () => {
if (ppfx) c.stylePrefix = ppfx + c.stylePrefix;
panels = new Panels(c.defaults);
PanelsViewObj = new PanelsView({
collection: panels,
config: c
});
return this;
},
@ -77,7 +73,7 @@ export default () => {
* @return {HTMLElement}
*/
getPanelsEl() {
return PanelsViewObj.el;
return PanelsViewObj && PanelsViewObj.el;
},
/**
@ -205,6 +201,11 @@ export default () => {
* @private
*/
render() {
PanelsViewObj && PanelsViewObj.remove();
PanelsViewObj = new PanelsView({
collection: panels,
config: c
});
return PanelsViewObj.render().el;
},
@ -235,7 +236,7 @@ export default () => {
destroy() {
panels.reset();
panels.stopListening();
PanelsViewObj.remove();
PanelsViewObj && PanelsViewObj.remove();
[c, panels, PanelsViewObj].forEach(i => (i = {}));
},

14
src/parser/config/config.js

@ -2,8 +2,20 @@ export default {
textTags: ['br', 'b', 'i', 'u', 'a', 'ul', 'ol'],
// Custom CSS parser
// @see https://grapesjs.com/docs/guides/Custom-CSS-parser.html
parserCss: null,
// Custom HTML parser
parserHtml: null
// At the moment, the custom HTML parser should rely on DOM Node instance as the result
// @example
// The return should be an instance of an Node as the root to traverse
// https://developer.mozilla.org/en-US/docs/Web/API/Node
// parserHtml: (input, opts = {}) => (new DOMParser()).parseFromString(input, 'text/xml')
// Here the result will be XMLDocument, which extends Node
parserHtml: null,
// DOMParser mime type (default 'text/html')
// @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString
// If you use the `text/html` parser, it will fix the invalid syntax automatically
htmlType: null
};

99
src/parser/index.js

@ -1,3 +1,29 @@
/**
* You can customize the initial state of the module from the editor initialization, by passing the following [Configuration Object](https://github.com/artf/grapesjs/blob/master/src/parser/config/config.js)
* ```js
* const editor = grapesjs.init({
* parser: {
* // options
* }
* })
* ```
*
* Once the editor is instantiated you can use its API. Before using these methods you should get the module from the instance
*
* ```js
* const { Parser } = editor;
* ```
* ## Available Events
* * `parse:html` - On HTML parse, an object containing the input and the output of the parser is passed as an argument
* * `parse:css` - On CSS parse, an object containing the input and the output of the parser is passed as an argument
*
* ## Methods
* * [getConfig](#getconfig)
* * [parseHtml](#parsehtml)
* * [parseCss](#parsecss)
*
* @module Parser
*/
import defaults from './config/config';
import parserCss from './model/ParserCss';
import parserHtml from './model/ParserHtml';
@ -13,36 +39,8 @@ export default () => {
parserHtml: null,
/**
* Name of the module
* @type {String}
* @private
*/
name: 'Parser',
/**
* Get config object
* @return {Object}
*/
getConfig() {
return conf;
},
/**
* Initialize module. Automatically called with a new instance of the editor
* @param {Object} config Configurations
* @param {Array<Object>} [config.blocks=[]] Default blocks
* @return {this}
* @example
* ...
* {
* blocks: [
* {id:'h1-block' label: 'Heading', content:'<h1>...</h1>'},
* ...
* ],
* }
* ...
*/
init(config = {}) {
conf = { ...defaults, ...config };
conf.Parser = this;
@ -55,23 +53,48 @@ export default () => {
},
/**
* Parse HTML string and return valid model
* @param {string} str HTML string
* @return {Object}
* Get the configuration object
* @returns {Object} Configuration object
* @example
* console.log(Parser.getConfig())
*/
getConfig() {
return conf;
},
/**
* Parse HTML string and return the object containing the Component Definition
* @param {String} input HTML string to parse
* @param {Object} [options] Options
* @param {String} [options.htmlType] [HTML mime type](https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString#Argument02) to parse
* @returns {Object} Object containing the result `{ html: ..., css: ... }`
* @example
* const resHtml = Parser.parseHtml(`<table><div>Hi</div></table>`, {
* htmlType: 'text/html', // default
* });
* // By using the `text/html`, this will fix automatically all the HTML syntax issues
* // Indeed the final representation, in this case, will be `<div>Hi</div><table></table>`
* const resXml = Parser.parseHtml(`<table><div>Hi</div></table>`, {
* htmlType: 'application/xml',
* });
* // This will preserve the original format as, from the XML point of view, is a valid format
*/
parseHtml(str) {
parseHtml(input, options = {}) {
const { em, compTypes } = this;
pHtml.compTypes = em ? em.get('DomComponents').getTypes() : compTypes;
return pHtml.parse(str, pCss);
return pHtml.parse(input, pCss, options);
},
/**
* Parse CSS string and return valid model
* @param {string} str CSS string
* @return {Array<Object>}
* Parse CSS string and return an array of valid definition objects for CSSRules
* @param {String} input CSS string to parse
* @returns {Array<Object>} Array containing the result
* @example
* const res = Parser.parseCss('.cls { color: red }');
* // [{ ... }]
*/
parseCss(str) {
return pCss.parse(str);
parseCss(input) {
return pCss.parse(input);
},
destroy() {

80
src/parser/model/BrowserParserHtml.js

@ -1,4 +1,57 @@
// PoC
import { each } from 'underscore';
const htmlType = 'text/html';
const defaultType = htmlType; // 'application/xml';
export default (str, config = {}) => {
const parser = new DOMParser();
const mimeType = config.htmlType || defaultType;
const toHTML = mimeType === htmlType;
const strF = toHTML ? str : `<div>${str}</div>`;
const doc = parser.parseFromString(strF, mimeType);
let res;
if (toHTML) {
// Replicate the old parser in order to avoid breaking changes
const { head, body } = doc;
// Move all scripts at the bottom of the page
const scripts = head.querySelectorAll('script');
each(scripts, node => body.appendChild(node));
// Move inside body all head children
const hEls = [];
each(head.children, n => hEls.push(n));
each(hEls, (node, i) => body.insertBefore(node, body.children[i]));
res = body;
} else {
res = doc.firstChild;
}
return res;
};
/**
* POC, custom html parser specs
* Parse an HTML string to an array of nodes
* example
* parse(`<div class="mycls" data-test>Hello</div><span>World <b>example</b></span>`)
* // result
* [
* {
* tagName: 'div',
* attributes: { class: 'mycls', 'data-test': '' },
* childNodes: ['Hello'],
* },{
* tagName: 'span',
* childNodes: [
* 'World ',
* {
* tagName: 'b',
* childNodes: ['example'],
* }
* ],
* }
* ]
*
export const parseNodes = nodes => {
const result = [];
@ -44,29 +97,7 @@ export const parseNode = el => {
};
};
/**
* Parse an HTML string to an array of nodes
* @example
* parse(`<div class="mycls" data-test>Hello</div><span>World <b>example</b></span>`)
* // result
* [
* {
* tagName: 'div',
* attributes: { class: 'mycls', 'data-test': '' },
* childNodes: ['Hello'],
* },{
* tagName: 'span',
* childNodes: [
* 'World ',
* {
* tagName: 'b',
* childNodes: ['example'],
* }
* ],
* }
* ]
*/
export default (str, config) => {
export default (str, config = {}) => {
const result = [];
const el = document.createElement('div');
el.innerHTML = str;
@ -79,3 +110,4 @@ export default (str, config) => {
return result;
};
*/

40
src/parser/model/ParserHtml.js

@ -1,9 +1,10 @@
import { each, isString } from 'underscore';
import { each, isString, isFunction } from 'underscore';
import BrowserParserHtml from './BrowserParserHtml';
export default config => {
var TEXT_NODE = 'span';
var c = config;
var modelAttrStart = 'data-gjs-';
let c = config;
const modelAttrStart = 'data-gjs-';
const event = 'parse:html';
return {
compTypes: '',
@ -101,7 +102,7 @@ export default config => {
* @param {HTMLElement} el DOM element to traverse
* @return {Array<Object>}
*/
parseNode(el) {
parseNode(el, opts = {}) {
const result = [];
const nodes = el.childNodes;
@ -128,7 +129,7 @@ export default config => {
// the first with a valid result will be that component
for (let it = 0; it < ct.length; it++) {
const compType = ct[it];
obj = compType.model.isComponent(node);
obj = compType.model.isComponent(node, opts);
if (obj) {
if (typeof obj !== 'object') {
@ -207,7 +208,10 @@ export default config => {
content: firstChild.nodeValue
};
} else {
model.components = this.parseNode(node);
model.components = this.parseNode(node, {
inSvg: model.type === 'svg',
...opts
});
}
}
@ -227,6 +231,11 @@ export default config => {
}
}
// Check for custom void elements (valid in XML)
if (!nodeChild && `${node.outerHTML}`.slice(-2) === '/>') {
model.void = true;
}
// If all children are texts and there is some textnode the parent should
// be text too otherwise I'm unable to edit texnodes
const comps = model.components;
@ -273,17 +282,19 @@ export default config => {
* @param {ParserCss} parserCss In case there is style tags inside HTML
* @return {Object}
*/
parse(str, parserCss) {
parse(str, parserCss, opts = {}) {
const { em } = c;
const config = (em && em.get('Config')) || {};
const res = { html: '', css: '' };
const el = document.createElement('div');
el.innerHTML = str;
const conf = (em && em.get('Config')) || {};
const res = { html: null, css: null };
const cf = { ...config, ...opts };
const el = isFunction(cf.parserHtml)
? cf.parserHtml(str, cf)
: BrowserParserHtml(str, cf);
const scripts = el.querySelectorAll('script');
let i = scripts.length;
// Remove all scripts
if (!config.allowScripts) {
if (!conf.allowScripts) {
while (i--) scripts[i].parentNode.removeChild(scripts[i]);
}
@ -301,12 +312,13 @@ export default config => {
if (styleStr) res.css = parserCss.parse(styleStr);
}
em && em.trigger(`${event}:root`, { input: str, root: el });
const result = this.parseNode(el);
// I have to keep it otherwise it breaks the DomComponents.addComponent (returns always array)
const resHtml =
result.length === 1 && !c.returnArray ? result[0] : result;
res.html = resHtml;
em && em.trigger('parse:html', { input: str, output: res });
em && em.trigger(event, { input: str, output: res });
return res;
}

3
src/rich_text_editor/index.js

@ -27,7 +27,7 @@
*/
import RichTextEditor from './model/RichTextEditor';
import { on, off } from 'utils/mixins';
import { on, hasWin } from 'utils/mixins';
import defaults from './config/config';
export default () => {
@ -75,6 +75,7 @@ export default () => {
this.pfx = config.stylePrefix;
actions = config.actions || [];
if (!hasWin()) return this;
toolbar = document.createElement('div');
toolbar.className = `${ppfx}rte-toolbar ${ppfx}one-bg`;
globalRte = this.initRte(document.createElement('div'));

26
src/selector_manager/index.js

@ -99,11 +99,6 @@ export default config => {
c.stylePrefix = ppfx + c.stylePrefix;
}
this.selectorTags = new ClassTagsView({
collection: new Selectors([], { em, config: c }),
config: c
});
// Global selectors container
selectors = new Selectors(c.selectors);
selectors.on('add', model => em.trigger('selector:add', model));
@ -133,6 +128,7 @@ export default config => {
select(value, opts = {}) {
const targets = Array.isArray(value) ? value : [value];
const toSelect = this.em.get('StyleManager').setTarget(targets, opts);
const selTags = this.selectorTags;
const res = toSelect
.filter(i => i)
.map(sel =>
@ -142,7 +138,7 @@ export default config => {
? sel
: sel.getSelectorsString()
);
this.selectorTags.componentChanged({ targets: res });
selTags && selTags.componentChanged({ targets: res });
return this;
},
@ -305,19 +301,21 @@ export default config => {
* @private
*/
render(selectors) {
if (selectors) {
this.selectorTags = new ClassTagsView({
collection: new Selectors(selectors),
config: c
});
return this.selectorTags.render().el;
} else return this.selectorTags.render().el;
const { em, selectorTags } = this;
selectorTags && selectorTags.remove();
this.selectorTags = new ClassTagsView({
collection: new Selectors(selectors || [], { em, config: c }),
config: c
});
return this.selectorTags.render().el;
},
destroy() {
const { selectorTags } = this;
selectors.reset();
selectors.stopListening();
this.selectorTags.remove();
selectorTags && selectorTags.remove();
[c, selectors].forEach(i => (i = {}));
this.em = {};
this.selectorTags = {};

10
src/selector_manager/view/ClassTagsView.js

@ -67,11 +67,10 @@ export default Backbone.View.extend({
const coll = this.collection;
this.target = this.config.em;
this.em = em;
const emitter = this.getStyleEmitter();
const toList = 'component:toggled component:update:classes';
const toListCls = 'component:update:classes change:state';
this.listenTo(em, toList, this.componentChanged);
this.listenTo(emitter, 'update', this.componentChanged);
this.listenTo(em, 'styleManager:update', this.componentChanged);
this.listenTo(em, toListCls, this.__handleStateChange);
this.listenTo(em, 'styleable:change change:device', this.checkSync); // component:styleUpdate
this.listenTo(coll, 'add', this.addNew);
@ -116,13 +115,6 @@ export default Backbone.View.extend({
});
},
getStyleEmitter() {
const { em } = this;
const sm = em && em.get('StyleManager');
const emitter = sm && sm.getEmitter();
return emitter || {};
},
/**
* Triggered when a tag is removed from collection
* @param {Object} model Removed model

32
src/storage_manager/index.js

@ -239,9 +239,9 @@ export default () => {
* });
* */
load(keys, clb) {
var st = this.get(this.getCurrent());
var keysF = [];
var result = {};
const st = this.get(this.getCurrent());
const keysF = [];
let result = {};
if (typeof keys === 'string') keys = [keys];
this.onStart('load', keys);
@ -254,13 +254,7 @@ export default () => {
st.load(
keysF,
res => {
// Restore keys name
var reg = new RegExp('^' + c.id + '');
for (var itemKey in res) {
var itemKeyR = itemKey.replace(reg, '');
result[itemKeyR] = res[itemKey];
}
result = this.__clearKeys(res);
this.onAfter('load', result);
clb && clb(result);
this.onEnd('load', result);
@ -275,6 +269,24 @@ export default () => {
}
},
/**
* Restore key names
* @param {Object} data
* @returns {Object}
* @private
*/
__clearKeys(data = {}) {
const result = {};
const reg = new RegExp('^' + c.id + '');
for (let itemKey in data) {
const itemKeyR = itemKey.replace(reg, '');
result[itemKeyR] = data[itemKey];
}
return result;
},
/**
* Load default storages
* @return {this}

52
src/storage_manager/model/LocalStorage.js

@ -1,6 +1,7 @@
import Backbone from 'backbone';
import { Model } from 'backbone';
import { hasWin } from 'utils/mixins';
export default Backbone.Model.extend({
export default Model.extend({
defaults: {
checkLocal: true
},
@ -8,31 +9,28 @@ export default Backbone.Model.extend({
/**
* @private
*/
store(data, clb) {
this.checkStorageEnvironment();
for (var key in data) localStorage.setItem(key, data[key]);
if (typeof clb == 'function') {
clb();
store(data, clb = () => {}) {
if (this.hasLocal()) {
for (let key in data) localStorage.setItem(key, data[key]);
}
clb && clb();
},
/**
* @private
*/
load(keys, clb) {
this.checkStorageEnvironment();
var result = {};
for (var i = 0, len = keys.length; i < len; i++) {
var value = localStorage.getItem(keys[i]);
if (value) result[keys[i]] = value;
load(keys, clb = () => {}) {
const result = {};
if (this.hasLocal()) {
for (let i = 0, len = keys.length; i < len; i++) {
const value = localStorage.getItem(keys[i]);
if (value) result[keys[i]] = value;
}
}
if (typeof clb == 'function') {
clb(result);
}
clb && clb(result);
return result;
},
@ -41,9 +39,9 @@ export default Backbone.Model.extend({
* @private
*/
remove(keys) {
this.checkStorageEnvironment();
if (!this.hasLocal()) return;
for (var i = 0, len = keys.length; i < len; i++)
for (let i = 0, len = keys.length; i < len; i++)
localStorage.removeItem(keys[i]);
},
@ -51,8 +49,14 @@ export default Backbone.Model.extend({
* Check storage environment
* @private
* */
checkStorageEnvironment() {
if (this.get('checkLocal') && !localStorage)
console.warn("Your browser doesn't support localStorage");
hasLocal() {
const win = hasWin();
if (this.get('checkLocal') && (!win || !localStorage)) {
win && console.warn("Your browser doesn't support localStorage");
return false;
}
return true;
}
});

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

Loading…
Cancel
Save