diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js
index 27e500c5e..44009fd68 100644
--- a/docs/.vuepress/config.js
+++ b/docs/.vuepress/config.js
@@ -59,6 +59,19 @@ module.exports = {
'/': [
'',
['/getting-started', 'Getting Started'],
+ {
+ title: 'Modules',
+ collapsable: false,
+ children: [
+ ['/modules/Assets', 'Assets'],
+ ['/modules/Blocks', 'Blocks'],
+ ['/modules/Components', 'Components'],
+ ['/modules/Components-js', 'Components & JS'],
+ ['/modules/Traits', 'Traits'],
+ ['/modules/Storage', 'Storage'],
+ ['/modules/Plugins', 'Plugins'],
+ ]
+ }
],
}
},
diff --git a/docs/modules/Assets.md b/docs/modules/Assets.md
new file mode 100644
index 000000000..90fd16ee2
--- /dev/null
+++ b/docs/modules/Assets.md
@@ -0,0 +1,585 @@
+---
+title: Assets
+---
+
+# Assets
+
+
+
+In this section, you will see how to setup and take the full advantage of built-in Asset Manager in GrapesJS, which is intentionally lightweight and implements just an `image` in its core, but as you'll see next it's easy to extend and create your own asset types.
+
+[[toc]]
+
+
+## Configuration
+
+To change default configurations you have to pass `assetManager` property with the main configuration object
+
+```js
+const editor = grapesjs.init({
+ ...
+ assetManager: {
+ assets: [...],
+ ...
+ }
+});
+```
+
+
+You can update most of them later by using `getConfig` inside of the module
+
+```js
+const amConfig = editor.AssetManager.getConfig();
+```
+
+
+Below the list of currently available options
+
+```js
+ // Default assets
+ // eg. [
+ // 'https://...image1.png',
+ // 'https://...image2.png',
+ // {type: 'image', src: 'https://...image3.png', someOtherCustomProp: 1},
+ // ..
+ // ]
+ assets: [],
+
+ // Content to add where there is no assets to show
+ // eg. 'No assets here, drag to upload'
+ noAssets: '',
+
+ // Upload endpoint, set `false` to disable upload
+ // upload: 'https://endpoint/upload/assets',
+ // upload: false,
+ upload: 0,
+
+ // The name used in POST to pass uploaded files
+ uploadName: 'files',
+
+ // Custom headers to pass with the upload request
+ headers: {},
+
+ // Custom parameters to pass with the upload request, eg. csrf token
+ params: {},
+
+ // If true, tries to add automatically uploaded assets.
+ // To make it work the server should respond with a JSON containing assets
+ // in a data key, eg:
+ // {
+ // data: [
+ // 'https://.../image.png',
+ // ...
+ // {src: 'https://.../image2.png'},
+ // ...
+ // ]
+ // }
+ autoAdd: 1,
+
+ // Text on upload input
+ uploadText: 'Drop files here or click to upload',
+
+ // Label for the add button
+ addBtnText: 'Add image',
+
+ // Custom uploadFile function
+ // @example
+ // uploadFile: (e) => {
+ // var files = e.dataTransfer ? e.dataTransfer.files : e.target.files;
+ // // ...send somewhere
+ // }
+ uploadFile: '',
+
+ // Handle the image url submit from the built-in 'Add image' form
+ // @example
+ // handleAdd: (textFromInput) => {
+ // // some check...
+ // editor.AssetManager.add(textFromInput);
+ // }
+ handleAdd: '',
+
+ // Enable an upload dropzone on the entire editor (not document) when dragging
+ // files over it
+ dropzone: 1,
+
+ // Open the asset manager once files are been dropped via the dropzone
+ openAssetsOnDrop: 1,
+
+ // Any dropzone content to append inside dropzone element
+ dropzoneContent: '',
+
+ // Default title for the asset manager modal
+ modalTitle: 'Select Image',
+```
+
+Not always docs are in inline with its code, therefore we'd suggest to keep an eye at the current state of configurations by checking the dedicated source file [Asset Manager Config](https://github.com/artf/grapesjs/blob/dev/src/asset_manager/config/config.js)
+
+
+
+
+
+## Initialization
+
+The Asset Manager is ready to work by default, so just pass few urls to see them loaded
+
+```js
+const editor = grapesjs.init({
+ ...
+ assetManager: {
+ assets: [
+ 'http://placehold.it/350x250/78c5d6/fff/image1.jpg',
+ // Pass an object with your properties
+ {
+ type: 'image',
+ src: 'http://placehold.it/350x250/459ba8/fff/image2.jpg',
+ height: 350,
+ width: 250
+ },
+ {
+ // 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
+ },
+ ],
+ }
+});
+```
+
+
+If you want a complete list of available properties check the source [AssetImage Model](https://github.com/artf/grapesjs/blob/dev/src/asset_manager/model/AssetImage.js)
+
+The built-in Asset Manager modal is implemented and is showing up when requested. By default, you can make it appear by dragging Image Components in canvas, double clicking on images and all other stuff related to images (eg. CSS styling)
+
+
+[[img/assets-builtin-modal.png]]
+
+
+Showing up of the modal is registered with a command, so you can make it appear with this
+
+```js
+// This command shows only assets with `image` type
+editor.runCommand('open-assets');
+```
+
+
+Worth nothing that by doing this you can't do much with assets (if you double click on them nothing happens) and this is because you've not indicated any target. Try just to select an image in your canvas and run this in console (you should first make the editor globally available `window.editor = editor;` in your script)
+
+```js
+editor.runCommand('open-assets', {
+ target: editor.getSelected()
+});
+```
+
+
+Now you should be able to change the image of the component.
+
+
+
+
+
+## Customization
+
+If you want to customize the Asset Manager after the initialization you have to use its [APIs](API-Asset-Manager)
+
+```js
+// Get the Asset Manager module first
+const am = editor.AssetManager;
+```
+
+First of all, it's worth nothing that Asset Manager keeps 2 collections of assets:
+* **global** - which is just the one with all available assets, you can get it with `am.getAll()`
+* **visible** - this is the collection which is currently rendered by the Asset Manager, you get it with `am.getAllVisible()`
+
+This allows you to decide which assets to show and when. Let's say we'd like to have a category switcher, first of all you gonna add to the **global** collection all your assets (which you may already defined at init by `config.assetManager.assets = [...]`)
+
+```js
+am.add([
+ {
+ // You can pass any custom property you want
+ category: 'c1',
+ src: 'http://placehold.it/350x250/78c5d6/fff/image1.jpg',
+ }, {
+ category: 'c1',
+ src: 'http://placehold.it/350x250/459ba8/fff/image2.jpg',
+ }, {
+ category: 'c2',
+ src: 'http://placehold.it/350x250/79c267/fff/image3.jpg',
+ }
+ // ...
+]);
+```
+
+Now if you call the `render()`, without any argument, you will see all the assets rendered
+
+```js
+// without any argument
+am.render();
+
+am.getAll().length // <- 3
+am.getAllVisible().length // <- 3
+```
+
+Ok, now let's show only assets form the first category
+
+```js
+const assets = am.getAll();
+
+am.render(assets.filter(
+ asset => asset.get('category') == 'c1'
+));
+
+am.getAll().length // Still have 3 assets
+am.getAllVisible().length // but only 2 are shown
+```
+
+Obviously, you can mix more arrays of assets
+
+```js
+am.render([...assets1, ...assets2, ...assets3]);
+```
+
+If you want to customize the asset manager container you can get its `HTMLElement`
+
+```js
+am.getContainer().insertAdjacentHTML('afterbegin', 'Click
');
+```
+
+For more APIs methods check out the [API Reference](API-Asset-Manager)
+
+
+
+
+
+### Define new Asset type
+
+Generally speaking, an asset is not only an image, it could be a `video`, `svg-icon`, or any other kind of `document`. Each type of the asset is applied in our templates/pages differently. If you need to change the image of the Component all you need is another `url` in `src` attribute, but in case of `svg-icon`, for instance, its not the same, you might want to replace the element with a new `` content. Besides this you also have to deal with the presentation/preview of the asset inside the panel/modal, like for example showing a thumbnail for big images or the possibility to preview videos.
+
+
+Defining a new asset it means we have to push on top of the 'Stack of Types' a new layer. This stack is iterated by the editor at any addition of the asset and tries to associate the correct type.
+
+```js
+am.add('https://.../image.png');
+// string, url, ends with '.png' -> it's an 'image' type
+
+am.add(' 'svg' type
+
+am.add({type: 'video', src: '...'});
+// an object, has 'video' type key -> 'video' type
+```
+
+Obviously, it's up to you tell the editor how to recognize your type and for this purpose you have to use `isType()` method.
+Let's see now an example of how we'd start to defining a type like `svg-icon`
+
+
+```js
+am.addType('svg-icon', {
+ // `value` is for example the argument passed in `am.add(VALUE);`
+ isType(value) {
+ // The condition is intentionally simple
+ if (value.substring(0, 5) == '
+
+
+ `);
+```
+
+
+The default `open-assets` command shows only `image` assets, so to render `svg-icon` run this
+
+```js
+am.render(am.getAll().filter(
+ asset => asset.get('type') == 'svg-icon'
+));
+```
+
+
+You should see something like this
+
+[[img/assets-empty-view.png]]
+
+
+The SVG asset is not correctly rendered and this is because we haven't yet configured its view
+
+```js
+am.addType('svg-icon', {
+ view: {
+ // `getPreview()` and `getInfo()` are just few helpers, you can
+ // override the entire template with `template()`
+ // Check the base `template()` here:
+ // https://github.com/artf/grapesjs/blob/dev/src/asset_manager/view/AssetView.js
+ getPreview() {
+ return `${this.model.get('svgContent')}
`;
+ },
+ getInfo() {
+ // You can use model's properties if you passed them:
+ // am.add({
+ // type: 'svg-icon',
+ // svgContent: 'SVG description';
+ },
+ },
+ isType(value) {...}
+})
+```
+
+
+This is the result
+
+[[img/assets-svg-view.png]]
+
+
+Now we have to deal with how to assign our `svgContent` to the selected element
+
+
+```js
+am.addType('svg-icon', {
+ view: {
+ // In our case the target is the selected component
+ updateTarget(target) {
+ const svg = this.model.get('svgContent');
+
+ // Just to make things bit interesting, if it's an image type
+ // I put the svg as a data uri, content otherwise
+ if (target.get('type') == 'image') {
+ // Tip: you can also use `data:image/svg+xml;utf8,SVG2 description';
+ },
+ }),
+ // The `isType` is important, but if you omit it the default one will be added
+ // isType(value) {
+ // if (value && value.type == id) {
+ // return {type: value.type};
+ // }
+ // };
+})
+```
+
+
+You can also extend the already defined types (to be sure to load assets with the old type extended create a plugin for your definitions)
+
+```js
+// Extend the original `image` and add a confirm dialog before removing it
+am.addType('image', {
+ // As you adding on top of an already defined type you can avoid indicating
+ // `am.getType('image').view.extend({...` the editor will do it by default
+ // but you can eventually extend some other type
+ view: {
+ // If you want to see more methods to extend check out
+ // https://github.com/artf/grapesjs/blob/dev/src/asset_manager/view/AssetImageView.js
+ onRemove(e) {
+ e.stopPropagation();
+ const model = this.model;
+
+ if (confirm('Are you sure?')) {
+ model.collection.remove(model);
+ }
+ }
+ },
+})
+```
+
+
+
+
+
+## Uploading assets
+
+Asset Manager includes an easy to use, drag and drop, uploader and integrates few UI helpers. The default uploader is already visible when you open the Asset Manager.
+
+
+[[img/assets-uploader.png]]
+
+
+You can click on the uploader to start select your files or just drag them directly from your computer to trigger the uploader. Obviously, before make it work you have to setup your server in order to receive your assets and specify the upload endpoint in configurations
+
+
+```js
+let editor = grapesjs.init({
+ ...
+ assetManager: {
+ ...
+ // Upload endpoint, set `false` to disable upload, default `false`
+ upload: 'https://endpoint/upload/assets',
+
+ // The name used in POST to pass uploaded files, default: `'files'`
+ uploadName: 'files',
+ ...
+ },
+ ...
+});
+```
+
+
+
+
+
+### Listeners
+
+If you want to execute some action before/after the uploading process (eg. loading animation) or even on response, you can make use of these listeners
+
+```js
+// The upload is started
+editor.on('asset:upload:start', () => {
+ ...
+ startAnimation();
+});
+
+// The upload is ended (completed or not)
+editor.on('asset:upload:end', () => {
+ ...
+ endAnimation();
+});
+
+// Error handling
+editor.on('asset:upload:error', (err) => {
+ ...
+ notifyError(err);
+});
+
+// Do something on response
+editor.on('asset:upload:response', (response) => {
+ ...
+});
+```
+
+
+
+
+
+### Response
+
+When the uploading is over, by default (via config parameter `autoAdd: 1`), the editor expects to receive a JSON of uploaded assets in a `data` key as a response and tries to add them to the main collection. The JSON might look like this:
+
+```js
+{
+ data: [
+ 'https://.../image.png',
+ // ...
+ {
+ src: 'https://.../image2.png',
+ type: 'image',
+ height: 100,
+ width: 200,
+ },
+ // ...
+ ]
+}
+```
+
+
+
+
+
+### Setup Dropzone
+
+There is also another helper which improve the uploading of assets, a full-width editor dropzone.
+
+
+[[img/assets-full-dropzone.gif]]
+
+
+All you have to do is to activate it and possibly set a custom content (you might also want to hide the default uploader)
+
+```js
+const editor = grapesjs.init({
+ ...
+ assetManager: {
+ ...,
+ dropzone: 1,
+ dropzoneContent: 'Drop here your assets
'
+ }
+});
+```
+
+
+
+
+
+## Events
+
+Currently available events you can listen to
+
+* `asset:add` - New asset added
+* `asset:remove` - Asset removed
+* `asset:upload:start` - Before the upload is started
+* `asset:upload:end` - After the upload is ended
+* `asset:upload:error` - On any error in upload, passes the error as an argument
+* `asset:upload:response` - On upload response, passes the result as an argument
diff --git a/docs/modules/Blocks.md b/docs/modules/Blocks.md
new file mode 100644
index 000000000..560755a2b
--- /dev/null
+++ b/docs/modules/Blocks.md
@@ -0,0 +1,74 @@
+---
+title: Blocks
+---
+
+# Blocks
+
+
+
+The Block is a group of [Components] and can be easily reused inside templates.
+
+To better understand the difference between components and blocks, the component is more atomic so, for example, a single image, a text box or a map fits perfectly in this concept. The block is what the end user will drag inside the canvas, so it could contain a single image (single Component) or the entire section like, for example, the footer with a lot of components inside (texts, images, inputs, etc).
+
+Check [Components] page to see the list of built-in components and how to create your own.
+
+Let's see how to add a new block to the editor using the [Blocks API]
+
+```js
+var editor = grapesjs.init({...});
+var blockManager = editor.BlockManager;
+
+// 'my-first-block' is the ID of the block
+blockManager.add('my-first-block', {
+ label: 'Simple block',
+ content: 'This is a simple block
',
+});
+```
+
+With this snippet a new block will be added to the collection. You can also update existent blocks
+
+```js
+blockManager.get('my-first-block').set({
+ label: 'Updated simple block',
+ attributes: {
+ title: 'My title'
+ }
+})
+```
+
+As you see a simple HTML string is enough to create a block, the editor will do the rest.
+If you want you could also pass an object representing the [Component].
+
+```js
+blockManager.add('my-map-block', {
+ label: 'Simple map block',
+ content: {
+ type: 'map', // Built-in 'map' component
+ style: {
+ height: '350px'
+ },
+ removable: false, // Once inserted it can't be removed
+ }
+})
+```
+
+From the v0.3.70 it's also possible to pass the HTML string with Component's properties as attributes.
+
+```js
+blockManager.add('the-row-block', {
+ label: '2 Columns',
+ content: '',
+});
+```
+
+In the example above you're defining a row component which will accept only elements which match '.row-cell' selector and cells which could be dragged only inside '.row' elements. We're also defining the custom name which will be seen inside the Layers panel.
+If you want to check the complete list of available Component's properties, check directly the Component model source:
+https://github.com/artf/grapesjs/blob/dev/src/dom_components/model/Component.js
+
+
+[Component]:
+[Components]:
+[Blocks API]:
diff --git a/docs/modules/Components-js.md b/docs/modules/Components-js.md
new file mode 100644
index 000000000..62f8246d8
--- /dev/null
+++ b/docs/modules/Components-js.md
@@ -0,0 +1,222 @@
+---
+title: Components & JS
+---
+
+# Components & JS
+
+In this guide you'll see how to attach component related scripts and deal with external javascript libraries (for stuff like counters, galleries, slideshows, etc.)
+
+[[toc]]
+
+
+## Basic scripts
+
+Let's see how to create a component with scripts using Blocks.
+
+```js
+editor.BlockManager.add('test-block', {
+ label: 'Test block',
+ attributes: {class: 'fa fa-text'},
+ content: {
+ script: "alert('Hi'); console.log('the element', this)",
+ // Add some style just to make the component visible
+ style: {
+ width: '100px',
+ height: '100px',
+ 'background-color': 'red',
+ }
+ }
+});
+```
+Now if you drag the new block inside the canvas you'll see an alert popup and the message in console, as you might expected.
+One thing worth noting is that `this` context is binded to the component element, so, for example, if you want to change some property you'd do `this.innerHTML = 'inner content'`.
+
+One thing you should take in account is how the script is binded to component once rendered in the canvas or in your final template. If you check now the generated HTML coded by the editor (via Export button or `editor.getHtml()`), you might see something like this:
+
+```html
+
+
+```
+
+As you see the editor attaches a unique ID to all components with scripts and retrieves them via `querySelectorAll`. Dragging another `test-block` will generate this:
+
+```html
+
+
+
+```
+
+Keep in mind that all component scripts are executed only inside the iframe of the canvas (isolated, just like your final template), therefore are NOT part of the current `document` and all your external libraries (eg. JQuery) are not there, but you'll see further how to manage scripted components with dependencies.
+
+One thing you might be concerned about is a string used for the `script`, definitely not the best way to deal with a code, for this reason GrapesJS is able also to handle functions for you, so the previous example might look like this:
+
+```js
+editor.BlockManager.add('test-block', {
+ ...
+ content: {
+ script: function () {
+ alert('Hi');
+ console.log('the element', this);
+ },
+ ...
+ }
+});
+```
+Much easier now, but be aware of a string conversion, you can't use variables outside of the function scope, let's see this scenario
+
+```js
+var myVar = 'John';
+
+editor.BlockManager.add('test-block', {
+...
+ script: function () {
+ alert('Hi ' + myVar);
+ console.log('the element', this);
+ },
+...
+});
+```
+
+Unfortunately, this won't work as you'll get undefined `myVar` error. The final HTML, with script functions converted to string, will look like this:
+
+```html
+
+
+```
+
+There is actually a solution to make your scripts behave dynamically, you can interpolate properties of the component model.
+
+```js
+editor.BlockManager.add('test-block', {
+ ...
+ content: {
+ myModelPropName: 'John',
+ script: function () {
+ alert('Hi {[ myModelPropName ]}');
+ console.log('the element', this);
+ },
+ ...
+ }
+});
+```
+
+The final HTML will be:
+
+```html
+
+
+```
+
+You can even change tags used for the interpolation
+
+```js
+var editor = grapesjs.init({
+ ...
+ // Default values
+ tagVarStart: '{[ ',
+ tagVarEnd: ' ]}',
+ ...
+});
+```
+
+You can use this technique with [property Traits](https://github.com/artf/grapesjs/wiki/Traits#add-traits-to-components) to create highly customizable components.
+
+
+
+
+## Dependencies
+
+As we mentioned above, scripts are executed independently inside the iframe of the canvas, where you won't find any dependency, so exactly as the final HTML generated by the editor.
+If you want to make use of external libraries you have basically 2 types of approaches, component related and template related.
+
+### Component related
+
+If you're building, for example, a slider component based on some third-party library you probably would like to include the external file only when the component is actually dragged inside the canvas, in this case, component related approaches is the perfect one as it's loading external libraries dynamically.
+All you have to do is to require the dependency when is needed and then call your script.
+
+```js
+...
+script: function () {
+ var el = this;
+ var initMySLider = function() {
+ CoolSliderJS.init(el);
+ }
+
+ if (typeof CoolSliderJS == 'undefined') {
+ var script = document.createElement('script');
+ script.onload = initMySLider;
+ script.src = 'https://.../coolslider.min.js';
+ document.body.appendChild(script);
+ }
+},
+...
+```
+
+### Template related
+
+Some dependency might be highly used along all your components (eg. JQuery) so instead requiring it inside each script you might want to inject it directly inside the canvas:
+
+```js
+var editor = grapesjs.init({
+ ...
+ canvas: {
+ scripts: ['https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js']
+ }
+});
+
+...
+ script: function () {
+ // Do stuff using jquery
+ $('...');
+ },
+...
+```
+
+
+
+
+
+## Examples
+
+Examples of components using scripts inside
+
+* [grapesjs-navbar](https://github.com/artf/grapesjs-navbar)
+* [grapesjs-component-countdown](https://github.com/artf/grapesjs-component-countdown)
diff --git a/docs/modules/Components.md b/docs/modules/Components.md
new file mode 100644
index 000000000..e1fbeda22
--- /dev/null
+++ b/docs/modules/Components.md
@@ -0,0 +1,270 @@
+---
+title: Components
+---
+
+# Components
+
+The Component is the base element for the template composition and, usually, elements like images, text boxes, maps, etc. fit perfectly in this concept. The concept of the component was made to allow the developer to bind different behaviors to different elements. Like for example, opening the Asset Manager on double click of the image.
+
+[[toc]]
+
+
+## Built-in components
+* Default (Basic)
+* Text
+* Image
+* Video
+* Link
+* Map
+* Table
+* Row (for the table)
+* Cell (for the table)
+
+
+
+## How Components work?
+
+When we pass an HTML string to the editor like this:
+
+```html
+
+
+
bar
+
+```
+
+The editor will create and store, for each DOM element, its object representation and all next changes to the template will be made on top of this structure, which will then reflect on canvas. So, each object, usually called *Model* (or state/store), will be the source of truth for the template, but what exactly does it mean? For instance, in more practical way, once the template is rendered on the canvas, if you try to remove one of the elements using the browser inspector and then ask the editor to print the HTML (using `editor.getHtml()`) you'll see, from the code, that the element will still be there, this because the editor relies on Models and not on the DOM inside the canvas. This approach allows us to be extremely flexible on how to generate the final code (from the *Model*) and how to render it inside the canvas (from the *View*).
+
+
+
+# Manage Components
+
+## Component recognition
+
+But now, how does the editor recognize which Component to bind to the `img` element and what to do with the `span` one?
+Each Component inherits, from the base one, a particular static method
+
+```js
+/**
+ * @param {HTMLElement} el
+ * @return {Object}
+ */
+isComponent: function(el) {
+ ...
+}
+```
+
+This method gives us the possibility to recognize and bind component types to each HTMLElement (div, img, iframe, etc.). Each HTML element introduced inside the canvas will be processed by `isComponent` of all available types and if it matches, the object represented the type should be returned. So, for example, with the image component this method looks like:
+
+```js
+// Image component
+isComponent: function(el) {
+ if(el.tagName == 'IMG')
+ return {type: 'image'};
+}
+```
+
+Let's try with something that might look a little bit tricky. What about a Google's Map?!? Google's maps are generally embedded as `iframe`s, but the template can be composed by a lot of different `iframe`s, how can I tell the editor that a particular iframe is actually a Google's Map. Well, this part is up to you to understand which is the right pattern to choose, you have the `HTMLElement` so you can make all checks you want and in this particular case this pattern is used:
+
+```js
+// Map component
+isComponent: function(el) {
+ if(el.tagName == 'IFRAME' && /maps\.google\.com/.test(el.src)) {
+ return {type: 'map', src: el.src};
+ }
+},
+```
+
+So, as you see, in addition to `tagName` check, we also used the `src` property, but, as you'll see, you can actually override it with your own logic by extending the built-in component.
+
+
+
+## Define new Component
+
+Let's see now an example, with another HTML element, which is not handled by default Component types. What about `input` elements?
+
+With the default GrapesJS configuration `input`s are treated just like any other element, you can move it around, style it, etc., but usually we'd like to handle this type of element more specifically. In this case, we have to create a new Component type.
+
+Let's define just few specs for our new *Input* type:
+
+* Can be dropped only inside `form` elements
+* Can't drop other elements inside it
+* Can change the type of the input (text, password, email, etc.)
+* Can make it required for the form
+
+To define a new Component type you need to choose from which built-in Component inherit its properties, in our case we just gonna choose the default one. Let's see a complete example of the new type definition
+
+```js
+// Get DomComponents module
+var comps = editor.DomComponents;
+
+// Get the model and the view from the default Component type
+var defaultType = comps.getType('default');
+var defaultModel = defaultType.model;
+var defaultView = defaultType.view;
+
+var inputTypes = [
+ {value: 'text', name: 'Text'},
+ {value: 'email', name: 'Email'},
+ {value: 'password', name: 'Password'},
+ {value: 'number', name: 'Number'},
+];
+
+// The `input` will be the Component type ID
+comps.addType('input', {
+ // Define the Model
+ model: defaultModel.extend({
+ // Extend default properties
+ defaults: Object.assign({}, defaultModel.prototype.defaults, {
+ // Can be dropped only inside `form` elements
+ draggable: 'form, form *',
+ // Can't drop other elements inside it
+ droppable: false,
+ // Traits (Settings)
+ traits: ['name', 'placeholder', {
+ // Change the type of the input (text, password, email, etc.)
+ type: 'select',
+ label: 'Type',
+ name: 'type',
+ options: inputTypes,
+ },{
+ // Can make it required for the form
+ type: 'checkbox',
+ label: 'Required',
+ name: 'required',
+ }],
+ }),
+ },
+ // The second argument of .extend are static methods and we'll put inside our
+ // isComponent() method. As you're putting a new Component type on top of the stack,
+ // not declaring isComponent() might probably break stuff, especially if you extend
+ // the default one.
+ {
+ isComponent: function(el) {
+ if(el.tagName == 'INPUT'){
+ return {type: 'input'};
+ }
+ },
+ }),
+
+ // Define the View
+ view: defaultType.view,
+});
+```
+
+The code above is pretty much self-explanatory and as you see a lot of work is basically done on top of the Model properties.
+The *View* is just extending the default one, so to cover also this part let's add some random behavior.
+
+```js
+comps.addType('input', {
+ model: {...},
+ view: defaultType.view.extend({
+ // Bind events
+ events: {
+ // If you want to bind the event to children elements
+ // 'click .someChildrenClass': 'methodName',
+ click: 'handleClick',
+ dblclick: function(){
+ alert('Hi!');
+ }
+ },
+
+ // It doesn't make too much sense this method inside the component
+ // but it's ok as an example
+ randomHex: function() {
+ return '#' + Math.floor(Math.random()*16777216).toString(16);
+ },
+
+ handleClick: function(e) {
+ this.model.set('style', {color: this.randomHex()}); // <- Affects the final HTML code
+ this.el.style.backgroundColor = this.randomHex(); // <- Doesn't affect the final HTML code
+ // Tip: updating the model will reflect the changes to the view, so, in this case,
+ // if you put the model change after the DOM one this will override the backgroundColor
+ // change made before
+ },
+
+ // The render() should return 'this'
+ render: function () {
+ // Extend the original render method
+ defaultType.view.prototype.render.apply(this, arguments);
+ this.el.placeholder = 'Text here'; // <- Doesn't affect the final HTML code
+ return this;
+ },
+ }),
+});
+```
+
+From the example above you can notice few interesting things: how to bind events, how to update directly the DOM and how to update the model. The difference between updating the DOM and the model is that the HTML code (the one you get with `editor.getHtml()`) is generated from the *Model* so updating directly the DOM will not affect it, it's just the change for the canvas.
+
+
+
+## Update Component type
+
+Here an example of how easily you can update/override the component
+
+```js
+var originalMap = comps.getType('map');
+
+comps.addType('map', {
+ model: originalMap.model.extend({
+ // Override how the component is rendered to HTML
+ toHTML: function() {
+ return 'My Custom Map
';
+ },
+ }, {
+ isComponent: function(el) {
+ // ... new logic for isComponent
+ },
+ }),
+ view: originalMap.view
+});
+```
+
+
+
+## Components & JS
+
+If you want to know how to create Components with javascript attached (eg. counters, galleries, slideshows, etc.) check the dedicated page
+[Components & JS](Components-&-JS)
+
+
+
+
+## Hints
+
+```html
+
+ ...
+
+ ...
+
+
+
+```
+
+In the example above the editor will not get the new type from the HTML because the content is already parsed and appended, so it'll get it only with new components (eg. from Blocks)
+
+Solution 1: turn off `autorender`
+
+```html
+
+```
+Solution 2: put all the stuff inside a plugin ([Creating plugins](https://github.com/artf/grapesjs/wiki/Creating-plugins))
diff --git a/docs/modules/Plugins.md b/docs/modules/Plugins.md
new file mode 100644
index 000000000..ee5619e0d
--- /dev/null
+++ b/docs/modules/Plugins.md
@@ -0,0 +1,92 @@
+---
+title: Plugins
+---
+
+# Plugins
+
+Creating plugins in GrapesJS is pretty straightforward and here you'll get how to achieve it.
+
+[[toc]]
+
+## Basic plugin
+
+Generally, you would make plugins in separated files to keep thing cleaner, so you'll probably get a similar structure:
+
+```
+/your/path/to/grapesjs.min.js
+/your/path/to/grapesjs-plugin.js
+```
+
+The order is important as before loading your plugin, GrapesJS have to be loaded first.
+
+So, in your `grapesjs-plugin.js` file:
+
+```js
+export default grapesjs.plugins.add('my-plugin-name', (editor, options) => {
+ /*
+ * Here you should rely on GrapesJS APIs, so check 'API Reference' for more info
+ * For example, you could do something like this to add some new command:
+ *
+ * editor.Commands.add(...);
+ */
+})
+```
+
+The name `my-plugin-name` is an ID of your plugin and you'll use it to tell your editor to grab it.
+
+Here is a complete generic example:
+
+```html
+
+
+
+
+
+
+
+
+```
+
+
+
+
+
+## Plugins with options
+
+It's also possible to pass custom parameters to plugins in the way to make them more flexible.
+
+```html
+
+```
+
+Inside you plugin you'll get those options via `options` argument
+
+```js
+export default grapesjs.plugins.add('my-plugin-name', (editor, options) => {
+ console.log(options);
+ //{ customField: 'customValue' }
+})
+```
+
+
+
+
+
+## Boilerplate
+
+If you want to start with a production-ready boilerplate, you might want to try [grapesjs-plugin-boilerplate](https://github.com/artf/grapesjs-plugin-boilerplate) which you can clone and start developing a plugin immediately. For more informations check the repository
diff --git a/docs/modules/Storage.md b/docs/modules/Storage.md
new file mode 100644
index 000000000..ead5a1d60
--- /dev/null
+++ b/docs/modules/Storage.md
@@ -0,0 +1,298 @@
+---
+title: Storage
+---
+
+# Storage
+
+The aim of this guide is to show how to setup correctly your storage configuration for common usages of the editor and explain also some additional advanced settings
+
+::: warning
+This guide requires GrapesJS v0.14.15 or higher
+:::
+
+[[toc]]
+
+## Basic configuration
+
+The storage manager is a built-in module implemented inside GrapesJS which allows the persistence of your data. By default, GrapesJS saves the data locally by using the built-in `LocalStorage` which just leverages [localStorage API].
+You can initialize the editor with different storage configurations via `storageManager` option:
+```js
+const editor = grapesjs.init({
+ ...
+ // Default configurations
+ storageManager: {
+ id: 'gjs-', // Prefix identifier that will be used on parameters
+ type: 'local', // Type of the storage
+ autosave: true, // Store data automatically
+ autoload: true, // Autoload stored data on init
+ stepsBeforeSave: 1, // If autosave enabled, indicates how many changes are necessary before store method is triggered
+ },
+});
+```
+The `id` option is used to prevent collisions (quite common with localStorage) in case of multiple editors on the same page, therefore you will see parameters passed like `{ 'gjs-components': '...', 'gjs-style': '...', }`
+
+If you need to disable the storage manager you can pass any empty `type`:
+```js
+...
+storageManager: { type: null },
+```
+
+For all other available options check directly the [configuration source file](https://github.com/artf/grapesjs/blob/dev/src/storage_manager/config/config.js).
+
+
+
+
+
+## Setup remote storage
+
+Switching up the remote storage is very simple, it's just a matter of specifying your endpoints for storing and loading, which generally might be also the same (if you rely on HTTP methods).
+
+```js
+const editor = grapesjs.init({
+ ...
+ storageManager: {
+ type: 'remote',
+ stepsBeforeSave: 3,
+ urlStore: 'http://endpoint/store-template/some-id-123',
+ urlLoad: 'http://endpoint/load-template/some-id-123',
+ // For custom parameters/headers on requests
+ params: { _some_token: '....' },
+ headers: { Authorization: 'Basic ...' },
+ }
+});
+```
+As you can see we've left some default option unchanged, increased changes necessary for autosave triggering and passed remote endpoints.
+
+
+
+
+
+## Store and load templates
+
+Even without a fully working endpoint, you can see what is sent from the editor by triggering the store and looking in the network panel of the inspector. GrapesJS sends mainly 4 types of parameters and it prefixes them with the `gjs-` key (you can disable it via `storageManager.id`). From the parameters, you will get the final result in 'gjs-html' and 'gjs-css' and this is what actually your end-users will gonna see on the final template/page. The other two, 'gjs-components' and 'gjs-style', are a JSON representation of your template and therefore those should be used for the template editing. **So be careful**, GrapesJS is able to start from any HTML/CSS but use this approach only for importing already existent HTML templates, once the user starts editing, rely always on JSON objects because the HTML doesn't contain information about your components. You can achieve it in a pretty straightforward way and if you load your page by server-side you don't even need to load asynchronously your data (so you can turn off the `autoload`).
+
+```js
+// Lets say, for instance, you start with your already defined HTML template and you'd like to
+// import it on fly for the user
+const LandingPage = {
+ html: `...
`,
+ css: null,
+ components: null,
+ style: null,
+};
+// ...
+const editor = grapesjs.init({
+ ...
+ // The `components` accepts HTML string or a JSON of components
+ // Here, at first, we check and use components if are already defined, otherwise
+ // the HTML string gonna be used
+ components: LandingPage.components || LandingPage.html,
+ // We might want to make the same check for styles
+ style: LandingPage.style || LandingPage.css,
+ // As we already initialize the editor with the template we can skip the `autoload`
+ storageManager: {
+ ...
+ autoload: false,
+ },
+});
+```
+
+If for any reason you need to get the data from the remote storage you can trigger the load, at any time, manually
+
+```js
+editor.load(res => console.log('Load callback'));
+```
+
+Similarly, you have the same control over the storing. By default, the `autosave` is enabled and is triggered by how many changes are made to the template (change it via `stepsBeforeSave` option). As before, you can disable this behavior and trigger it manually when you need it
+
+```js
+...
+const editor = grapesjs.init({
+ ...
+ storageManager: {
+ ...
+ autosave: false,
+ },
+});
+// Call load somewhere
+editor.store(res => console.log('Store callback'));
+```
+
+If you need to check changes which yet need to be stored you can use `editor.getDirtyCount()`. At any, successful, store of the editor, it resets the count.
+
+
+
+
+
+## Setup the server
+
+Server configuration might differ for any use case so generally, it's something up to you on how to make it work, but usually, the flow is pretty straightforward. Create two endpoints, one for storing (eg. `mydomain.com/store-page/123`) and the other one for loading (eg. `mydomain.com/load-page/123`), you can also create just one and distinguish them via HTTP methods (eg. `mydomain.com/page/123`, via GET you load the template, with POST you store it).
+When you **store**, the editor doesn't expect any particular result but only a valid response from the server (status code 200).
+When you **load** the template, return a JSON object with the data you have (don't forget to include the `id` prefix if it's used)
+```js
+{
+ // `gjs-` is the id prefix
+ 'gjs-components': [{ tagName: 'div', ... }, {...}, ...],
+ 'gjs-style': [{...}, {...}, ...],
+}
+```
+Be sure to have a correct `Content-Type` response header, eg. in PHP you would do something like this:
+```php
+header('Content-Type: application/json');
+echo json_encode([
+ 'gjs-components': [...],
+ 'gjs-style': [...],
+]);
+```
+
+
+
+
+## Storage API
+
+The Storage module has also its own [set of API](https://github.com/artf/grapesjs/wiki/API-Storage-Manager) that allows you to extend and add new functionalities.
+
+
+
+### Define new storage
+
+One of the most useful methods of API is the possibility to add new storages. You might think, we have the `local` and `remote` storages, what else do we need, right? Well, let's take as an example the `local` one. As you already know, it relies on [localStorage API] which is really cool and easy to use but one of his specs might be a big limit, by default it has a limited amount of MB to use per site (something around 5MB-10MB, depends on the browser implementation). As an alternative, we can make use of [IndexedDB] which is also quite [well supported](https://caniuse.com/#search=indexedDB) and allows more space usage (each browser implements its own rules, for a better understanding on how browser storage limits work, check [here](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Browser_storage_limits_and_eviction_criteria)).
+[IndexedDB configuration](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Using_IndexedDB) might be too much verbose for this guide so we decided to create the [grapesjs-indexeddb] plugin, so you can check its source and see how it's implemented. For this guide we gonna see something more simpler but with the same flow, it'll be just a simple javascript object which stores key-value data, not persistent at all but the concept is the same.
+
+```js
+const editor = grapesjs.init({
+ ...
+ storageManager: { type: 'simple-storage' },
+});
+
+// Here our `simple-storage` implementation
+const SimpleStorage = {};
+
+editor.StorageManager.add('simple-storage', {
+ /**
+ * Load the data
+ * @param {Array} keys Array containing values to load, eg, ['gjs-components', 'gjs-style', ...]
+ * @param {Function} clb Callback function to call when the load is ended
+ * @param {Function} clbErr Callback function to call in case of errors
+ */
+ load(keys, clb, clbErr) {
+ const result = {};
+
+ keys.forEach(key => {
+ const value = SimpleStorage[key];
+ if (value) {
+ result[key] = value;
+ }
+ });
+
+ // Might be called inside some async method
+ clb(result);
+ },
+
+ /**
+ * Store the data
+ * @param {Object} data Data object to store
+ * @param {Function} clb Callback function to call when the load is ended
+ * @param {Function} clbErr Callback function to call in case of errors
+ */
+ store(data, clb, clbErr) {
+ for (let key in data) {
+ SimpleStorage[key] = data[key];
+ }
+ // Might be called inside some async method
+ clb();
+ }
+});
+```
+
+
+
+### Extend storage
+
+Among other needs, you might need to use existing storages to create more complex uses. For example, let's say we would like to mix the local and remote storages inside another one. This is how it would look like:
+```js
+const sm = editor.StorageManager;
+
+sm.add('local-remote', {
+ store(data, clb, clbErr) {
+ const remote = sm.get('remote');
+ const local = sm.get('local');
+ // ...
+ remote.store(data, clb, err => {
+ // eg. some error on remote side, store it locally
+ local.store(data, clb, clbError);
+ });
+ },
+
+ load(keys, clb, clbErr) {
+ // ...
+ },
+});
+```
+
+If you need to completely replace the storage, just use the same id in `add` method
+```js
+editor.StorageManager.add('local', {
+ // New logic for the local storage
+ load() {
+ // ...
+ },
+
+ store() {
+ // ...
+ },
+});
+```
+
+
+
+### Examples
+
+Here you can find some of the plugins extending the Storage Manager
+
+* [grapesjs-indexeddb] - Storage wrapper for IndexedDB
+* [grapesjs-firestore] - Storage wrapper for [Cloud Firestore](https://firebase.google.com/docs/firestore)
+
+
+
+
+
+## Events
+
+Another way to extend storage capabilities is to make use of GrapesJS's event hooks, you can check [here](https://github.com/artf/grapesjs/wiki/API-Editor#storages) the list of all available events for the Storage module. Let's see some of the cases where you might want to use them:
+
+* Loading animation on storage requests
+```js
+editor.on('storage:start', startLoading);
+editor.on('storage:end', endLoading);
+```
+* Error handling
+```js
+editor.on('storage:error', (err) => {
+ alert(`Error: ${err}`);
+});
+```
+* Extend parameters to store
+```js
+editor.on('storage:start:store', (objectToStore) => {
+ if (needToAddExtraParam) {
+ objectToStore.customHtml = `...${editor.getHtml()}...
`;
+ }
+});
+```
+* Do stuff post load
+```js
+editor.on('storage:end:load', (resultObject) => {
+ if (resultObject.hasSomeKey) {
+ // do stuff
+ }
+});
+```
+
+
+
+
+[grapesjs-indexeddb]:
+[grapesjs-firestore]:
+[localStorage API]:
+[IndexedDB]:
diff --git a/docs/modules/Traits.md b/docs/modules/Traits.md
new file mode 100644
index 000000000..b60765e94
--- /dev/null
+++ b/docs/modules/Traits.md
@@ -0,0 +1,149 @@
+---
+title: Traits
+---
+
+# Traits
+
+In GrapesJS, Traits could define different parameters and behaviors of a single component. The user generally will see traits as *Settings* of each component. A common use of traits is to customize element attributes (eg. `placeholder` for inputs) and in this case the editor comes already with some built-in, easy configurable, types.
+
+[[toc]]
+
+
+## Built-in trait types
+
+* text
+* number
+* checkbox
+* select
+* color
+
+
+
+
+
+## Add Traits to Components
+
+You can add traits to the component by extending them or while creating a new one. Let's see in this example how to make inputs more customizable by the editor. All components, by default, contain 2 traits: id and title (at the moment of writing). So, if you select an input and open the Settings panel you will see just this:
+
+[[img/default-traits.png]]
+
+In this case we gonna create a new Component ([check here](Components) for more details about the creation of new components) with a new set of traits
+
+```js
+var editor = grapesjs.init({...});
+var domComps = editor.DomComponents;
+var dType = domComps.getType('default');
+var dModel = dType.model;
+var dView = dType.view;
+
+domComps.addType('input', {
+ model: dModel.extend({
+ defaults: Object.assign({}, dModel.prototype.defaults, {
+ traits: [
+ // strings are automatically converted to text types
+ 'name',
+ 'placeholder',
+ {
+ type: 'select',
+ label: 'Type',
+ name: 'type',
+ options: [
+ {value: 'text', name: 'Text'},
+ {value: 'email', name: 'Email'},
+ {value: 'password', name: 'Password'},
+ {value: 'number', name: 'Number'},
+ ]
+ }, {
+ type: 'checkbox',
+ label: 'Required',
+ name: 'required',
+ }],
+ }),
+ }, {
+ isComponent: function(el) {
+ if(el.tagName == 'INPUT'){
+ return {type: 'input'};
+ }
+ },
+ }),
+
+ view: dView,
+});
+```
+
+Now the result will be
+
+[[img/input-custom-traits.png]]
+
+By default, traits modify attributes of the model (which than reflected in canvas) but you can also have traits which change the property
+
+```js
+...
+traits: [{
+ type: 'text',
+ label: 'Test',
+ name: 'model-prop-name',
+ changeProp: 1,
+}],
+...
+```
+
+In this way you're able to listen this changes and react with your own logic
+
+```js
+editor.DomComponents.addType('input', {
+ model: dModel.extend({
+ init() {
+ this.listenTo(this, 'change:model-prop-name', this.doStuff);
+ },
+
+ doStuff() {}
+ }),
+ ...
+});
+```
+
+
+
+
+
+## Define new Trait type
+
+If built-in types are not enough (eg. something with more complex UI) you can define a new one.
+Let's see this simple `textarea` element which updates contents of the component.
+
+```js
+// Each new type extends the default Trait
+editor.TraitManager.addType('content', {
+ events:{
+ 'keyup': 'onChange', // trigger parent onChange method on keyup
+ },
+
+ /**
+ * Returns the input element
+ * @return {HTMLElement}
+ */
+ getInputEl: function() {
+ if (!this.inputEl) {
+ var input = document.createElement('textarea');
+ input.value = this.target.get('content');
+ this.inputEl = input;
+ }
+ return this.inputEl;
+ },
+
+ /**
+ * Triggered when the value of the model is changed
+ */
+ onValueChange: function () {
+ this.target.set('content', this.model.get('value'));
+ }
+});
+
+// And then use it in your component
+...
+traits: [{
+ type: 'content',
+}],
+...
+```