From fee82781656302b3ca0b7de0e6fe90051e5807f0 Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Thu, 2 May 2019 01:12:15 +0200 Subject: [PATCH] deploy docs --- docs/404.html | 19 + docs/Home.html | 335 ++++++++++ docs/api/assets.html | 85 +++ docs/api/block_manager.html | 85 +++ docs/api/canvas.html | 57 ++ docs/api/commands.html | 76 +++ docs/api/component.html | 152 +++++ docs/api/components.html | 91 +++ docs/api/css_composer.html | 84 +++ docs/api/device_manager.html | 56 ++ docs/api/editor.html | 136 ++++ docs/api/index.html | 36 ++ docs/api/keymaps.html | 70 +++ docs/api/modal_dialog.html | 50 ++ docs/api/panels.html | 91 +++ docs/api/rich_text_editor.html | 79 +++ docs/api/selector_manager.html | 68 ++ docs/api/storage_manager.html | 70 +++ docs/api/style_manager.html | 95 +++ docs/api/undo_manager.html | 55 ++ .../js/{app.f0e5ae13.js => app.be6a3a1f.js} | 4 +- docs/faq.html | 33 + docs/getting-started.html | 584 ++++++++++++++++++ docs/guides/Custom-CSS-parser.html | 214 +++++++ docs/guides/Replace-Rich-Text-Editor.html | 106 ++++ docs/index.html | 42 ++ docs/modules/Assets.html | 372 +++++++++++ docs/modules/Blocks.html | 94 +++ docs/modules/Commands.html | 169 +++++ docs/modules/Components-js.html | 168 +++++ docs/modules/Components.html | 315 ++++++++++ docs/modules/Plugins.html | 105 ++++ docs/modules/Storage.html | 197 ++++++ docs/modules/Style-manager.html | 50 ++ docs/modules/Traits.html | 130 ++++ 35 files changed, 4371 insertions(+), 2 deletions(-) create mode 100644 docs/404.html create mode 100644 docs/Home.html create mode 100644 docs/api/assets.html create mode 100644 docs/api/block_manager.html create mode 100644 docs/api/canvas.html create mode 100644 docs/api/commands.html create mode 100644 docs/api/component.html create mode 100644 docs/api/components.html create mode 100644 docs/api/css_composer.html create mode 100644 docs/api/device_manager.html create mode 100644 docs/api/editor.html create mode 100644 docs/api/index.html create mode 100644 docs/api/keymaps.html create mode 100644 docs/api/modal_dialog.html create mode 100644 docs/api/panels.html create mode 100644 docs/api/rich_text_editor.html create mode 100644 docs/api/selector_manager.html create mode 100644 docs/api/storage_manager.html create mode 100644 docs/api/style_manager.html create mode 100644 docs/api/undo_manager.html rename docs/assets/js/{app.f0e5ae13.js => app.be6a3a1f.js} (77%) create mode 100644 docs/faq.html create mode 100644 docs/getting-started.html create mode 100644 docs/guides/Custom-CSS-parser.html create mode 100644 docs/guides/Replace-Rich-Text-Editor.html create mode 100644 docs/index.html create mode 100644 docs/modules/Assets.html create mode 100644 docs/modules/Blocks.html create mode 100644 docs/modules/Commands.html create mode 100644 docs/modules/Components-js.html create mode 100644 docs/modules/Components.html create mode 100644 docs/modules/Plugins.html create mode 100644 docs/modules/Storage.html create mode 100644 docs/modules/Style-manager.html create mode 100644 docs/modules/Traits.html diff --git a/docs/404.html b/docs/404.html new file mode 100644 index 000000000..1a49a6417 --- /dev/null +++ b/docs/404.html @@ -0,0 +1,19 @@ + + + + + + GrapesJS + + + + + + + + + +

404

Looks like we've got some broken links.
Take me home.
+ + + diff --git a/docs/Home.html b/docs/Home.html new file mode 100644 index 000000000..4b770fafa --- /dev/null +++ b/docs/Home.html @@ -0,0 +1,335 @@ + + + + + + Getting started | GrapesJS + + + + + + + + + +

Getting started

This page will introduce you to the main options of GrapesJS and how it works, in the way to be able to create your custom editor.

The pretty minimalistic way to instantiate the editor could be like this:

<link rel="stylesheet" href="path/to/grapes.min.css">
+<script src="path/to/grapes.min.js"></script>
+
+<div id="gjs"></div>
+
+<script type="text/javascript">
+  var editor = grapesjs.init({
+      container : '#gjs',
+      components: '<div class="txt-red">Hello world!</div>',
+      style: '.txt-red{color: red}',
+  });
+</script>
+

In just few lines, with the default configurations, you're already able to see something with which play around.

[[img/default-gjs.jpg]]

You'll see components commands on top left position that come handy to create and manage your blocks, below there are options which need to highlight and export them. When you select components ('mouse pointer' icon), on the right side, you should see pop up Class Manager and Style Manager options which allow to customize the style of the components. There is also a Layer Manager/Navigator ('hamburger' icon) which helps to manage easily the structure.

Of course all those stuff (panels, buttons, commands, etc.) are set just as default so you can overwrite them and add more other. Before you start to create things you should know that GrapesJS UI is composed basically by a canvas (where you will 'draw') and panels (which will contain buttons)

[[img/canvas-panels.jpg]]

If you'd like to extend the already instantiated editor you have to check API Reference. Check also how to create plugins using the same API. +In this guide we'll focus on how to initialize the editor with all custom UI from scratch.

Let's start the editor with some basic toolbar panel

...
+var editor = grapesjs.init({
+    container : '#gjs',
+    height: '100%',
+
+    panels: {
+      defaults: [{
+          id: 'commands',
+      }],
+    }
+});
+...
+

In this example we set a panel with 'commands' as an id and after the render we'll see nothing more than an empty div added to our panels. The new panel is already styled as the id 'commands' is one of the default but you can use whatever you like and place it wherever you want with CSS. With refresh we might see something like shown in the image below, with the new panel on the left:

[[img/new-panel.png]]

Check Editor API Reference for more details about editor configurations

Now let's put some button inside

...
+  panels: {
+    defaults  : [{
+        id      : 'commands',
+        buttons : [{
+            id          : 'smile',
+            className   : 'fa fa-smile-o',
+            attributes  : { title: 'Smile' }
+        }],
+    }],
+  }
+...
+

On refresh the page might present some changes ('fa fa-smile-o' are from FontAwesome set, so be sure to have placed correctly the font directory)

[[img/new-btn.png]]

Yeah, the button is pretty nice and happy, but useless without any command assigned, if you click on it nothing gonna happen.

Check Panels API Reference for more details about Panels and Buttons

Assigning commands is pretty easy, but before you should define one or use one of defaults (Built-in commands). So in this case we gonna create a new one.

...
+  panels: {
+    defaults  : [{
+        id      : 'commands',
+        buttons : [{
+            id          : 'smile',
+            className   : 'fa fa-smile-o',
+            attributes  : { title: 'Smile' },
+            command     : 'helloWorld',
+        }],
+    }],
+  },
+  commands: {
+    defaults: [{
+        id: 'helloWorld',
+
+        run:  function(editor, senderBtn){
+          alert('Hello world!');
+          // Deactivate button
+          senderBtn.set('active', false);
+        },
+
+        stop:  function(editor, senderBtn){
+        },
+    }]
+  }
+...
+

As you see we added a new command helloWorld and used its id as an identifier inside button.command. In addition to this we've also implemented two required methods, run and stop, to make button execute commands.

[[img/btn-clicked.png]]

Check Commands API Reference

Check the demo for more complete usage of panels, buttons and built-in commands.

Components

Components are elements inside the canvas, which can be drawn by commands or injected directly via configurations. In simple terms components represent the structure of our HTML document. You can init the editor with passing components as an HTML string

...
+  // Disable default local storage in case you've already used GrapesJS
+  storageManager: {type: 'none'},
+
+  components: '<div style="width:300px; min-height:100px; margin: 0 auto"></div>' +
+              '<div style="width:400px; min-height:100px; margin: 0 auto"></div>' +
+              '<div style="width:500px; min-height:100px; margin: 0 auto"></div>',
+...
+

We added 3 simple components with some basic style. If you refresh probably you'll see the same empty page but are actually there, you only need to highlight them. +For this purpose already exists a command, so add it to your panel in this way

...
+  panels: {
+    defaults  : [{
+        id      : 'commands',
+        buttons : [
+          {
+            id: 'smile',
+            ...
+          },
+          {
+            id        : 'vis',
+            className : 'fa fa-eye',
+            command   : 'sw-visibility',
+            context   : 'some-random-context', // For grouping context of buttons in the same panel
+            active    : true,
+          },
+        ],
+    }],
+  },
+...
+

Worth noting the use of context option (try to click 'smile' command without it) and active to enable it after the render. +Now you should be able to see blocks inside canvas.

[[img/blocks3.jpg]]

You could add other commands to enable interactions with blocks. Check Built-in commands to get more information

Check Components API Reference

Style Manager

Any HTML structure requires, at some point, a proper style, so to meet this need the Style Manager was added as a built-in feature in GrapesJS. Style manager is composed by sectors, which group inside different types of CSS properties. So you can add, for instance, a Dimension sector for width and height, and another one as Typography for font-size and color. So it's up to you decide how organize sectors.

To enable this module we rely on a built-in command open-sm, which shows up the Style Manager, which we gonna bind to another button in a separate panel

...
+panels: {
+    defaults  : [
+      {
+        id      : 'commands',
+        ...
+      },{
+        // If you use this id the default CSS will place this panel on top right corner for you
+        id      : 'views',
+        buttons : [{
+            id        : 'open-style-manager',
+            className : 'fa fa-paint-brush',
+            command   : 'open-sm',
+            active    : true,
+        }]
+      }
+    ],
+},
+...
+

After this you'll be able to see something like in the image below

[[img/enabled-sm.jpg]]

As you can see Style Manager is enabled but before using it you have to select an element in the canvas, for this purpose we can add another button with a built-in command select-comp in this way

...
+  panels: {
+    defaults  : [{
+        id      : 'commands',
+        buttons : [
+          {
+            id: 'smile',
+            ...
+          },{
+            id         : 'select',
+            className : 'fa fa-mouse-pointer',
+            command   : 'select-comp',
+          }
+        ],
+    }],
+  },
+...
+

Selecting one of the component will show up the Style Manager with default sectors, properties and an input where you can manage classes. The default class you see (cXX) was generated by extracting style from the component

[[img/default-sm.jpg]]

As we exploring different configurations inside GrapesJS we gonna overwrite all the default sectors to create some custom one

Let's put a few sectors with use of buildProps which helps us building common properties

...
+  styleManager : {
+    sectors: [{
+      name: 'Dimension',
+      buildProps: ['width', 'min-height']
+    },{
+      name: 'Extra',
+      buildProps: ['background-color', 'box-shadow']
+    }]
+  }
+...
+

Now you should be able to style components

[[img/style-comp.jpg]]

You can check the list of usable properties inside buildProps here: Built-in properties +otherwise is possible to build them on your own, let's see how we'd have done the previous configuration without the buildProps helper

...
+styleManager : {
+  sectors: [
+    {
+      name: 'Dimension',
+      properties:[
+        {
+            // Just the name
+            name      : 'Width',
+            // CSS property
+            property  : 'width',
+            // Type of the input, options: integer | radio | select | color | file | composite | stack
+            type      : 'integer',
+            // Units, available only for 'integer' types
+            units     : ['px', '%'],
+            // Default value
+            defaults  : 'auto',
+            // Min value, available only for 'integer' types
+            min       : 0,
+        },{
+            // Here I'm going to be more original
+            name      : 'Minimum height',
+            property  : 'min-height',
+            type      : 'select',
+            defaults  : '100px',
+            // List of options, available only for 'select' and 'radio'  types
+            list    : [{
+                      value   : '100px',
+                      name    : '100',
+                    },{
+                      value   : '200px',
+                      name    : '200',
+                    },{
+                      value   : '300px',
+                      name    : '300',
+                    }],
+        }
+      ]
+    },{
+      name: 'Extra',
+      // Sectors are expanded by default so put this one closed
+      open: false,
+      properties:[
+        {
+          name      : 'Background',
+          property  : 'background-color',
+          type      : 'color',
+          defaults:   'none'
+        },{
+          name    : 'Box shadow',
+          property  : 'box-shadow',
+          type    : 'stack',
+          preview   : true,
+          // List of nested properties, available only for 'stack' and 'composite'  types
+          properties  : [{
+                  name:     'Shadow type',
+                  // Nested properties with stack/composite type don't require proper 'property' name
+                  // as all of them will be merged to parent property, eg. box-shadow: X Y ...;
+                  property:   'shadow-type',
+                  type:     'select',
+                  defaults:   '',
+                  list:   [ { value : '', name : 'Outside', },
+                              { value : 'inset', name : 'Inside', }],
+                },{
+                  name:     'X position',
+                  property:   'shadow-x',
+                  type:     'integer',
+                  units:    ['px','%'],
+                  defaults :  0,
+                },{
+                  name:     'Y position',
+                  property:   'shadow-y',
+                  type:     'integer',
+                  units:    ['px','%'],
+                  defaults :  0,
+                },{
+                  name:     'Blur',
+                  property: 'shadow-blur',
+                  type:     'integer',
+                  units:    ['px'],
+                  defaults :  0,
+                  min:    0,
+                },{
+                  name:     'Spread',
+                  property:   'shadow-spread',
+                  type:     'integer',
+                  units:    ['px'],
+                  defaults :  0,
+                },{
+                  name:     'Color',
+                  property:   'shadow-color',
+                  type:     'color',
+                  defaults:   'black',
+                },],
+        }
+      ]
+    }
+  ]
+}
+...
+

As you can see using buildProps actually will save you a lot of work. You could also mix this techniques to obtain custom properties in less time. For example, let's see how can we setup the same width but with a different value of min:

...
+  styleManager : {
+    sectors: [{
+      name: 'Dimension',
+      buildProps: ['width', 'min-height'],
+      properties:[{
+        property: 'width', // Use 'property' as id
+        min: 30
+      }]
+    },
+    ...
+  }
+...
+

Check Style Manager API Reference

Store/load data

In this last part we're gonna see how to store and load template data inside GrapesJS. You may already noticed that even if you refresh the page after changes on canvas your data are not lost and this because GrapesJS comes with some built-in storage implementation. +The default one is the localStorage which is pretty simple and all the data are stored locally on your computer. Let's see the options available for this storage

...
+var editor = grapesjs.init({
+    container : '#gjs',
+    ...
+    // Default configuration
+    storageManager: {
+      id: 'gjs-',             // Prefix identifier that will be used inside storing and loading
+      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
+      storeComponents: false, // Enable/Disable storing of components in JSON format
+      storeStyles: false,     // Enable/Disable storing of rules/style in JSON format
+      storeHtml: true,        // Enable/Disable storing of components as HTML string
+      storeCss: true,         // Enable/Disable storing of rules/style as CSS string
+    }
+});
+...
+

Worth noting the defaut id parameter which adds a prefix for all keys to store. If you check the localStorage inside your DOM panel you'll see something like { 'gjs-components': '<div>....' ...} in this way it prevents the risk of collisions, quite common with localStorage use in large applications.

Storing data locally it's easy and fast but useless in some common cases. In the next example we'll see how to setup a remote storage, which is not far from the previous one

...
+var editor = grapesjs.init({
+    container : '#gjs',
+    ...
+    storageManager: {
+      type: 'remote',
+      stepsBeforeSave: 10,
+      urlStore: 'http://store/endpoint',
+      urlLoad: 'http://load/endpoint',
+      params: {},   // For custom values on requests
+    }
+});
+...
+

As you can see we've left some default option unchanged, increased changes necessary for autosave triggering and passed remote endpoints.

If you prefer you could also disable autosaving and do it by yourself using some custom command in this way:

...
+  storageManager: {
+    type: 'remote',
+    autosave: false,
+  },
+  ...
+  commands: {
+    defaults: [{
+        id: 'storeData',
+        run:  function(editor, senderBtn){
+          editor.store();
+        },
+    }]
+  }
+...
+

Check Storage Manager API Reference

Last Updated: 7/13/2018, 12:12:35 AM
+ + + diff --git a/docs/api/assets.html b/docs/api/assets.html new file mode 100644 index 000000000..e8da87779 --- /dev/null +++ b/docs/api/assets.html @@ -0,0 +1,85 @@ + + + + + + GrapesJS + + + + + + + + + +

AssetManager

You can customize the initial state of the module from the editor initialization, by passing the following Configuration Object

const editor = grapesjs.init({
+ assetManager: {
+   // options
+ }
+})
+

Once the editor is instantiated you can use its API. Before using these methods you should get the module from the instance

const assetManager = editor.AssetManager;
+

add

Add new asset/s to the collection. URLs are supposed to be unique

Parameters

Examples

// In case of strings, would be interpreted as images
+assetManager.add('http://img.jpg');
+assetManager.add(['http://img.jpg', './path/to/img.png']);
+
+// Using objects you could indicate the type and other meta informations
+assetManager.add({
+	src: 'http://img.jpg',
+	//type: 'image',	//image is default
+	height: 300,
+width: 200,
+});
+assetManager.add([{
+	src: 'http://img.jpg',
+},{
+	src: './path/to/img.png',
+}]);
+

Returns Model

get

Returns the asset by URL

Parameters

Examples

var asset = assetManager.get('http://img.jpg');
+

Returns Object Object representing the asset

getAll

Return the global collection, containing all the assets

Returns Collection

getAllVisible

Return the visible collection, which containes assets actually rendered

Returns Collection

remove

Remove the asset by its URL

Parameters

Examples

assetManager.remove('http://img.jpg');
+

Returns this

store

Store assets data to the selected storage

Parameters

  • noStoreBoolean If true, won't store

Examples

var assets = assetManager.store();
+

Returns Object Data to store

load

Load data from the passed object. +The fetched data will be added to the collection.

Parameters

  • dataObject Object of data to load (optional, default {})

Examples

var assets = assetManager.load({
+	assets: [...]
+})
+

Returns Object Loaded assets

getContainer

Return the Asset Manager Container

Returns HTMLElement

getAssetsEl

Get assets element container

Returns HTMLElement

render

Render assets

Parameters

  • assetsarray Assets to render, without the argument will render +all global assets

Examples

// Render all assets
+assetManager.render();
+
+// Render some of the assets
+const assets = assetManager.getAll();
+assetManager.render(assets.filter(
+ asset => asset.get('category') == 'cats'
+));
+

Returns HTMLElement

addType

Add new type. If you want to get more about type definition we suggest to read the module's page

Parameters

  • idstring Type ID
  • definitionObject Definition of the type. Each definition contains +model (business logic), view (presentation logic) +and isType function which recognize the type of the +passed entity

Examples

assetManager.addType('my-type', {
+ model: {},
+ view: {},
+ isType: (value) => {},
+})
+

getType

Get type

Parameters

Returns Object Type definition

getTypes

Get types

Returns Array

Last Updated: 7/8/2018, 1:46:22 PM
+ + + diff --git a/docs/api/block_manager.html b/docs/api/block_manager.html new file mode 100644 index 000000000..138e9e5f8 --- /dev/null +++ b/docs/api/block_manager.html @@ -0,0 +1,85 @@ + + + + + + GrapesJS + + + + + + + + + +

BlockManager

You can customize the initial state of the module from the editor initialization, by passing the following Configuration Object

const editor = grapesjs.init({
+ blockManager: {
+   // options
+ }
+})
+

Once the editor is instantiated you can use its API. Before using these methods you should get the module from the instance

const blockManager = editor.BlockManager;
+

getConfig

Get configuration object

Returns Object

onLoad

Load default blocks if the collection is empty

add

Add new block to the collection.

Parameters

  • idstring Block id
  • optsObject Options +
    • opts.labelstring Name of the block
    • opts.contentstring HTML content
    • opts.category(string | Object) Group the block inside a catgegory. +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'}
    • opts.attributesObject Block attributes (optional, default {})

Examples

blockManager.add('h1-block', {
+  label: 'Heading',
+  content: '<h1>Put your title here</h1>',
+  category: 'Basic',
+  attributes: {
+    title: 'Insert h1 block'
+  }
+});
+

Returns Block Added block

get

Return the block by id

Parameters

Examples

const block = blockManager.get('h1-block');
+console.log(JSON.stringify(block));
+// {label: 'Heading', content: '<h1>Put your ...', ...}
+

getAll

Return all blocks

Examples

const blocks = blockManager.getAll();
+console.log(JSON.stringify(blocks));
+// [{label: 'Heading', content: '<h1>Put your ...'}, ...]
+

Returns Collection

getAllVisible

Return the visible collection, which containes blocks actually rendered

Returns Collection

remove

Remove a block by id

Parameters

Returns Block Removed block

getCategories

Get all available categories. +It's possible to add categories only within blocks via 'add()' method

Returns (Array | Collection)

getContainer

Return the Blocks container element

Returns HTMLElement

render

Render blocks

Parameters

  • blocksArray Blocks to render, without the argument will render all global blocks
  • optsObject Options (optional, default {}) +
    • opts.externalBoolean? Render blocks in a new container (HTMLElement will be returned)
    • opts.ignoreCategoriesBoolean? Render blocks without categories

Examples

// Render all blocks (inside the global collection)
+blockManager.render();
+
+// Render new set of blocks
+const blocks = blockManager.getAll();
+const filtered = blocks.filter(block => block.get('category') == 'sections')
+
+blockManager.render(filtered);
+// Or a new set from an array
+blockManager.render([
+ {label: 'Label text', content: '<div>Content</div>'}
+]);
+
+// Back to blocks from the global collection
+blockManager.render();
+
+// You can also render your blocks outside of the main block container
+const newBlocksEl = blockManager.render(filtered, { external: true });
+document.getElementById('some-id').appendChild(newBlocksEl);
+

Returns HTMLElement Rendered element

Last Updated: 1/28/2019, 7:45:05 PM
+ + + diff --git a/docs/api/canvas.html b/docs/api/canvas.html new file mode 100644 index 000000000..11bbd6168 --- /dev/null +++ b/docs/api/canvas.html @@ -0,0 +1,57 @@ + + + + + + GrapesJS + + + + + + + + + +

Canvas

You can customize the initial state of the module from the editor initialization, by passing the following Configuration Object

const editor = grapesjs.init({
+ canvas: {
+   // options
+ }
+})
+

Once the editor is instantiated you can use its API. Before using these methods you should get the module from the instance

const canvas = editor.Canvas;
+

getConfig

Get the configuration object

Returns Object

getElement

Get the canvas element

Returns HTMLElement

getFrameEl

Get the iframe element of the canvas

Returns HTMLIFrameElement

getWindow

Get the window instance of the iframe element

Returns Window

getDocument

Get the document of the iframe element

Returns HTMLDocument

getBody

Get the body of the iframe element

Returns HTMLBodyElement

getWrapperEl

Get the wrapper element containing all the components

Returns HTMLElement

setCustomBadgeLabel

Set custom badge naming strategy

Parameters

Examples

canvas.setCustomBadgeLabel(function(component){
+ return component.getName();
+});
+

getRect

Get canvas rectangular data

Returns Object

hasFocus

Check if the canvas is focused

Returns Boolean

scrollTo

Scroll canvas to the element if it's not visible. The scrolling is +executed via scrollIntoView API and options of this method are +passed to it. For instance, you can scroll smoothly by using +{ behavior: 'smooth' }.

Parameters

  • el(HTMLElement | Component)
  • optsObject Options, same as options for scrollIntoView (optional, default {}) +
    • opts.forceBoolean Force the scroll, even if the element is already visible (optional, default false)

Examples

const selected = editor.getSelected();
+// Scroll smoothly (this behavior can be polyfilled)
+canvas.scrollTo(selected, { behavior: 'smooth' });
+// Force the scroll, even if the element is alredy visible
+canvas.scrollTo(selected, { force: true });
+

setZoom

Set zoom value

Parameters

  • valueNumber The zoom value, from 0 to 100

Returns this

getZoom

Get zoom value

Returns Number

Last Updated: 3/11/2019, 6:17:19 PM
+ + + diff --git a/docs/api/commands.html b/docs/api/commands.html new file mode 100644 index 000000000..de49fd9d8 --- /dev/null +++ b/docs/api/commands.html @@ -0,0 +1,76 @@ + + + + + + GrapesJS + + + + + + + + + +

Commands

You can customize the initial state of the module from the editor initialization, by passing the following Configuration Object

const editor = grapesjs.init({
+ commands: {
+   // options
+ }
+})
+

Once the editor is instantiated you can use its API. Before using these methods you should get the module from the instance

const commands = editor.Commands;
+

add

Add new command to the collection

Parameters

  • idstring Command's ID
  • command(Object | Function) Object representing your command, +By passing just a function it's intended as a stateless command +(just like passing an object with only run method).

Examples

commands.add('myCommand', {
+	run(editor, sender) {
+		alert('Hello world!');
+	},
+	stop(editor, sender) {
+	},
+});
+// As a function
+commands.add('myCommand2', editor => { ... });
+

Returns this

get

Get command by ID

Parameters

Examples

var myCommand = commands.get('myCommand');
+myCommand.run();
+

Returns Object Object representing the command

extend

Extend the command. The command to extend should be defined as an object

Parameters

  • idstring Command's ID
  • cmd (optional, default {})
  • ObjectObject with the new command functions

Examples

commands.extend('old-command', {
+ someInnerFunction() {
+ // ...
+ }
+});
+

Returns this

has

Check if command exists

Parameters

Returns Boolean

getAll

Get an object containing all the commands

Returns Object

run

Execute the command

Parameters

  • idString Command ID
  • optionsObject Options (optional, default {})

Examples

commands.run('myCommand', { someOption: 1 });
+

Returns any The return is defined by the command

stop

Stop the command

Parameters

  • idString Command ID
  • optionsObject Options (optional, default {})

Examples

commands.stop('myCommand', { someOption: 1 });
+

Returns any The return is defined by the command

isActive

Check if the command is active. You activate commands with run +and disable them with stop. If the command was created without stop +method it can't be registered as active

Parameters

Examples

const cId = 'some-command';
+commands.run(cId);
+commands.isActive(cId);
+// -> true
+commands.stop(cId);
+commands.isActive(cId);
+// -> false
+

Returns Boolean

getActive

Get all active commands

Examples

console.log(commands.getActive());
+// -> { someCommand: itsLastReturn, anotherOne: ... };
+

Returns Object

Last Updated: 8/26/2018, 2:24:48 PM
+ + + diff --git a/docs/api/component.html b/docs/api/component.html new file mode 100644 index 000000000..1fc83896f --- /dev/null +++ b/docs/api/component.html @@ -0,0 +1,152 @@ + + + + + + GrapesJS + + + + + + + + + +

Component

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). +An example on how to update properties:

component.set({
+ tagName: 'span',
+ attributes: { ... },
+ removable: false,
+});
+component.get('tagName');
+// -> 'span'
+

Properties

  • typeString? Component type, eg. text, image, video, etc.
  • tagNameString? HTML tag of the component, eg. span. Default: div
  • attributesObject? Key-value object of the component's attributes, eg. { title: 'Hello' } Default: {}
  • nameString? Name of the component. Will be used, for example, in Layers and badges
  • removableBoolean? When true the component is removable from the canvas, default: true
  • draggable(Boolean | String)? Indicates if it's possible to drag the component inside others. +You can also specify a query string to indentify elements, +eg. '.some-class[title=Hello], [data-gjs-type=column]' means you can drag the component only inside elements +containing some-class class and Hello title, and column components. Default: true
  • droppable(Boolean | String)? Indicates if it's possible to drop other components inside. You can use +a query string as with draggable. Default: true
  • badgableBoolean? Set to false if you don't want to see the badge (with the name) over the component. Default: true
  • stylable(Boolean | Array<String>)? True if it's possible to style the component. +You can also indicate an array of CSS properties which is possible to style, eg. ['color', 'width'], all other properties +will be hidden from the style manager. Default: true
  • stylable-requireArray<String>? Indicate an array of style properties to show up which has been marked as toRequire. Default: []
  • unstylableArray<String>? Indicate an array of style properties which should be hidden from the style manager. Default: []
  • style-signatureArray<String>? This option comes handy when you need to remove or export strictly component-specific rules. Be default, if this option is not empty, the editor will remove rules when there are no components, of that type, in the canvas. Eg. '['.navbar', '[navbar-']'. Default: ''
  • highlightableBoolean? It can be highlighted with 'dotted' borders if true. Default: true
  • copyableBoolean? True if it's possible to clone the component. Default: true
  • resizableBoolean? Indicates if it's possible to resize the component. It's also possible to pass an object as options for the Resizer. Default: false
  • editableBoolean? Allow to edit the content of the component (used on Text components). Default: false
  • layerableBoolean? Set to false if you need to hide the component inside Layers. Default: true
  • selectableBoolean? Allow component to be selected when clicked. Default: true
  • hoverableBoolean? Shows a highlight outline when hovering on the element if true. Default: true
  • voidBoolean? This property is used by the HTML exporter as void elements don't have closing tags, eg. <br/>, <hr/>, etc. Default: false
  • contentString? Content of the component (not escaped) which will be appended before children rendering. Default: ''
  • iconString? Component's icon, this string will be inserted before the name (in Layers and badge), eg. it can be an HTML string ''. Default: ''
  • script(String | Function)? Component's javascript. More about it here. Default: ''
  • traitsArray<(Object | String)>? Component's traits. More about it here. Default: ['id', 'title']
  • propagateArray<String>? Indicates an array of properties which will be inhereted by all NEW appended children. +For example if you create a component likes this: { removable: false, draggable: false, propagate: ['removable', 'draggable'] } +and append some new component inside, the new added component will get the exact same properties indicated in the propagate array (and the propagate property itself). Default: []
  • toolbarArray<Object>? Set an array of items to show up inside the toolbar when the component is selected (move, clone, delete). +Eg. toolbar: [ { attributes: {class: 'fa fa-arrows'}, command: 'tlb-move' }, ... ]. +By default, when toolbar property is falsy the editor will add automatically commands like move, delete, etc. based on its properties.
  • componentsCollection<Component>? Children components. Default: null

init

Hook method, called once the model is created

updated

Hook method, called when the model has been updated (eg. updated some model's property)

Parameters

  • propertyString Property name, if triggered after some property update
  • valueany Property value, if triggered after some property update
  • previousany Property previous value, if triggered after some property update

removed

Hook method, called once the model has been removed

is

Check component's type

Parameters

Examples

component.is('image')
+// -> false
+

Returns Boolean

index

Get the index of the component in the parent collection.

Returns Number

find

Find inner components by query string. +ATTENTION: this method works only with already rendered component

Parameters

Examples

component.find('div > .class');
+// -> [Component, Component, ...]
+

Returns Array Array of components

findType

Find all inner components by component id. +The advantage of this method over find is that you can use it +also before rendering the component

Parameters

Examples

const allImages = component.findType('image');
+console.log(allImages[0]) // prints the first found component
+

Returns Array<Component>

closest

Find the closest parent component by query string. +ATTENTION: this method works only with already rendered component

Parameters

Examples

component.closest('div.some-class');
+// -> Component
+

Returns Component

replaceWith

Replace a component with another one

Parameters

Examples

component.replaceWith('<div>Some new content</div>');
+// -> Component
+

Returns (Component | Array<Component>) New added component/s

setAttributes

Update attributes of the component

Parameters

  • attrsObject Key value attributes
  • opts (optional, default {})

Examples

component.setAttributes({ id: 'test', 'data-key': 'value' });
+

Returns this

addAttributes

Add attributes to the component

Parameters

  • attrsObject Key value attributes

Examples

component.addAttributes({ 'data-key': 'value' });
+

Returns this

getStyle

Get the style of the component

Returns Object

setStyle

Set the style on the component

Parameters

  • propObject Key value style object (optional, default {})
  • opts (optional, default {})

Examples

component.setStyle({ color: 'red' });
+

Returns Object

getAttributes

Return all component's attributes

Returns Object

addClass

Add classes

Parameters

Examples

model.addClass('class1');
+model.addClass('class1 class2');
+model.addClass(['class1', 'class2']);
+// -> [SelectorObject, ...]
+

Returns Array Array of added selectors

setClass

Set classes (resets current collection)

Parameters

Examples

model.setClass('class1');
+model.setClass('class1 class2');
+model.setClass(['class1', 'class2']);
+// -> [SelectorObject, ...]
+

Returns Array Array of added selectors

removeClass

Remove classes

Parameters

Examples

model.removeClass('class1');
+model.removeClass('class1 class2');
+model.removeClass(['class1', 'class2']);
+// -> [SelectorObject, ...]
+

Returns Array Array of removed selectors

getClasses

Returns component's classes as an array of strings

Returns Array

append

Add new component children

Parameters

  • components(Component | String) Component to add
  • optsObject Options, same as in model.add()(from backbone) (optional, default {})

Examples

someComponent.get('components').length // -> 0
+const videoComponent = someComponent.append('<video></video><div></div>')[0];
+// This will add 2 components (`video` and `div`) to your `someComponent`
+someComponent.get('components').length // -> 2
+// You can pass components directly
+otherComponent.append(otherComponent2);
+otherComponent.append([otherComponent3, otherComponent4]);
+

Returns Array Array of appended components

components

Set new collection if components are provided, otherwise the +current collection is returned

Parameters

Examples

// Set new collection
+component.components('<span></span><div></div>');
+// Get current collection
+const collection = component.components();
+console.log(collection.length);
+// -> 2
+

Returns (Collection | Array<Component>)

parent

Get the parent component, if exists

Examples

component.parent();
+// -> Component
+

Returns Component

getTrait

Get the trait by id/name

Parameters

  • idString The id or name of the trait

Examples

const traitTitle = component.getTrait('title');
+traitTitle && traitTitle.set('label', 'New label');
+

Returns Trait Trait model

updateTrait

Update a trait

Parameters

  • idString The id or name of the trait
  • propsObject Object with the props to update

Examples

component.updateTrait('title', {
+ type: 'select',
+ options: [ 'Option 1', 'Option 2' ],
+});
+

Returns this

getTraitIndex

Get the trait position index by id/name. Useful in case you want to +replace some trait, at runtime, with something else.

Parameters

  • idString The id or name of the trait

Examples

const traitTitle = component.getTraitIndex('title');
+console.log(traitTitle); // 1
+

Returns Number Index position of the current trait

removeTrait

Remove trait/s by id/s.

Parameters

Examples

component.removeTrait('title');
+component.removeTrait(['title', 'id']);
+

Returns Array Array of removed traits

addTrait

Add trait/s by id/s.

Parameters

Examples

component.addTrat('title', { at: 1 }); // Add title trait (`at` option is the position index)
+component.addTrat({
+ type: 'checkbox',
+ name: 'disabled',
+});
+component.addTrat(['title', {...}, ...]);
+

Returns Array Array of added traits

getName

Get the name of the component

Returns String

getIcon

Get the icon string

Returns String

toHTML

Return HTML string of the component

Parameters

  • optsObject Options (optional, default {}) +
    • opts.attributes(Object | Function) You can pass an object of custom attributes to replace +with the current one or you can even pass a function to generate attributes dynamically (optional, default null)

Examples

// Simple HTML return
+component.set({ tagName: 'span' });
+component.setAttributes({ title: 'Hello' });
+component.toHTML();
+// -> <span title="Hello"></span>
+
+// Custom attributes
+component.toHTML({ attributes: { 'data-test': 'Hello' } });
+// -> <span data-test="Hello"></span>
+
+// Custom dynamic attributes
+component.toHTML({
+ attributes(component, attributes) {
+   if (component.get('tagName') == 'span') {
+     attributes.title = 'Custom attribute';
+   }
+   return attributes;
+ },
+});
+// -> <span title="Custom attribute"></span>
+

Returns String HTML string

getId

Return the component id

Returns String

setId

Set new id on the component

Parameters

Returns this

getEl

Get the DOM element of the component. +This works only if the component is already rendered

Returns HTMLElement

getView

Get the View of the component. +This works only if the component is already rendered

Returns ComponentView

onAll

Execute callback function on itself and all inner components

Parameters

  • clbFunction Callback function, the model is passed as an argument

Examples

component.onAll(component => {
+ // do something with component
+})
+

Returns this

remove

Remove the component

Returns this

getList

The list of components is taken from the Components module. +Initially, the list, was set statically on the Component object but it was +not ok, as it was shared between multiple editor instances

Parameters

  • model

checkId

This method checks, for each parsed component and style object +(are not Components/CSSRules yet), for duplicated id and fixes them +This method is used in Components.js just after the parsing

Parameters

  • components
  • styles (optional, default [])
  • list (optional, default {})
Last Updated: 3/11/2019, 6:17:19 PM
+ + + diff --git a/docs/api/components.html b/docs/api/components.html new file mode 100644 index 000000000..67dc045ff --- /dev/null +++ b/docs/api/components.html @@ -0,0 +1,91 @@ + + + + + + GrapesJS + + + + + + + + + +

DomComponents

With this module is possible to manage components inside the canvas. You can customize the initial state of the module from the editor initialization, by passing the following Configuration Object

const editor = grapesjs.init({
+ domComponents: {
+   // options
+ }
+})
+

Once the editor is instantiated you can use its API. Before using these methods you should get the module from the instance

const domComponents = editor.DomComponents;
+

load

Load components from the passed object, if the object is empty will try to fetch them +autonomously from the selected storage +The fetched data will be added to the collection

Parameters

  • dataObject Object of data to load (optional, default '')

Returns Object Loaded data

store

Store components on the selected storage

Parameters

  • noStoreBoolean If true, won't store

Returns Object Data to store

getWrapper

Returns root component inside the canvas. Something like <body> inside HTML page +The wrapper doesn't differ from the original Component Model

Examples

// Change background of the wrapper and set some attribute
+var wrapper = domComponents.getWrapper();
+wrapper.set('style', {'background-color': 'red'});
+wrapper.set('attributes', {'title': 'Hello!'});
+

Returns Component Root Component

getComponents

Returns wrapper's children collection. Once you have the collection you can +add other Components(Models) inside. Each component can have several nested +components inside and you can nest them as more as you wish.

Examples

// Let's add some component
+var wrapperChildren = domComponents.getComponents();
+var comp1 = wrapperChildren.add({
+  style: { 'background-color': 'red'}
+});
+var comp2 = wrapperChildren.add({
+  tagName: 'span',
+  attributes: { title: 'Hello!'}
+});
+// Now let's add an other one inside first component
+// First we have to get the collection inside. Each
+// component has 'components' property
+var comp1Children = comp1.get('components');
+// Procede as before. You could also add multiple objects
+comp1Children.add([
+  { style: { 'background-color': 'blue'}},
+  { style: { height: '100px', width: '100px'}}
+]);
+// Remove comp2
+wrapperChildren.remove(comp2);
+

Returns Components Collection of components

addComponent

Add new components to the wrapper's children. It's the same +as 'domComponents.getComponents().add(...)'

Parameters

  • component(Object | Component | Array<Object>) Component/s to add +
    • component.tagNamestring Tag name (optional, default 'div')
    • component.typestring Type of the component. Available: ''(default), 'text', 'image' (optional, default '')
    • component.removableboolean If component is removable (optional, default true)
    • component.draggableboolean If is possible to move the component around the structure (optional, default true)
    • component.droppableboolean If is possible to drop inside other components (optional, default true)
    • component.badgableboolean If the badge is visible when the component is selected (optional, default true)
    • component.stylableboolean If is possible to style component (optional, default true)
    • component.copyableboolean If is possible to copy&paste the component (optional, default true)
    • component.contentstring String inside component (optional, default '')
    • component.styleObject Style object (optional, default {})
    • component.attributesObject Attribute object (optional, default {})

Examples

// Example of a new component with some extra property
+var comp1 = domComponents.addComponent({
+  tagName: 'div',
+  removable: true, // Can't remove it
+  draggable: true, // Can't move it
+  copyable: true, // Disable copy/past
+  content: 'Content text', // Text inside component
+  style: { color: 'red'},
+  attributes: { title: 'here' }
+});
+

Returns (Component | Array<Component>) Component/s added

render

Render and returns wrapper element with all components inside. +Once the wrapper is rendered, and it's what happens when you init the editor, +the all new components will be added automatically and property changes are all +updated immediately

Returns HTMLElement

clear

Remove all components

Returns this

addType

Add new component type. +Read more about this in Define New Component

Parameters

Returns this

getType

Get component type. +Read more about this in Define New Component

Parameters

Returns Object Component type defintion, eg. { model: ..., view: ... }

getTypes

Return the array of all types

Returns Array

Last Updated: 1/5/2019, 1:17:11 AM
+ + + diff --git a/docs/api/css_composer.html b/docs/api/css_composer.html new file mode 100644 index 000000000..6d8d15842 --- /dev/null +++ b/docs/api/css_composer.html @@ -0,0 +1,84 @@ + + + + + + GrapesJS + + + + + + + + + +

CssComposer

This module contains and manage CSS rules for the template inside the canvas. +You can customize the initial state of the module from the editor initialization, by passing the following Configuration Object

const editor = grapesjs.init({
+ cssComposer: {
+   // options
+ }
+})
+

Once the editor is instantiated you can use its API. Before using these methods you should get the module from the instance

const cssComposer = editor.CssComposer;
+

load

Load data from the passed object, if the object is empty will try to fetch them +autonomously from the storage manager. +The fetched data will be added to the collection

Parameters

  • dataObject Object of data to load

Returns Object Loaded rules

store

Store data to the selected storage

Parameters

  • noStoreBoolean If true, won't store

Returns Object Data to store

add

Add new rule to the collection, if not yet exists with the same selectors

Parameters

  • selectorsArray<Selector> Array of selectors
  • stateString Css rule state
  • widthString For which device this style is oriented
  • optsObject Other options for the rule (optional, default {})

Examples

var sm = editor.SelectorManager;
+var sel1 = sm.add('myClass1');
+var sel2 = sm.add('myClass2');
+var rule = cssComposer.add([sel1, sel2], 'hover');
+rule.set('style', {
+  width: '100px',
+  color: '#fff',
+});
+

Returns Model

get

Get the rule

Parameters

  • selectorsArray<Selector> Array of selectors
  • stateString Css rule state
  • widthString For which device this style is oriented
  • rulePropsObject Other rule props

Examples

var sm = editor.SelectorManager;
+var sel1 = sm.add('myClass1');
+var sel2 = sm.add('myClass2');
+var rule = cssComposer.get([sel1, sel2], 'hover');
+// Update the style
+rule.set('style', {
+  width: '300px',
+  color: '#000',
+});
+

Returns (Model | null)

getAll

Get the collection of rules

Returns Collection

clear

Remove all rules

Returns this

setRule

Add/update the CSS rule with a generic selector

Parameters

  • selectorsstring Selector, eg. '.myclass'
  • styleObject Style properties and values
  • optsObject Additional properties (optional, default {}) +
    • opts.atRuleTypeString At-rule type, eg. 'media' (optional, default '')
    • opts.atRuleParamsString At-rule parameters, eg. '(min-width: 500px)' (optional, default '')

Examples

// Simple class-based rule
+const rule = cc.setRule('.class1.class2', { color: 'red' });
+console.log(rule.toCSS()) // output: .class1.class2 { color: red }
+// With state and other mixed selector
+const rule = cc.setRule('.class1.class2:hover, div#myid', { color: 'red' });
+// output: .class1.class2:hover, div#myid { color: red }
+// With media
+const rule = cc.setRule('.class1:hover', { color: 'red' }, {
+ atRuleType: 'media',
+ atRuleParams: '(min-width: 500px)',
+});
+// output: @media (min-width: 500px) { .class1:hover { color: red } }
+

Returns CssRule The new/updated rule

getRule

Get the CSS rule by a generic selector

Parameters

  • selectorsstring Selector, eg. '.myclass:hover'
  • opts (optional, default {})

Examples

const rule = cc.getRule('.myclass1:hover');
+const rule2 = cc.getRule('.myclass1:hover, div#myid');
+const rule3 = cc.getRule('.myclass1', {
+ atRuleType: 'media',
+ atRuleParams: '(min-width: 500px)',
+});
+

Returns CssRule

Last Updated: 1/28/2019, 7:45:05 PM
+ + + diff --git a/docs/api/device_manager.html b/docs/api/device_manager.html new file mode 100644 index 000000000..10e74b49c --- /dev/null +++ b/docs/api/device_manager.html @@ -0,0 +1,56 @@ + + + + + + GrapesJS + + + + + + + + + +

DeviceManager

You can customize the initial state of the module from the editor initialization, by passing the following Configuration Object

const editor = grapesjs.init({
+ deviceManager: {
+   // options
+ }
+})
+

Once the editor is instantiated you can use its API. Before using these methods you should get the module from the instance

const deviceManager = editor.DeviceManager;
+

add

Add new device to the collection. URLs are supposed to be unique

Parameters

Examples

deviceManager.add('Tablet', '900px');
+deviceManager.add('Tablet2', '900px', {
+ height: '300px',
+ widthMedia: '810px', // the width that will be used for the CSS media
+});
+

Returns Device Added device

get

Return device by name

Parameters

Examples

var device = deviceManager.get('Tablet');
+console.log(JSON.stringify(device));
+// {name: 'Tablet', width: '900px'}
+

getAll

Return all devices

Examples

var devices = deviceManager.getAll();
+console.log(JSON.stringify(devices));
+// [{name: 'Desktop', width: ''}, ...]
+

Returns Collection

Last Updated: 7/8/2018, 11:31:47 PM
+ + + diff --git a/docs/api/editor.html b/docs/api/editor.html new file mode 100644 index 000000000..0ed609ee7 --- /dev/null +++ b/docs/api/editor.html @@ -0,0 +1,136 @@ + + + + + + GrapesJS + + + + + + + + + +

Editor

Editor contains the top level API which you'll probably use to customize the editor or extend it with plugins. +You get the Editor instance on init method and you can pass options via its Configuration Object

const editor = grapesjs.init({
+   // options
+});
+

Available Events

You can make use of available events in this way

editor.on('EVENT-NAME', (some, argument) => {
+   // do something
+})
+

Components

  • component:create - Component is created (only the model, is not yet mounted in the canvas), called after the init() method
  • component:mount - Component is mounted to an element and rendered in canvas
  • component:add - Triggered when a new component is added to the editor, the model is passed as an argument to the callback
  • component:remove - Triggered when a component is removed, the model is passed as an argument to the callback
  • component:clone - Triggered when a component is cloned, the new model is passed as an argument to the callback
  • component:update - Triggered when a component is updated (moved, styled, etc.), the model is passed as an argument to the callback
  • component:update:{propertyName} - Listen any property change, the model is passed as an argument to the callback
  • component:styleUpdate - Triggered when the style of the component is updated, the model is passed as an argument to the callback
  • component:styleUpdate:{propertyName} - Listen for a specific style property change, the model is passed as an argument to the callback
  • component:selected - New component selected, the selected model is passed as an argument to the callback
  • component:deselected - Component deselected, the deselected model is passed as an argument to the callback
  • component:toggled - Component selection changed, toggled model is passed as an argument to the callback
  • component:type:add - New component type added, the new type is passed as an argument to the callback
  • component:type:update - Component type updated, the updated type is passed as an argument to the callback

Blocks

  • block:add - New block added
  • block:remove - Block removed
  • block:drag:start - Started dragging block, model of the block is passed as an argument
  • block:drag - Dragging block, the block's model and the drag event are passed as arguments
  • block:drag:stop - Dragging of the block is stopped. As agruments for the callback you get, the dropped component model (if dropped successfully) and the model of the block

Assets

  • 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

Keymaps

  • keymap:add - New keymap added. The new keyamp object is passed as an argument
  • keymap:remove - Keymap removed. The removed keyamp object is passed as an argument
  • keymap:emit - Some keymap emitted, in arguments you get keymapId, shortcutUsed, Event
  • keymap:emit:{keymapId} - keymapId emitted, in arguments you get keymapId, shortcutUsed, Event

Style Manager

  • styleManager:update:target - The target (Component or CSSRule) is changed
  • styleManager:change - Triggered on style property change from new selected component, the view of the property is passed as an argument to the callback
  • styleManager:change:{propertyName} - As above but for a specific style property

Storages

  • storage:start - Before the storage request is started
  • storage:start:store - Before the store request. The object to store is passed as an argumnet (which you can edit)
  • storage:start:load - Before the load request. Items to load are passed as an argumnet (which you can edit)
  • storage:load - Triggered when something was loaded from the storage, loaded object passed as an argumnet
  • storage:store - Triggered when something is stored to the storage, stored object passed as an argumnet
  • storage:end - After the storage request is ended
  • storage:end:store - After the store request
  • storage:end:load - After the load request
  • storage:error - On any error on storage request, passes the error as an argument
  • storage:error:store - Error on store request, passes the error as an argument
  • storage:error:load - Error on load request, passes the error as an argument

Canvas

  • canvas:dragenter - When something is dragged inside the canvas, DataTransfer instance passed as an argument
  • canvas:dragover - When something is dragging on canvas, DataTransfer instance passed as an argument
  • canvas:drop - Something is dropped in canvas, DataTransfer instance and the dropped model are passed as arguments
  • canvas:dragend - When a drag operation is ended, DataTransfer instance passed as an argument
  • canvas:dragdata - On any dataTransfer parse, DataTransfer instance and the result are passed as arguments. +By changing result.content you're able to customize what is dropped

Selectors

  • selector:add - Triggers when a new selector/class is created

RTE

  • rte:enable - RTE enabled. The view, on which RTE is enabled, is passed as an argument
  • rte:disable - RTE disabled. The view, on which RTE is disabled, is passed as an argument
  • modal:open - Modal is opened
  • modal:close - Modal is closed

Commands

  • run:{commandName} - Triggered when some command is called to run (eg. editor.runCommand('preview'))
  • stop:{commandName} - Triggered when some command is called to stop (eg. editor.stopCommand('preview'))
  • run:{commandName}:before - Triggered before the command is called
  • stop:{commandName}:before - Triggered before the command is called to stop
  • 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

General

  • canvasScroll - Canvas is scrolled
  • update - The structure of the template is updated (its HTML/CSS)
  • undo - Undo executed
  • redo - Redo executed
  • load - Editor is loaded

getConfig

Returns configuration object

Parameters

Returns any Returns the configuration object or +the value of the specified property

getHtml

Returns HTML built inside canvas

Parameters

  • opts

Returns string HTML string

getCss

Returns CSS built inside canvas

Parameters

  • optsObject Options (optional, default {})

Returns string CSS string

getJs

Returns JS of all components

Returns string JS string

getComponents

Return the complete tree of components. Use getWrapper to include also the wrapper

Returns Components

getWrapper

Return the wrapper and its all components

Returns Component

setComponents

Set components inside editor's canvas. This method overrides actual components

Parameters

Examples

editor.setComponents('<div class="cls">New component</div>');
+// or
+editor.setComponents({
+ type: 'text',
+  classes:['cls'],
+  content: 'New component'
+});
+

Returns this

addComponents

Add components

Parameters

  • components(Array<Object> | Object | string) HTML string or components model
  • optsObject Options +
    • opts.avoidUpdateStyleBoolean 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)

Examples

editor.addComponents('<div class="cls">New component</div>');
+// or
+editor.addComponents({
+ type: 'text',
+  classes:['cls'],
+  content: 'New component'
+});
+

Returns (Model | Array<Model>)

getStyle

Returns style in JSON format object

Returns Object

setStyle

Set style inside editor's canvas. This method overrides actual style

Parameters

Examples

editor.setStyle('.cls{color: red}');
+//or
+editor.setStyle({
+  selectors: ['cls']
+  style: { color: 'red' }
+});
+

Returns this

getSelected

Returns the last selected component, if there is one

Returns Model

getSelectedAll

Returns an array of all selected components

Returns Array

getSelectedToStyle

Get a stylable entity from the selected component. +If you select a component without classes the entity is the Component +itself and all changes will go inside its 'style' attribute. Otherwise, +if the selected component has one or more classes, the function will +return the corresponding CSS Rule

Returns Model

select

Select a component

Parameters

  • el(Component | HTMLElement) Component to select
  • optsObject? Options +
    • opts.scrollBoolean? Scroll canvas to the selected element

Examples

// Select dropped block
+editor.on('block:drag:stop', function(model) {
+ editor.select(model);
+});
+

Returns this

selectAdd

Add component to selection

Parameters

Examples

editor.selectAdd(model);
+

Returns this

selectRemove

Remove component from selection

Parameters

Examples

editor.selectRemove(model);
+

Returns this

selectToggle

Toggle component selection

Parameters

Examples

editor.selectToggle(model);
+

Returns this

setDevice

Set device to the editor. If the device exists it will +change the canvas to the proper width

Parameters

Examples

editor.setDevice('Tablet');
+

Returns this

getDevice

Return the actual active device

Examples

var device = editor.getDevice();
+console.log(device);
+// 'Tablet'
+

Returns string Device name

runCommand

Execute command

Parameters

  • idstring Command ID
  • optionsObject Custom options (optional, default {})

Examples

editor.runCommand('myCommand', {someValue: 1});
+

Returns any The return is defined by the command

stopCommand

Stop the command if stop method was provided

Parameters

  • idstring Command ID
  • optionsObject Custom options (optional, default {})

Examples

editor.stopCommand('myCommand', {someValue: 1});
+

Returns any The return is defined by the command

store

Store data to the current storage

Parameters

Returns Object Stored data

load

Load data from the current storage

Parameters

Returns Object Stored data

getContainer

Returns container element. The one which was indicated as 'container' +on init method

Returns HTMLElement

getDirtyCount

Return the count of changes made to the content and not yet stored. +This count resets at any store()

Returns number

setCustomRte

Replace the built-in Rich Text Editor with a custom one.

Parameters

  • objObject Custom RTE Interface

Examples

editor.setCustomRte({
+  // Function for enabling custom RTE
+  // el is the HTMLElement of the double clicked Text Component
+  // rte is the same instance you have returned the first time you call
+  // enable(). This is useful if need to check if the RTE is already enabled so
+  // ion this case you'll need to return the RTE and the end of the function
+  enable: function(el, rte) {
+    rte = new MyCustomRte(el, {}); // this depends on the Custom RTE API
+    ...
+    return rte; // return the RTE instance
+  },
+
+  // Disable the editor, called for example when you unfocus the Text Component
+ disable: function(el, rte) {
+    rte.blur(); // this depends on the Custom RTE API
+ }
+
+// Called when the Text Component is focused again. If you returned the RTE instance
+// from the enable function, the enable won't be called again instead will call focus,
+// in this case to avoid double binding of the editor
+ focus: function (el, rte) {
+  rte.focus(); // this depends on the Custom RTE API
+ }
+});
+

setCustomParserCss

Replace the default CSS parser with a custom one. +The parser function receives a CSS string as a parameter and expects +an array of CSSRule objects as a result. If you need to remove the +custom parser, pass null as the argument

Parameters

Examples

editor.setCustomParserCss(css => {
+ const result = [];
+ // ... parse the CSS string
+ result.push({
+   selectors: '.someclass, div .otherclass',
+   style: { color: 'red' }
+ })
+ // ...
+ return result;
+});
+

Returns this

log

Trigger event log message

Parameters

  • msgany Message to log
  • optsObject Custom options (optional, default {}) +
    • opts.nsString Namespace of the log (eg. to use in plugins) (optional, default '')
    • opts.levelString Level of the log, debug, info, warning, error (optional, default 'debug')

Examples

editor.log('Something done!', { ns: 'from-plugin-x', level: 'info' });
+// This will trigger following events
+// `log`, `log:info`, `log-from-plugin-x`, `log-from-plugin-x:info`
+// Callbacks of those events will always receive the message and
+// options, as arguments, eg:
+// editor.on('log:info', (msg, opts) => console.info(msg, opts))
+

Returns this

on

Attach event

Parameters

Returns this

once

Attach event and detach it after the first run

Parameters

Returns this

off

Detach event

Parameters

Returns this

trigger

Trigger event

Parameters

Returns this

destroy

Destroy the editor

render

Render editor

Returns HTMLElement

Last Updated: 3/11/2019, 6:17:19 PM
+ + + diff --git a/docs/api/index.html b/docs/api/index.html new file mode 100644 index 000000000..5aa6fb845 --- /dev/null +++ b/docs/api/index.html @@ -0,0 +1,36 @@ + + + + + + API Reference | GrapesJS + + + + + + + + + +

API Reference

Here you can find the documentation about GrapesJS' APIs. Mainly, you would use them for your editor to extend the basic functionality of the framework or you could also create a plugin and make those extensions reusable and available to others.

Last Updated: 7/9/2018, 1:03:59 AM
+ + + diff --git a/docs/api/keymaps.html b/docs/api/keymaps.html new file mode 100644 index 000000000..234db11d2 --- /dev/null +++ b/docs/api/keymaps.html @@ -0,0 +1,70 @@ + + + + + + GrapesJS + + + + + + + + + +

Keymaps

You can customize the initial state of the module from the editor initialization

const editor = grapesjs.init({
+ keymaps: {
+    // Object of keymaps
+   defaults: {
+     'your-namespace:keymap-name' {
+       keys: '⌘+z, ctrl+z',
+       handler: 'some-command-id'
+     },
+     ...
+   }
+ }
+})
+

Once the editor is instantiated you can use its API. Before using these methods you should get the module from the instance

const keymaps = editor.Keymaps;
+

getConfig

Get module configurations

Returns Object Configuration object

add

Add new keymap

Parameters

  • idstring Keymap id
  • keysstring Keymap keys, eg. ctrl+a, ⌘+z, ctrl+z
  • handler(Function | string) Keymap handler, might be a function
  • optsObject Options (optional, default {})

Examples

// 'ns' is just a custom namespace
+keymaps.add('ns:my-keymap', '⌘+j, ⌘+u, ctrl+j, alt+u', editor => {
+ console.log('do stuff');
+});
+// or
+keymaps.add('ns:my-keymap', '⌘+s, ctrl+s', 'some-gjs-command');
+
+// listen to events
+editor.on('keymap:emit', (id, shortcut, e) => {
+ // ...
+})
+

Returns Object Added keymap +or just a command id as a string

get

Get the keymap by id

Parameters

Examples

keymaps.get('ns:my-keymap');
+// -> {keys, handler};
+

Returns Object Keymap object

getAll

Get all keymaps

Examples

keymaps.getAll();
+// -> {id1: {}, id2: {}};
+

Returns Object

remove

Remove the keymap by id

Parameters

Examples

keymaps.remove('ns:my-keymap');
+// -> {keys, handler};
+

Returns Object Removed keymap

removeAll

Remove all binded keymaps

Returns this

Last Updated: 3/11/2019, 6:17:19 PM
+ + + diff --git a/docs/api/modal_dialog.html b/docs/api/modal_dialog.html new file mode 100644 index 000000000..8e9e0539c --- /dev/null +++ b/docs/api/modal_dialog.html @@ -0,0 +1,50 @@ + + + + + + GrapesJS + + + + + + + + + +

You can customize the initial state of the module from the editor initialization, by passing the following Configuration Object

const editor = grapesjs.init({
+ modal: {
+   // options
+ }
+})
+

Once the editor is instantiated you can use its API. Before using these methods you should get the module from the instance

const modal = editor.Modal;
+

open

Open the modal window

Parameters

Returns this

close

Close the modal window

Returns this

onceClose

Execute callback when the modal will be closed. +The callback will be called one only time

Parameters

Returns this

onceOpen

Execute callback when the modal will be opened. +The callback will be called one only time

Parameters

Returns this

isOpen

Checks if the modal window is open

Returns Boolean

setTitle

Set the title to the modal window

Parameters

Examples

modal.setTitle('New title');
+

Returns this

getTitle

Returns the title of the modal window

Returns string

setContent

Set the content of the modal window

Parameters

Examples

modal.setContent('<div>Some HTML content</div>');
+

Returns this

getContent

Get the content of the modal window

Returns string

Last Updated: 8/10/2018, 12:36:09 AM
+ + + diff --git a/docs/api/panels.html b/docs/api/panels.html new file mode 100644 index 000000000..1dc254d66 --- /dev/null +++ b/docs/api/panels.html @@ -0,0 +1,91 @@ + + + + + + GrapesJS + + + + + + + + + +

Panels

You can customize the initial state of the module from the editor initialization, by passing the following Configuration Object

const editor = grapesjs.init({
+ panels: {
+   // options
+ }
+})
+

Once the editor is instantiated you can use its API. Before using these methods you should get the module from the instance

const panelManager = editor.Panels;
+

getPanels

Returns the collection of panels

Returns Collection Collection of panel

getPanelsEl

Returns panels element

Returns HTMLElement

addPanel

Add new panel to the collection

Parameters

  • panel(Object | Panel) Object with right properties or an instance of Panel

Examples

var newPanel = panelManager.addPanel({
+  id: 'myNewPanel',
+ visible  : true,
+ buttons  : [...],
+});
+

Returns Panel Added panel. Useful in case passed argument was an Object

removePanel

Remove a panel from the collection

Parameters

  • panel(Object | Panel | String) Object with right properties or an instance of Panel or Painel id

Examples

const newPanel = panelManager.removePanel({
+  id: 'myNewPanel',
+ visible  : true,
+ buttons  : [...],
+});
+
+const newPanel = panelManager.removePanel('myNewPanel');
+

Returns Panel Removed panel. Useful in case passed argument was an Object

getPanel

Get panel by ID

Parameters

Examples

var myPanel = panelManager.getPanel('myNewPanel');
+

Returns (Panel | null)

addButton

Add button to the panel

Parameters

  • panelIdstring Panel's ID
  • button(Object | Button) Button object or instance of Button

Examples

var newButton = panelManager.addButton('myNewPanel',{
+  id: 'myNewButton',
+  className: 'someClass',
+  command: 'someCommand',
+  attributes: { title: 'Some title'},
+  active: false,
+});
+// It's also possible to pass the command as an object
+// with .run and .stop methods
+...
+command: {
+  run: function(editor) {
+    ...
+  },
+  stop: function(editor) {
+    ...
+  }
+},
+// Or simply like a function which will be evaluated as a single .run command
+...
+command: function(editor) {
+  ...
+}
+

Returns (Button | null) Added button. Useful in case passed button was an Object

removeButton

Remove button from the panel

Parameters

Examples

const removedButton = panelManager.addButton('myNewPanel',{
+  id: 'myNewButton',
+  className: 'someClass',
+  command: 'someCommand',
+  attributes: { title: 'Some title'},
+  active: false,
+});
+
+const removedButton = panelManager.removeButton('myNewPanel', 'myNewButton');
+

Returns (Button | null) Removed button.

getButton

Get button from the panel

Parameters

Examples

var button = panelManager.getButton('myPanel','myButton');
+

Returns (Button | null)

Last Updated: 11/22/2018, 7:37:29 AM
+ + + diff --git a/docs/api/rich_text_editor.html b/docs/api/rich_text_editor.html new file mode 100644 index 000000000..21e5d1495 --- /dev/null +++ b/docs/api/rich_text_editor.html @@ -0,0 +1,79 @@ + + + + + + GrapesJS + + + + + + + + + +

RichTextEditor

This module allows to customize the built-in toolbar of the Rich Text Editor and use commands from the HTML Editing APIs. +It's highly recommended to keep this toolbar as small as possible, especially from styling commands (eg. 'fontSize') and leave this task to the Style Manager

You can customize the initial state of the module from the editor initialization, by passing the following Configuration Object

const editor = grapesjs.init({
+ rte: {
+   // options
+ }
+})
+

Once the editor is instantiated you can use its API. Before using these methods you should get the module from the instance

const rte = editor.RichTextEditor;
+

add

Add a new action to the built-in RTE toolbar

Parameters

  • namestring Action name
  • actionObject Action options (optional, default {})

Examples

rte.add('bold', {
+  icon: '<b>B</b>',
+  attributes: {title: 'Bold',}
+  result: rte => rte.exec('bold')
+});
+rte.add('link', {
+  icon: document.getElementById('t'),
+  attributes: {title: 'Link',}
+  // Example on it's easy to wrap a selected content
+  result: rte => rte.insertHTML(`<a href="#">${rte.selection()}</a>`)
+});
+// An example with fontSize
+rte.add('fontSize', {
+  icon: `<select class="gjs-field">
+        <option>1</option>
+        <option>4</option>
+        <option>7</option>
+      </select>`,
+    // Bind the 'result' on 'change' listener
+  event: 'change',
+  result: (rte, action) => rte.exec('fontSize', action.btn.firstChild.value),
+  // Callback on any input change (mousedown, keydown, etc..)
+  update: (rte, action) => {
+    const value = rte.doc.queryCommandValue(action.name);
+    if (value != 'false') { // value is a string
+      action.btn.firstChild.value = value;
+    }
+   }
+  })
+

get

Get the action by its name

Parameters

Examples

const action = rte.get('bold');
+// {name: 'bold', ...}
+

Returns Object

getAll

Get all actions

Returns Array

remove

Remove the action from the toolbar

Parameters

Examples

const action = rte.remove('bold');
+// {name: 'bold', ...}
+

Returns Object Removed action

getToolbarEl

Get the toolbar element

Returns HTMLElement

Last Updated: 7/8/2018, 11:53:31 PM
+ + + diff --git a/docs/api/selector_manager.html b/docs/api/selector_manager.html new file mode 100644 index 000000000..22b5f4994 --- /dev/null +++ b/docs/api/selector_manager.html @@ -0,0 +1,68 @@ + + + + + + GrapesJS + + + + + + + + + +

SelectorManager

Selectors in GrapesJS are used in CSS Composer inside Rules and in Components as classes. To illustrate this concept let's take +a look at this code:

span > #send-btn.btn{
+ ...
+}
+
<span>
+  <button id="send-btn" class="btn"></button>
+</span>
+

In this scenario we get:

  • span -> selector of type tag
  • send-btn -> selector of type id
  • btn -> selector of type class

So, for example, being btn the same class entity it'll be easier to refactor and track things.

You can customize the initial state of the module from the editor initialization, by passing the following Configuration Object

const editor = grapesjs.init({
+ selectorManager: {
+   // options
+ }
+})
+

Once the editor is instantiated you can use its API. Before using these methods you should get the module from the instance

const selectorManager = editor.SelectorManager;
+

getConfig

Get configuration object

Returns Object

add

Add a new selector to collection if it's not already exists. Class type is a default one

Parameters

  • name(String | Array) Selector/s name
  • optsObject Selector options (optional, default {}) +
    • opts.labelString Label for the selector, if it's not provided the label will be the same as the name (optional, default '')
    • opts.typeString Type of the selector. At the moment, only 'class' (1) is available (optional, default 1)

Examples

const selector = selectorManager.add('selectorName');
+// Same as
+const selector = selectorManager.add('selectorName', {
+  type: 1,
+  label: 'selectorName'
+});
+// Multiple selectors
+const selectors = selectorManager.add(['.class1', '.class2', '#id1']);
+

Returns (Model | Array)

addClass

Add class selectors

Parameters

Examples

sm.addClass('class1');
+sm.addClass('class1 class2');
+sm.addClass(['class1', 'class2']);
+// -> [SelectorObject, ...]
+

Returns Array Array of added selectors

get

Get the selector by its name

Parameters

Examples

const selector = selectorManager.get('selectorName');
+// or get an array
+const selectors = selectorManager.get(['class1', 'class2']);
+

Returns (Model | Array)

getAll

Get all selectors

Returns Collection

Last Updated: 4/9/2019, 10:02:45 PM
+ + + diff --git a/docs/api/storage_manager.html b/docs/api/storage_manager.html new file mode 100644 index 000000000..7e20857ab --- /dev/null +++ b/docs/api/storage_manager.html @@ -0,0 +1,70 @@ + + + + + + GrapesJS + + + + + + + + + +

StorageManager

You can customize the initial state of the module from the editor initialization, by passing the following Configuration Object

const editor = grapesjs.init({
+ storageManager: {
+   // options
+ }
+})
+

Once the editor is instantiated you can use its API. Before using these methods you should get the module from the instance

const storageManager = editor.StorageManager;
+

getConfig

Get configuration object

Returns Object

isAutosave

Checks if autosave is enabled

Returns Boolean

setAutosave

Set autosave value

Parameters

Returns this

getStepsBeforeSave

Returns number of steps required before trigger autosave

Returns number

setStepsBeforeSave

Set steps required before trigger autosave

Parameters

Returns this

add

Add new storage

Parameters

Examples

storageManager.add('local2', {
+  load: function(keys, clb, clbErr) {
+    var res = {};
+    for (var i = 0, len = keys.length; i < len; i++){
+      var v = localStorage.getItem(keys[i]);
+      if(v) res[keys[i]] = v;
+    }
+    clb(res); // might be called inside some async method
+    // In case of errors...
+    // clbErr('Went something wrong');
+  },
+  store: function(data, clb, clbErr) {
+    for(var key in data)
+      localStorage.setItem(key, data[key]);
+    clb(); // might be called inside some async method
+  }
+});
+

Returns this

get

Returns storage by id

Parameters

Returns (Object | null)

getStorages

Returns all storages

Returns Array

getCurrent

Returns current storage type

Returns string

setCurrent

Set current storage type

Parameters

Returns this

store

Store key-value resources in the current storage

Parameters

  • dataObject Data in key-value format, eg. {item1: value1, item2: value2}
  • clbFunction Callback function

Examples

storageManager.store({item1: value1, item2: value2});
+

Returns (Object | null)

load

Load resource from the current storage by keys

Parameters

Examples

storageManager.load(['item1', 'item2'], res => {
+ // res -> {item1: value1, item2: value2}
+});
+storageManager.load('item1', res => {
+// res -> {item1: value1}
+});
+

getCurrentStorage

Get current storage

Returns Storage

Last Updated: 7/8/2018, 11:25:18 PM
+ + + diff --git a/docs/api/style_manager.html b/docs/api/style_manager.html new file mode 100644 index 000000000..c1b30d67b --- /dev/null +++ b/docs/api/style_manager.html @@ -0,0 +1,95 @@ + + + + + + GrapesJS + + + + + + + + + +

StyleManager

With Style Manager you build categories (called sectors) of CSS properties which could be used to customize the style of components. +You can customize the initial state of the module from the editor initialization, by passing the following Configuration Object

const editor = grapesjs.init({
+ styleManager: {
+   // options
+ }
+})
+

Once the editor is instantiated you can use its API. Before using these methods you should get the module from the instance

const styleManager = editor.StyleManager;
+

getConfig

Get configuration object

Returns Object

addSector

Add new sector to the collection. If the sector with the same id already exists, +that one will be returned

Parameters

  • idstring Sector id
  • sectorObject Object representing sector +
    • sector.namestring Sector's label (optional, default '')
    • sector.openBoolean Indicates if the sector should be opened (optional, default true)
    • sector.propertiesArray<Object> Array of properties (optional, default [])

Examples

var sector = styleManager.addSector('mySector',{
+  name: 'My sector',
+  open: true,
+  properties: [{ name: 'My property'}]
+});
+

Returns Sector Added Sector

getSector

Get sector by id

Parameters

Examples

var sector = styleManager.getSector('mySector');
+

Returns (Sector | null)

removeSector

Remove a sector by id

Parameters

Examples

const removed = styleManager.removeSector('mySector');
+

Returns Sector Removed sector

getSectors

Get all sectors

Returns Sectors Collection of sectors

addProperty

Add property to the sector identified by id

Parameters

  • sectorIdstring Sector id
  • propertyObject Property object +
    • property.namestring Name of the property (optional, default '')
    • property.propertystring CSS property, eg. min-height (optional, default '')
    • property.typestring Type of the property: integer | radio | select | color | file | composite | stack (optional, default '')
    • property.unitsArray<string> Unit of measure available, eg. ['px','%','em']. Only for integer type (optional, default [])
    • property.unitstring Default selected unit from units. Only for integer type (optional, default '')
    • property.minnumber Min possible value. Only for integer type (optional, default null)
    • property.maxnumber Max possible value. Only for integer type (optional, default null)
    • property.defaultsstring Default value (optional, default '')
    • property.infostring Some description (optional, default '')
    • property.iconstring Class name. If exists no text will be displayed (optional, default '')
    • property.previewBoolean Show layers preview. Only for stack type (optional, default false)
    • property.functionNamestring Indicates if value need to be wrapped in some function, for istance transform: rotate(90deg) (optional, default '')
    • property.propertiesArray<Object> Nested properties for composite and stack type (optional, default [])
    • property.layersArray<Object> Layers for stack properties (optional, default [])
    • property.listArray<Object> List of possible options for radio and select types (optional, default [])

Examples

var property = styleManager.addProperty('mySector',{
+  name: 'Minimum height',
+  property: 'min-height',
+  type: 'select',
+  defaults: '100px',
+  list: [{
+    value: '100px',
+    name: '100',
+   },{
+     value: '200px',
+     name: '200',
+   }],
+});
+

Returns (Property | null) Added Property or null in case sector doesn't exist

getProperty

Get property by its CSS name and sector id

Parameters

  • sectorIdstring Sector id
  • namestring CSS property name, eg. 'min-height'

Examples

var property = styleManager.getProperty('mySector','min-height');
+

Returns (Property | null)

removeProperty

Remove a property from the sector

Parameters

  • sectorIdstring Sector id
  • namestring CSS property name, eg. 'min-height'

Examples

const property = styleManager.removeProperty('mySector', 'min-height');
+

Returns Property Removed property

getProperties

Get properties of the sector

Parameters

Examples

var properties = styleManager.getProperties('mySector');
+

Returns Properties Collection of properties

getModelToStyle

Get what to style inside Style Manager. If you select the component +without classes the entity is the Component itself and all changes will +go inside its 'style' property. Otherwise, if the selected component has +one or more classes, the function will return the corresponding CSS Rule

Parameters

  • modelModel

Returns Model

addType

Add new property type

Parameters

  • idstring Type ID
  • definitionObject Definition of the type. Each definition contains +model (business logic), view (presentation logic) +and isType function which recognize the type of the +passed entity

Examples

styleManager.addType('my-type', {
+ model: {},
+ view: {},
+ isType: (value) => {
+   if (value && value.type == 'my-type') {
+     return value;
+   }
+ },
+})
+

getType

Get type

Parameters

Returns Object Type definition

getTypes

Get all types

Returns Array

createType

Create new property from type

Parameters

  • idstring Type ID
  • optionsObject Options (optional, default {}) +
    • options.modelObject Custom model object (optional, default {})
    • options.viewObject Custom view object (optional, default {})

Examples

const propView = styleManager.createType('integer', {
+ model: {units: ['px', 'rem']}
+});
+propView.render();
+propView.model.on('change:value', ...);
+someContainer.appendChild(propView.el);
+

Returns PropertyView

setTarget

Select different target for the Style Manager. +It could be a Component, CSSRule, or a string of any CSS selector

Parameters

  • target(Component | CSSRule | String)
  • opts

Returns Styleable A Component or CSSRule

Last Updated: 1/5/2019, 1:17:11 AM
+ + + diff --git a/docs/api/undo_manager.html b/docs/api/undo_manager.html new file mode 100644 index 000000000..8211aef11 --- /dev/null +++ b/docs/api/undo_manager.html @@ -0,0 +1,55 @@ + + + + + + GrapesJS + + + + + + + + + +

UndoManager

This module allows to manage the stack of changes applied in canvas. +Once the editor is instantiated you can use its API. Before using these methods you should get the module from the instance

const um = editor.UndoManager;
+

getConfig

Get module configurations

Examples

const config = um.getConfig();
+// { ... }
+

Returns Object Configuration object

add

Add an entity (Model/Collection) to track +Note: New Components and CSSRules will be added automatically

Parameters

  • entity(Model | Collection) Entity to track

Examples

um.add(someModelOrCollection);
+

Returns this

remove

Remove and stop tracking the entity (Model/Collection)

Parameters

  • entity(Model | Collection) Entity to remove

Examples

um.remove(someModelOrCollection);
+

Returns this

removeAll

Remove all entities

Examples

um.removeAll();
+

Returns this

start

Start/resume tracking changes

Examples

um.start();
+

Returns this

stop

Stop tracking changes

Examples

um.stop();
+

Returns this

undo

Undo last change

Examples

um.undo();
+

Returns this

undoAll

Undo all changes

Examples

um.undoAll();
+

Returns this

redo

Redo last change

Examples

um.redo();
+

Returns this

redoAll

Redo all changes

Examples

um.redoAll();
+

Returns this

hasUndo

Checks if exists an available undo

Examples

um.hasUndo();
+

Returns Boolean

hasRedo

Checks if exists an available redo

Examples

um.hasRedo();
+

Returns Boolean

getStack

Get stack of changes

Examples

const stack = um.getStack();
+stack.each(item => ...);
+

Returns Collection

clear

Clear the stack

Examples

um.clear();
+

Returns this

Last Updated: 7/9/2018, 12:11:12 AM
+ + + diff --git a/docs/assets/js/app.f0e5ae13.js b/docs/assets/js/app.be6a3a1f.js similarity index 77% rename from docs/assets/js/app.f0e5ae13.js rename to docs/assets/js/app.be6a3a1f.js index c43e511a6..2394be443 100644 --- a/docs/assets/js/app.f0e5ae13.js +++ b/docs/assets/js/app.be6a3a1f.js @@ -3,6 +3,6 @@ * Vue.js v2.5.17 * (c) 2014-2018 Evan You * Released under the MIT License. - */var r=Object.freeze({});function i(e){return null==e}function a(e){return null!=e}function o(e){return!0===e}function l(e){return"string"==typeof e||"number"==typeof e||"symbol"==typeof e||"boolean"==typeof e}function s(e){return null!==e&&"object"==typeof e}var u=Object.prototype.toString;function c(e){return"[object Object]"===u.call(e)}function p(e){return"[object RegExp]"===u.call(e)}function f(e){var t=parseFloat(String(e));return t>=0&&Math.floor(t)===t&&isFinite(e)}function d(e){return null==e?"":"object"==typeof e?JSON.stringify(e,null,2):String(e)}function v(e){var t=parseFloat(e);return isNaN(t)?e:t}function h(e,t){for(var n=Object.create(null),r=e.split(","),i=0;i-1)return e.splice(n,1)}}var y=Object.prototype.hasOwnProperty;function b(e,t){return y.call(e,t)}function _(e){var t=Object.create(null);return function(n){return t[n]||(t[n]=e(n))}}var x=/-(\w)/g,C=_(function(e){return e.replace(x,function(e,t){return t?t.toUpperCase():""})}),w=_(function(e){return e.charAt(0).toUpperCase()+e.slice(1)}),k=/\B([A-Z])/g,E=_(function(e){return e.replace(k,"-$1").toLowerCase()});var S=Function.prototype.bind?function(e,t){return e.bind(t)}:function(e,t){function n(n){var r=arguments.length;return r?r>1?e.apply(t,arguments):e.call(t,n):e.call(t)}return n._length=e.length,n};function $(e,t){t=t||0;for(var n=e.length-t,r=new Array(n);n--;)r[n]=e[n+t];return r}function O(e,t){for(var n in t)e[n]=t[n];return e}function P(e){for(var t={},n=0;n0,X=W&&W.indexOf("edge/")>0,Y=(W&&W.indexOf("android"),W&&/iphone|ipad|ipod|ios/.test(W)||"ios"===G),Q=(W&&/chrome\/\d+/.test(W),{}.watch),Z=!1;if(z)try{var ee={};Object.defineProperty(ee,"passive",{get:function(){Z=!0}}),window.addEventListener("test-passive",null,ee)}catch(e){}var te=function(){return void 0===H&&(H=!z&&!V&&"undefined"!=typeof global&&"server"===global.process.env.VUE_ENV),H},ne=z&&window.__VUE_DEVTOOLS_GLOBAL_HOOK__;function re(e){return"function"==typeof e&&/native code/.test(e.toString())}var ie,ae="undefined"!=typeof Symbol&&re(Symbol)&&"undefined"!=typeof Reflect&&re(Reflect.ownKeys);ie="undefined"!=typeof Set&&re(Set)?Set:function(){function e(){this.set=Object.create(null)}return e.prototype.has=function(e){return!0===this.set[e]},e.prototype.add=function(e){this.set[e]=!0},e.prototype.clear=function(){this.set=Object.create(null)},e}();var oe=A,le=0,se=function(){this.id=le++,this.subs=[]};se.prototype.addSub=function(e){this.subs.push(e)},se.prototype.removeSub=function(e){g(this.subs,e)},se.prototype.depend=function(){se.target&&se.target.addDep(this)},se.prototype.notify=function(){for(var e=this.subs.slice(),t=0,n=e.length;t-1)if(a&&!b(i,"default"))o=!1;else if(""===o||o===E(e)){var s=Be(String,i.type);(s<0||l0&&(ut((u=e(u,(n||"")+"_"+s))[0])&&ut(p)&&(r[c]=he(p.text+u[0].text),u.shift()),r.push.apply(r,u)):l(u)?ut(p)?r[c]=he(p.text+u):""!==u&&r.push(he(u)):ut(u)&&ut(p)?r[c]=he(p.text+u.text):(o(t._isVList)&&a(u.tag)&&i(u.key)&&a(n)&&(u.key="__vlist"+n+"_"+s+"__"),r.push(u)));return r}(e):void 0}function ut(e){return a(e)&&a(e.text)&&!1===e.isComment}function ct(e,t){return(e.__esModule||ae&&"Module"===e[Symbol.toStringTag])&&(e=e.default),s(e)?t.extend(e):e}function pt(e){return e.isComment&&e.asyncFactory}function ft(e){if(Array.isArray(e))for(var t=0;tOt&&wt[n].id>e.id;)n--;wt.splice(n+1,0,e)}else wt.push(e);St||(St=!0,Ze(Pt))}}(this)},Tt.prototype.run=function(){if(this.active){var e=this.get();if(e!==this.value||s(e)||this.deep){var t=this.value;if(this.value=e,this.user)try{this.cb.call(this.vm,e,t)}catch(e){Fe(e,this.vm,'callback for watcher "'+this.expression+'"')}else this.cb.call(this.vm,e,t)}}},Tt.prototype.evaluate=function(){this.value=this.get(),this.dirty=!1},Tt.prototype.depend=function(){for(var e=this.deps.length;e--;)this.deps[e].depend()},Tt.prototype.teardown=function(){if(this.active){this.vm._isBeingDestroyed||g(this.vm._watchers,this);for(var e=this.deps.length;e--;)this.deps[e].removeSub(this);this.active=!1}};var jt={enumerable:!0,configurable:!0,get:A,set:A};function Lt(e,t,n){jt.get=function(){return this[t][n]},jt.set=function(e){this[t][n]=e},Object.defineProperty(e,n,jt)}function Mt(e){e._watchers=[];var t=e.$options;t.props&&function(e,t){var n=e.$options.propsData||{},r=e._props={},i=e.$options._propKeys=[];e.$parent&&xe(!1);var a=function(a){i.push(a);var o=De(a,t,n,e);Se(r,a,o),a in e||Lt(e,"_props",a)};for(var o in t)a(o);xe(!0)}(e,t.props),t.methods&&function(e,t){e.$options.props;for(var n in t)e[n]=null==t[n]?A:S(t[n],e)}(e,t.methods),t.data?function(e){var t=e.$options.data;c(t=e._data="function"==typeof t?function(e,t){ce();try{return e.call(t,t)}catch(e){return Fe(e,t,"data()"),{}}finally{pe()}}(t,e):t||{})||(t={});var n=Object.keys(t),r=e.$options.props,i=(e.$options.methods,n.length);for(;i--;){var a=n[i];0,r&&b(r,a)||(o=void 0,36!==(o=(a+"").charCodeAt(0))&&95!==o&&Lt(e,"_data",a))}var o;Ee(t,!0)}(e):Ee(e._data={},!0),t.computed&&function(e,t){var n=e._computedWatchers=Object.create(null),r=te();for(var i in t){var a=t[i],o="function"==typeof a?a:a.get;0,r||(n[i]=new Tt(e,o||A,A,It)),i in e||Rt(e,i,a)}}(e,t.computed),t.watch&&t.watch!==Q&&function(e,t){for(var n in t){var r=t[n];if(Array.isArray(r))for(var i=0;i=0||n.indexOf(e[i])<0)&&r.push(e[i]);return r}return e}function pn(e){this._init(e)}function fn(e){e.cid=0;var t=1;e.extend=function(e){e=e||{};var n=this,r=n.cid,i=e._Ctor||(e._Ctor={});if(i[r])return i[r];var a=e.name||n.options.name;var o=function(e){this._init(e)};return(o.prototype=Object.create(n.prototype)).constructor=o,o.cid=t++,o.options=Ie(n.options,e),o.super=n,o.options.props&&function(e){var t=e.options.props;for(var n in t)Lt(e.prototype,"_props",n)}(o),o.options.computed&&function(e){var t=e.options.computed;for(var n in t)Rt(e.prototype,n,t[n])}(o),o.extend=n.extend,o.mixin=n.mixin,o.use=n.use,D.forEach(function(e){o[e]=n[e]}),a&&(o.options.components[a]=o),o.superOptions=n.options,o.extendOptions=e,o.sealedOptions=O({},o.options),i[r]=o,o}}function dn(e){return e&&(e.Ctor.options.name||e.tag)}function vn(e,t){return Array.isArray(e)?e.indexOf(t)>-1:"string"==typeof e?e.split(",").indexOf(t)>-1:!!p(e)&&e.test(t)}function hn(e,t){var n=e.cache,r=e.keys,i=e._vnode;for(var a in n){var o=n[a];if(o){var l=dn(o.componentOptions);l&&!t(l)&&mn(n,a,r,i)}}}function mn(e,t,n,r){var i=e[t];!i||r&&i.tag===r.tag||i.componentInstance.$destroy(),e[t]=null,g(n,t)}!function(e){e.prototype._init=function(e){var t=this;t._uid=sn++,t._isVue=!0,e&&e._isComponent?function(e,t){var n=e.$options=Object.create(e.constructor.options),r=t._parentVnode;n.parent=t.parent,n._parentVnode=r,n._parentElm=t._parentElm,n._refElm=t._refElm;var i=r.componentOptions;n.propsData=i.propsData,n._parentListeners=i.listeners,n._renderChildren=i.children,n._componentTag=i.tag,t.render&&(n.render=t.render,n.staticRenderFns=t.staticRenderFns)}(t,e):t.$options=Ie(un(t.constructor),e||{},t),t._renderProxy=t,t._self=t,function(e){var t=e.$options,n=t.parent;if(n&&!t.abstract){for(;n.$options.abstract&&n.$parent;)n=n.$parent;n.$children.push(e)}e.$parent=n,e.$root=n?n.$root:e,e.$children=[],e.$refs={},e._watcher=null,e._inactive=null,e._directInactive=!1,e._isMounted=!1,e._isDestroyed=!1,e._isBeingDestroyed=!1}(t),function(e){e._events=Object.create(null),e._hasHookEvent=!1;var t=e.$options._parentListeners;t&&ht(e,t)}(t),function(e){e._vnode=null,e._staticTrees=null;var t=e.$options,n=e.$vnode=t._parentVnode,i=n&&n.context;e.$slots=mt(t._renderChildren,i),e.$scopedSlots=r,e._c=function(t,n,r,i){return ln(e,t,n,r,i,!1)},e.$createElement=function(t,n,r,i){return ln(e,t,n,r,i,!0)};var a=n&&n.data;Se(e,"$attrs",a&&a.attrs||r,null,!0),Se(e,"$listeners",t._parentListeners||r,null,!0)}(t),Ct(t,"beforeCreate"),function(e){var t=Nt(e.$options.inject,e);t&&(xe(!1),Object.keys(t).forEach(function(n){Se(e,n,t[n])}),xe(!0))}(t),Mt(t),function(e){var t=e.$options.provide;t&&(e._provided="function"==typeof t?t.call(e):t)}(t),Ct(t,"created"),t.$options.el&&t.$mount(t.$options.el)}}(pn),function(e){var t={get:function(){return this._data}},n={get:function(){return this._props}};Object.defineProperty(e.prototype,"$data",t),Object.defineProperty(e.prototype,"$props",n),e.prototype.$set=$e,e.prototype.$delete=Oe,e.prototype.$watch=function(e,t,n){if(c(t))return Ut(this,e,t,n);(n=n||{}).user=!0;var r=new Tt(this,e,t,n);return n.immediate&&t.call(this,r.value),function(){r.teardown()}}}(pn),function(e){var t=/^hook:/;e.prototype.$on=function(e,n){if(Array.isArray(e))for(var r=0,i=e.length;r1?$(t):t;for(var n=$(arguments,1),r=0,i=t.length;rparseInt(this.max)&&mn(o,l[0],l,this._vnode)),t.data.keepAlive=!0}return t||e&&e[0]}}};!function(e){var t={get:function(){return N}};Object.defineProperty(e,"config",t),e.util={warn:oe,extend:O,mergeOptions:Ie,defineReactive:Se},e.set=$e,e.delete=Oe,e.nextTick=Ze,e.options=Object.create(null),D.forEach(function(t){e.options[t+"s"]=Object.create(null)}),e.options._base=e,O(e.options.components,yn),function(e){e.use=function(e){var t=this._installedPlugins||(this._installedPlugins=[]);if(t.indexOf(e)>-1)return this;var n=$(arguments,1);return n.unshift(this),"function"==typeof e.install?e.install.apply(e,n):"function"==typeof e&&e.apply(null,n),t.push(e),this}}(e),function(e){e.mixin=function(e){return this.options=Ie(this.options,e),this}}(e),fn(e),function(e){D.forEach(function(t){e[t]=function(e,n){return n?("component"===t&&c(n)&&(n.name=n.name||e,n=this.options._base.extend(n)),"directive"===t&&"function"==typeof n&&(n={bind:n,update:n}),this.options[t+"s"][e]=n,n):this.options[t+"s"][e]}})}(e)}(pn),Object.defineProperty(pn.prototype,"$isServer",{get:te}),Object.defineProperty(pn.prototype,"$ssrContext",{get:function(){return this.$vnode&&this.$vnode.ssrContext}}),Object.defineProperty(pn,"FunctionalRenderContext",{value:Qt}),pn.version="2.5.17";var bn=h("style,class"),_n=h("input,textarea,option,select,progress"),xn=h("contenteditable,draggable,spellcheck"),Cn=h("allowfullscreen,async,autofocus,autoplay,checked,compact,controls,declare,default,defaultchecked,defaultmuted,defaultselected,defer,disabled,enabled,formnovalidate,hidden,indeterminate,inert,ismap,itemscope,loop,multiple,muted,nohref,noresize,noshade,novalidate,nowrap,open,pauseonexit,readonly,required,reversed,scoped,seamless,selected,sortable,translate,truespeed,typemustmatch,visible"),wn="http://www.w3.org/1999/xlink",kn=function(e){return":"===e.charAt(5)&&"xlink"===e.slice(0,5)},En=function(e){return kn(e)?e.slice(6,e.length):""},Sn=function(e){return null==e||!1===e};function $n(e){for(var t=e.data,n=e,r=e;a(r.componentInstance);)(r=r.componentInstance._vnode)&&r.data&&(t=On(r.data,t));for(;a(n=n.parent);)n&&n.data&&(t=On(t,n.data));return function(e,t){if(a(e)||a(t))return Pn(e,An(t));return""}(t.staticClass,t.class)}function On(e,t){return{staticClass:Pn(e.staticClass,t.staticClass),class:a(e.class)?[e.class,t.class]:t.class}}function Pn(e,t){return e?t?e+" "+t:e:t||""}function An(e){return Array.isArray(e)?function(e){for(var t,n="",r=0,i=e.length;r-1?Zn(e,t,n):Cn(t)?Sn(n)?e.removeAttribute(t):(n="allowfullscreen"===t&&"EMBED"===e.tagName?"true":t,e.setAttribute(t,n)):xn(t)?e.setAttribute(t,Sn(n)||"false"===n?"false":"true"):kn(t)?Sn(n)?e.removeAttributeNS(wn,En(t)):e.setAttributeNS(wn,t,n):Zn(e,t,n)}function Zn(e,t,n){if(Sn(n))e.removeAttribute(t);else{if(K&&!J&&"TEXTAREA"===e.tagName&&"placeholder"===t&&!e.__ieph){var r=function(t){t.stopImmediatePropagation(),e.removeEventListener("input",r)};e.addEventListener("input",r),e.__ieph=!0}e.setAttribute(t,n)}}var er={create:Yn,update:Yn};function tr(e,t){var n=t.elm,r=t.data,o=e.data;if(!(i(r.staticClass)&&i(r.class)&&(i(o)||i(o.staticClass)&&i(o.class)))){var l=$n(t),s=n._transitionClasses;a(s)&&(l=Pn(l,An(s))),l!==n._prevClass&&(n.setAttribute("class",l),n._prevClass=l)}}var nr,rr={create:tr,update:tr},ir="__r",ar="__c";function or(e,t,n,r,i){var a;t=(a=t)._withTask||(a._withTask=function(){Je=!0;var e=a.apply(null,arguments);return Je=!1,e}),n&&(t=function(e,t,n){var r=nr;return function i(){null!==e.apply(null,arguments)&&lr(t,i,n,r)}}(t,e,r)),nr.addEventListener(e,t,Z?{capture:r,passive:i}:r)}function lr(e,t,n,r){(r||nr).removeEventListener(e,t._withTask||t,n)}function sr(e,t){if(!i(e.data.on)||!i(t.data.on)){var n=t.data.on||{},r=e.data.on||{};nr=t.elm,function(e){if(a(e[ir])){var t=K?"change":"input";e[t]=[].concat(e[ir],e[t]||[]),delete e[ir]}a(e[ar])&&(e.change=[].concat(e[ar],e.change||[]),delete e[ar])}(n),at(n,r,or,lr,t.context),nr=void 0}}var ur={create:sr,update:sr};function cr(e,t){if(!i(e.data.domProps)||!i(t.data.domProps)){var n,r,o=t.elm,l=e.data.domProps||{},s=t.data.domProps||{};for(n in a(s.__ob__)&&(s=t.data.domProps=O({},s)),l)i(s[n])&&(o[n]="");for(n in s){if(r=s[n],"textContent"===n||"innerHTML"===n){if(t.children&&(t.children.length=0),r===l[n])continue;1===o.childNodes.length&&o.removeChild(o.childNodes[0])}if("value"===n){o._value=r;var u=i(r)?"":String(r);pr(o,u)&&(o.value=u)}else o[n]=r}}}function pr(e,t){return!e.composing&&("OPTION"===e.tagName||function(e,t){var n=!0;try{n=document.activeElement!==e}catch(e){}return n&&e.value!==t}(e,t)||function(e,t){var n=e.value,r=e._vModifiers;if(a(r)){if(r.lazy)return!1;if(r.number)return v(n)!==v(t);if(r.trim)return n.trim()!==t.trim()}return n!==t}(e,t))}var fr={create:cr,update:cr},dr=_(function(e){var t={},n=/:(.+)/;return e.split(/;(?![^(]*\))/g).forEach(function(e){if(e){var r=e.split(n);r.length>1&&(t[r[0].trim()]=r[1].trim())}}),t});function vr(e){var t=hr(e.style);return e.staticStyle?O(e.staticStyle,t):t}function hr(e){return Array.isArray(e)?P(e):"string"==typeof e?dr(e):e}var mr,gr=/^--/,yr=/\s*!important$/,br=function(e,t,n){if(gr.test(t))e.style.setProperty(t,n);else if(yr.test(n))e.style.setProperty(t,n.replace(yr,""),"important");else{var r=xr(t);if(Array.isArray(n))for(var i=0,a=n.length;i-1?t.split(/\s+/).forEach(function(t){return e.classList.add(t)}):e.classList.add(t);else{var n=" "+(e.getAttribute("class")||"")+" ";n.indexOf(" "+t+" ")<0&&e.setAttribute("class",(n+t).trim())}}function Er(e,t){if(t&&(t=t.trim()))if(e.classList)t.indexOf(" ")>-1?t.split(/\s+/).forEach(function(t){return e.classList.remove(t)}):e.classList.remove(t),e.classList.length||e.removeAttribute("class");else{for(var n=" "+(e.getAttribute("class")||"")+" ",r=" "+t+" ";n.indexOf(r)>=0;)n=n.replace(r," ");(n=n.trim())?e.setAttribute("class",n):e.removeAttribute("class")}}function Sr(e){if(e){if("object"==typeof e){var t={};return!1!==e.css&&O(t,$r(e.name||"v")),O(t,e),t}return"string"==typeof e?$r(e):void 0}}var $r=_(function(e){return{enterClass:e+"-enter",enterToClass:e+"-enter-to",enterActiveClass:e+"-enter-active",leaveClass:e+"-leave",leaveToClass:e+"-leave-to",leaveActiveClass:e+"-leave-active"}}),Or=z&&!J,Pr="transition",Ar="animation",Tr="transition",jr="transitionend",Lr="animation",Mr="animationend";Or&&(void 0===window.ontransitionend&&void 0!==window.onwebkittransitionend&&(Tr="WebkitTransition",jr="webkitTransitionEnd"),void 0===window.onanimationend&&void 0!==window.onwebkitanimationend&&(Lr="WebkitAnimation",Mr="webkitAnimationEnd"));var Ir=z?window.requestAnimationFrame?window.requestAnimationFrame.bind(window):setTimeout:function(e){return e()};function Rr(e){Ir(function(){Ir(e)})}function Dr(e,t){var n=e._transitionClasses||(e._transitionClasses=[]);n.indexOf(t)<0&&(n.push(t),kr(e,t))}function Ur(e,t){e._transitionClasses&&g(e._transitionClasses,t),Er(e,t)}function Nr(e,t,n){var r=Fr(e,t),i=r.type,a=r.timeout,o=r.propCount;if(!i)return n();var l=i===Pr?jr:Mr,s=0,u=function(){e.removeEventListener(l,c),n()},c=function(t){t.target===e&&++s>=o&&u()};setTimeout(function(){s0&&(n=Pr,c=o,p=a.length):t===Ar?u>0&&(n=Ar,c=u,p=s.length):p=(n=(c=Math.max(o,u))>0?o>u?Pr:Ar:null)?n===Pr?a.length:s.length:0,{type:n,timeout:c,propCount:p,hasTransform:n===Pr&&Br.test(r[Tr+"Property"])}}function Hr(e,t){for(;e.length1}function Kr(e,t){!0!==t.data.show&&zr(t)}var Jr=function(e){var t,n,r={},s=e.modules,u=e.nodeOps;for(t=0;tv?b(e,i(n[g+1])?null:n[g+1].elm,n,d,g,r):d>g&&x(0,t,f,v)}(s,d,v,n,l):a(v)?(a(e.text)&&u.setTextContent(s,""),b(s,null,v,0,v.length-1,n)):a(d)?x(0,d,0,d.length-1):a(e.text)&&u.setTextContent(s,""):e.text!==t.text&&u.setTextContent(s,t.text),a(f)&&a(c=f.hook)&&a(c=c.postpatch)&&c(e,t)}}}function E(e,t,n){if(o(n)&&a(e.parent))e.parent.data.pendingInsert=t;else for(var r=0;r-1,o.selected!==a&&(o.selected=a);else if(L(ei(o),r))return void(e.selectedIndex!==l&&(e.selectedIndex=l));i||(e.selectedIndex=-1)}}function Zr(e,t){return t.every(function(t){return!L(t,e)})}function ei(e){return"_value"in e?e._value:e.value}function ti(e){e.target.composing=!0}function ni(e){e.target.composing&&(e.target.composing=!1,ri(e.target,"input"))}function ri(e,t){var n=document.createEvent("HTMLEvents");n.initEvent(t,!0,!0),e.dispatchEvent(n)}function ii(e){return!e.componentInstance||e.data&&e.data.transition?e:ii(e.componentInstance._vnode)}var ai={model:Xr,show:{bind:function(e,t,n){var r=t.value,i=(n=ii(n)).data&&n.data.transition,a=e.__vOriginalDisplay="none"===e.style.display?"":e.style.display;r&&i?(n.data.show=!0,zr(n,function(){e.style.display=a})):e.style.display=r?a:"none"},update:function(e,t,n){var r=t.value;!r!=!t.oldValue&&((n=ii(n)).data&&n.data.transition?(n.data.show=!0,r?zr(n,function(){e.style.display=e.__vOriginalDisplay}):Vr(n,function(){e.style.display="none"})):e.style.display=r?e.__vOriginalDisplay:"none")},unbind:function(e,t,n,r,i){i||(e.style.display=e.__vOriginalDisplay)}}},oi={name:String,appear:Boolean,css:Boolean,mode:String,type:String,enterClass:String,leaveClass:String,enterToClass:String,leaveToClass:String,enterActiveClass:String,leaveActiveClass:String,appearClass:String,appearActiveClass:String,appearToClass:String,duration:[Number,String,Object]};function li(e){var t=e&&e.componentOptions;return t&&t.Ctor.options.abstract?li(ft(t.children)):e}function si(e){var t={},n=e.$options;for(var r in n.propsData)t[r]=e[r];var i=n._parentListeners;for(var a in i)t[C(a)]=i[a];return t}function ui(e,t){if(/\d-keep-alive$/.test(t.tag))return e("keep-alive",{props:t.componentOptions.propsData})}var ci={name:"transition",props:oi,abstract:!0,render:function(e){var t=this,n=this.$slots.default;if(n&&(n=n.filter(function(e){return e.tag||pt(e)})).length){0;var r=this.mode;0;var i=n[0];if(function(e){for(;e=e.parent;)if(e.data.transition)return!0}(this.$vnode))return i;var a=li(i);if(!a)return i;if(this._leaving)return ui(e,i);var o="__transition-"+this._uid+"-";a.key=null==a.key?a.isComment?o+"comment":o+a.tag:l(a.key)?0===String(a.key).indexOf(o)?a.key:o+a.key:a.key;var s=(a.data||(a.data={})).transition=si(this),u=this._vnode,c=li(u);if(a.data.directives&&a.data.directives.some(function(e){return"show"===e.name})&&(a.data.show=!0),c&&c.data&&!function(e,t){return t.key===e.key&&t.tag===e.tag}(a,c)&&!pt(c)&&(!c.componentInstance||!c.componentInstance._vnode.isComment)){var p=c.data.transition=O({},s);if("out-in"===r)return this._leaving=!0,ot(p,"afterLeave",function(){t._leaving=!1,t.$forceUpdate()}),ui(e,i);if("in-out"===r){if(pt(a))return u;var f,d=function(){f()};ot(s,"afterEnter",d),ot(s,"enterCancelled",d),ot(p,"delayLeave",function(e){f=e})}}return i}}},pi=O({tag:String,moveClass:String},oi);function fi(e){e.elm._moveCb&&e.elm._moveCb(),e.elm._enterCb&&e.elm._enterCb()}function di(e){e.data.newPos=e.elm.getBoundingClientRect()}function vi(e){var t=e.data.pos,n=e.data.newPos,r=t.left-n.left,i=t.top-n.top;if(r||i){e.data.moved=!0;var a=e.elm.style;a.transform=a.WebkitTransform="translate("+r+"px,"+i+"px)",a.transitionDuration="0s"}}delete pi.mode;var hi={Transition:ci,TransitionGroup:{props:pi,render:function(e){for(var t=this.tag||this.$vnode.data.tag||"span",n=Object.create(null),r=this.prevChildren=this.children,i=this.$slots.default||[],a=this.children=[],o=si(this),l=0;l-1?In[e]=t.constructor===window.HTMLUnknownElement||t.constructor===window.HTMLElement:In[e]=/HTMLUnknownElement/.test(t.toString())},O(pn.options.directives,ai),O(pn.options.components,hi),pn.prototype.__patch__=z?Jr:A,pn.prototype.$mount=function(e,t){return function(e,t,n){var r;return e.$el=t,e.$options.render||(e.$options.render=ve),Ct(e,"beforeMount"),r=function(){e._update(e._render(),n)},new Tt(e,r,A,null,!0),n=!1,null==e.$vnode&&(e._isMounted=!0,Ct(e,"mounted")),e}(this,e=e&&z?function(e){if("string"==typeof e){var t=document.querySelector(e);return t||document.createElement("div")}return e}(e):void 0,t)},z&&setTimeout(function(){N.devtools&&ne&&ne.emit("init",pn)},0),t.a=pn},function(e,t,n){"use strict";function r(e,t,n,r,i,a,o,l){var s,u="function"==typeof e?e.options:e;if(t&&(u.render=t,u.staticRenderFns=n,u._compiled=!0),r&&(u.functional=!0),a&&(u._scopeId="data-v-"+a),o?(s=function(e){(e=e||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext)||"undefined"==typeof __VUE_SSR_CONTEXT__||(e=__VUE_SSR_CONTEXT__),i&&i.call(this,e),e&&e._registeredComponents&&e._registeredComponents.add(o)},u._ssrRegister=s):i&&(s=l?function(){i.call(this,this.$root.$options.shadowRoot)}:i),s)if(u.functional){u._injectStyles=s;var c=u.render;u.render=function(e,t){return s.call(t),c(e,t)}}else{var p=u.beforeCreate;u.beforeCreate=p?[].concat(p,s):[s]}return{exports:e,options:u}}n.d(t,"a",function(){return r})},function(e,t,n){"use strict";var r=n(67),i=n(35),a=n(1),o=Object(a.a)(i.default,r.a,r.b,!1,null,null,null);o.options.__file="Layout.vue",t.default=o.exports},function(e,t,n){var r=n(70)("wks"),i=n(26),a=n(4).Symbol,o="function"==typeof a;(e.exports=function(e){return r[e]||(r[e]=o&&a[e]||(o?a:i)("Symbol."+e))}).store=r},function(e,t){var n=e.exports="undefined"!=typeof window&&window.Math==Math?window:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")();"number"==typeof __g&&(__g=n)},function(e,t,n){var r=n(4),i=n(15),a=n(11),o=n(16),l=n(13),s=function(e,t,n){var u,c,p,f,d=e&s.F,v=e&s.G,h=e&s.S,m=e&s.P,g=e&s.B,y=v?r:h?r[t]||(r[t]={}):(r[t]||{}).prototype,b=v?i:i[t]||(i[t]={}),_=b.prototype||(b.prototype={});for(u in v&&(n=t),n)p=((c=!d&&y&&void 0!==y[u])?y:n)[u],f=g&&c?l(p,r):m&&"function"==typeof p?l(Function.call,p):p,y&&o(y,u,p,e&s.U),b[u]!=p&&a(b,u,f),m&&_[u]!=p&&(_[u]=p)};r.core=i,s.F=1,s.G=2,s.S=4,s.P=8,s.B=16,s.W=32,s.U=64,s.R=128,e.exports=s},function(e,t){e.exports=function(e){return"object"==typeof e?null!==e:"function"==typeof e}},function(e,t){e.exports=function(e){try{return!!e()}catch(e){return!0}}},function(e,t,n){var r=n(6);e.exports=function(e){if(!r(e))throw TypeError(e+" is not an object!");return e}},function(e,t,n){e.exports=!n(7)(function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a})},function(e,t,n){"use strict";function r(e){return function(e){if(Array.isArray(e)){for(var t=0,n=new Array(e.length);t=e.length?(this._t=void 0,i(1)):i(0,"keys"==t?n:"values"==t?e[n]:[n,e[n]])},"values"),a.Arguments=a.Array,r("keys"),r("values"),r("entries")},function(e,t,n){"use strict";var r=n(7);e.exports=function(e,t){return!!e&&r(function(){t?e.call(null,function(){},1):e.call(null)})}},function(e,t,n){"use strict";var r=n(5),i=n(32)(3);r(r.P+r.F*!n(22)([].some,!0),"Array",{some:function(e){return i(this,e,arguments[1])}})},function(e,t,n){var r=n(5);r(r.S+r.F,"Object",{assign:n(102)})},function(e,t){e.exports=function(e){if("function"!=typeof e)throw TypeError(e+" is not a function!");return e}},function(e,t){var n=0,r=Math.random();e.exports=function(e){return"Symbol(".concat(void 0===e?"":e,")_",(++n+r).toString(36))}},function(e,t,n){var r=n(78),i=n(63);e.exports=Object.keys||function(e){return r(e,i)}},function(e,t,n){var r=n(61),i=n(20);e.exports=function(e){return r(i(e))}},function(e,t,n){var r=n(20);e.exports=function(e){return Object(r(e))}},function(e,t,n){var r=n(29),i=n(27);n(81)("keys",function(){return function(e){return i(r(e))}})},function(e,t,n){"use strict";var r=n(5),i=n(32)(0),a=n(22)([].forEach,!0);r(r.P+r.F*!a,"Array",{forEach:function(e){return i(this,e,arguments[1])}})},function(e,t,n){var r=n(13),i=n(61),a=n(29),o=n(59),l=n(114);e.exports=function(e,t){var n=1==e,s=2==e,u=3==e,c=4==e,p=6==e,f=5==e||p,d=t||l;return function(t,l,v){for(var h,m,g=a(t),y=i(g),b=r(l,v,3),_=o(y.length),x=0,C=n?d(t,_):s?d(t,0):void 0;_>x;x++)if((f||x in y)&&(m=b(h=y[x],x,g),e))if(n)C[x]=m;else if(m)switch(e){case 3:return!0;case 5:return h;case 6:return x;case 2:C.push(h)}else if(c)return!1;return p?-1:u||c?c:C}}},function(e,t,n){"use strict";var r=n(5),i=n(32)(1);r(r.P+r.F*!n(22)([].map,!0),"Array",{map:function(e){return i(this,e,arguments[1])}})},function(e,t,n){n(66)("replace",2,function(e,t,n){return[function(r,i){"use strict";var a=e(this),o=null==r?void 0:r[t];return void 0!==o?o.call(r,a,i):n.call(String(a),r,i)},n]})},function(e,t,n){"use strict";var r=n(36),i=n.n(r);t.default=i.a},function(e,t,n){"use strict";var r=n(162).default,i=n(155).default;e.exports={components:{Layout:r,CarbonAds:i}}},function(e,t,n){},function(e,t,n){},function(e,t,n){},function(e,t,n){},function(e,t,n){},function(e,t,n){},function(e,t,n){},function(e,t,n){},function(e,t,n){},function(e,t,n){},function(e,t,n){},function(e,t,n){},function(e,t,n){},function(e,t,n){"use strict";n.r(t);var r=n(51),i=n.n(r);for(var a in r)"default"!==a&&function(e){n.d(t,e,function(){return r[e]})}(a);t.default=i.a},function(e,t,n){"use strict";window.BSANativeCallback=function(e){if(!e.ads.length){var t=document.createElement("script");t.src="//cdn.carbonads.com/carbon.js?zoneid=1673&serve=C6AILKT&placement=grapesjscom",t.setAttribute("id","_carbonads_js");var n=document.getElementById("native-carbon");n&&n.appendChild(t)}},e.exports={render:function(e){return e("div",{class:"carbon-ads",attrs:{id:"native-carbon"}})},mounted:function(){this.load()},watch:{$route:function(e,t){e.path!==t.path&&this.$el.querySelector("#carbonads")&&(this.$el.innerHTML="",this.load())}},methods:{initCarbon:function(){var e=window._bsa;void 0!==e&&e&&e.init("default","CK7I62QJ","placement:grapesjscomdocs",{target:"#native-carbon"})},load:function(){var e=this,t=document.createElement("script");t.src="//m.servedby-buysellads.com/monetization.js",t.onload=function(){return e.initCarbon()},this.$el.appendChild(t)}}}},function(e,t,n){},function(e,t,n){},function(e,t,n){},function(e,t,n){var r,i; + */var r=Object.freeze({});function i(e){return null==e}function a(e){return null!=e}function o(e){return!0===e}function l(e){return"string"==typeof e||"number"==typeof e||"symbol"==typeof e||"boolean"==typeof e}function s(e){return null!==e&&"object"==typeof e}var u=Object.prototype.toString;function c(e){return"[object Object]"===u.call(e)}function p(e){return"[object RegExp]"===u.call(e)}function f(e){var t=parseFloat(String(e));return t>=0&&Math.floor(t)===t&&isFinite(e)}function d(e){return null==e?"":"object"==typeof e?JSON.stringify(e,null,2):String(e)}function v(e){var t=parseFloat(e);return isNaN(t)?e:t}function h(e,t){for(var n=Object.create(null),r=e.split(","),i=0;i-1)return e.splice(n,1)}}var y=Object.prototype.hasOwnProperty;function b(e,t){return y.call(e,t)}function _(e){var t=Object.create(null);return function(n){return t[n]||(t[n]=e(n))}}var x=/-(\w)/g,C=_(function(e){return e.replace(x,function(e,t){return t?t.toUpperCase():""})}),w=_(function(e){return e.charAt(0).toUpperCase()+e.slice(1)}),k=/\B([A-Z])/g,E=_(function(e){return e.replace(k,"-$1").toLowerCase()});var S=Function.prototype.bind?function(e,t){return e.bind(t)}:function(e,t){function n(n){var r=arguments.length;return r?r>1?e.apply(t,arguments):e.call(t,n):e.call(t)}return n._length=e.length,n};function $(e,t){t=t||0;for(var n=e.length-t,r=new Array(n);n--;)r[n]=e[n+t];return r}function O(e,t){for(var n in t)e[n]=t[n];return e}function P(e){for(var t={},n=0;n0,X=W&&W.indexOf("edge/")>0,Y=(W&&W.indexOf("android"),W&&/iphone|ipad|ipod|ios/.test(W)||"ios"===G),Q=(W&&/chrome\/\d+/.test(W),{}.watch),Z=!1;if(z)try{var ee={};Object.defineProperty(ee,"passive",{get:function(){Z=!0}}),window.addEventListener("test-passive",null,ee)}catch(e){}var te=function(){return void 0===H&&(H=!z&&!V&&"undefined"!=typeof global&&"server"===global.process.env.VUE_ENV),H},ne=z&&window.__VUE_DEVTOOLS_GLOBAL_HOOK__;function re(e){return"function"==typeof e&&/native code/.test(e.toString())}var ie,ae="undefined"!=typeof Symbol&&re(Symbol)&&"undefined"!=typeof Reflect&&re(Reflect.ownKeys);ie="undefined"!=typeof Set&&re(Set)?Set:function(){function e(){this.set=Object.create(null)}return e.prototype.has=function(e){return!0===this.set[e]},e.prototype.add=function(e){this.set[e]=!0},e.prototype.clear=function(){this.set=Object.create(null)},e}();var oe=A,le=0,se=function(){this.id=le++,this.subs=[]};se.prototype.addSub=function(e){this.subs.push(e)},se.prototype.removeSub=function(e){g(this.subs,e)},se.prototype.depend=function(){se.target&&se.target.addDep(this)},se.prototype.notify=function(){for(var e=this.subs.slice(),t=0,n=e.length;t-1)if(a&&!b(i,"default"))o=!1;else if(""===o||o===E(e)){var s=Be(String,i.type);(s<0||l0&&(ut((u=e(u,(n||"")+"_"+s))[0])&&ut(p)&&(r[c]=he(p.text+u[0].text),u.shift()),r.push.apply(r,u)):l(u)?ut(p)?r[c]=he(p.text+u):""!==u&&r.push(he(u)):ut(u)&&ut(p)?r[c]=he(p.text+u.text):(o(t._isVList)&&a(u.tag)&&i(u.key)&&a(n)&&(u.key="__vlist"+n+"_"+s+"__"),r.push(u)));return r}(e):void 0}function ut(e){return a(e)&&a(e.text)&&!1===e.isComment}function ct(e,t){return(e.__esModule||ae&&"Module"===e[Symbol.toStringTag])&&(e=e.default),s(e)?t.extend(e):e}function pt(e){return e.isComment&&e.asyncFactory}function ft(e){if(Array.isArray(e))for(var t=0;tOt&&wt[n].id>e.id;)n--;wt.splice(n+1,0,e)}else wt.push(e);St||(St=!0,Ze(Pt))}}(this)},Tt.prototype.run=function(){if(this.active){var e=this.get();if(e!==this.value||s(e)||this.deep){var t=this.value;if(this.value=e,this.user)try{this.cb.call(this.vm,e,t)}catch(e){Fe(e,this.vm,'callback for watcher "'+this.expression+'"')}else this.cb.call(this.vm,e,t)}}},Tt.prototype.evaluate=function(){this.value=this.get(),this.dirty=!1},Tt.prototype.depend=function(){for(var e=this.deps.length;e--;)this.deps[e].depend()},Tt.prototype.teardown=function(){if(this.active){this.vm._isBeingDestroyed||g(this.vm._watchers,this);for(var e=this.deps.length;e--;)this.deps[e].removeSub(this);this.active=!1}};var jt={enumerable:!0,configurable:!0,get:A,set:A};function Lt(e,t,n){jt.get=function(){return this[t][n]},jt.set=function(e){this[t][n]=e},Object.defineProperty(e,n,jt)}function Mt(e){e._watchers=[];var t=e.$options;t.props&&function(e,t){var n=e.$options.propsData||{},r=e._props={},i=e.$options._propKeys=[];e.$parent&&xe(!1);var a=function(a){i.push(a);var o=De(a,t,n,e);Se(r,a,o),a in e||Lt(e,"_props",a)};for(var o in t)a(o);xe(!0)}(e,t.props),t.methods&&function(e,t){e.$options.props;for(var n in t)e[n]=null==t[n]?A:S(t[n],e)}(e,t.methods),t.data?function(e){var t=e.$options.data;c(t=e._data="function"==typeof t?function(e,t){ce();try{return e.call(t,t)}catch(e){return Fe(e,t,"data()"),{}}finally{pe()}}(t,e):t||{})||(t={});var n=Object.keys(t),r=e.$options.props,i=(e.$options.methods,n.length);for(;i--;){var a=n[i];0,r&&b(r,a)||(o=void 0,36!==(o=(a+"").charCodeAt(0))&&95!==o&&Lt(e,"_data",a))}var o;Ee(t,!0)}(e):Ee(e._data={},!0),t.computed&&function(e,t){var n=e._computedWatchers=Object.create(null),r=te();for(var i in t){var a=t[i],o="function"==typeof a?a:a.get;0,r||(n[i]=new Tt(e,o||A,A,It)),i in e||Rt(e,i,a)}}(e,t.computed),t.watch&&t.watch!==Q&&function(e,t){for(var n in t){var r=t[n];if(Array.isArray(r))for(var i=0;i=0||n.indexOf(e[i])<0)&&r.push(e[i]);return r}return e}function pn(e){this._init(e)}function fn(e){e.cid=0;var t=1;e.extend=function(e){e=e||{};var n=this,r=n.cid,i=e._Ctor||(e._Ctor={});if(i[r])return i[r];var a=e.name||n.options.name;var o=function(e){this._init(e)};return(o.prototype=Object.create(n.prototype)).constructor=o,o.cid=t++,o.options=Ie(n.options,e),o.super=n,o.options.props&&function(e){var t=e.options.props;for(var n in t)Lt(e.prototype,"_props",n)}(o),o.options.computed&&function(e){var t=e.options.computed;for(var n in t)Rt(e.prototype,n,t[n])}(o),o.extend=n.extend,o.mixin=n.mixin,o.use=n.use,D.forEach(function(e){o[e]=n[e]}),a&&(o.options.components[a]=o),o.superOptions=n.options,o.extendOptions=e,o.sealedOptions=O({},o.options),i[r]=o,o}}function dn(e){return e&&(e.Ctor.options.name||e.tag)}function vn(e,t){return Array.isArray(e)?e.indexOf(t)>-1:"string"==typeof e?e.split(",").indexOf(t)>-1:!!p(e)&&e.test(t)}function hn(e,t){var n=e.cache,r=e.keys,i=e._vnode;for(var a in n){var o=n[a];if(o){var l=dn(o.componentOptions);l&&!t(l)&&mn(n,a,r,i)}}}function mn(e,t,n,r){var i=e[t];!i||r&&i.tag===r.tag||i.componentInstance.$destroy(),e[t]=null,g(n,t)}!function(e){e.prototype._init=function(e){var t=this;t._uid=sn++,t._isVue=!0,e&&e._isComponent?function(e,t){var n=e.$options=Object.create(e.constructor.options),r=t._parentVnode;n.parent=t.parent,n._parentVnode=r,n._parentElm=t._parentElm,n._refElm=t._refElm;var i=r.componentOptions;n.propsData=i.propsData,n._parentListeners=i.listeners,n._renderChildren=i.children,n._componentTag=i.tag,t.render&&(n.render=t.render,n.staticRenderFns=t.staticRenderFns)}(t,e):t.$options=Ie(un(t.constructor),e||{},t),t._renderProxy=t,t._self=t,function(e){var t=e.$options,n=t.parent;if(n&&!t.abstract){for(;n.$options.abstract&&n.$parent;)n=n.$parent;n.$children.push(e)}e.$parent=n,e.$root=n?n.$root:e,e.$children=[],e.$refs={},e._watcher=null,e._inactive=null,e._directInactive=!1,e._isMounted=!1,e._isDestroyed=!1,e._isBeingDestroyed=!1}(t),function(e){e._events=Object.create(null),e._hasHookEvent=!1;var t=e.$options._parentListeners;t&&ht(e,t)}(t),function(e){e._vnode=null,e._staticTrees=null;var t=e.$options,n=e.$vnode=t._parentVnode,i=n&&n.context;e.$slots=mt(t._renderChildren,i),e.$scopedSlots=r,e._c=function(t,n,r,i){return ln(e,t,n,r,i,!1)},e.$createElement=function(t,n,r,i){return ln(e,t,n,r,i,!0)};var a=n&&n.data;Se(e,"$attrs",a&&a.attrs||r,null,!0),Se(e,"$listeners",t._parentListeners||r,null,!0)}(t),Ct(t,"beforeCreate"),function(e){var t=Nt(e.$options.inject,e);t&&(xe(!1),Object.keys(t).forEach(function(n){Se(e,n,t[n])}),xe(!0))}(t),Mt(t),function(e){var t=e.$options.provide;t&&(e._provided="function"==typeof t?t.call(e):t)}(t),Ct(t,"created"),t.$options.el&&t.$mount(t.$options.el)}}(pn),function(e){var t={get:function(){return this._data}},n={get:function(){return this._props}};Object.defineProperty(e.prototype,"$data",t),Object.defineProperty(e.prototype,"$props",n),e.prototype.$set=$e,e.prototype.$delete=Oe,e.prototype.$watch=function(e,t,n){if(c(t))return Ut(this,e,t,n);(n=n||{}).user=!0;var r=new Tt(this,e,t,n);return n.immediate&&t.call(this,r.value),function(){r.teardown()}}}(pn),function(e){var t=/^hook:/;e.prototype.$on=function(e,n){if(Array.isArray(e))for(var r=0,i=e.length;r1?$(t):t;for(var n=$(arguments,1),r=0,i=t.length;rparseInt(this.max)&&mn(o,l[0],l,this._vnode)),t.data.keepAlive=!0}return t||e&&e[0]}}};!function(e){var t={get:function(){return N}};Object.defineProperty(e,"config",t),e.util={warn:oe,extend:O,mergeOptions:Ie,defineReactive:Se},e.set=$e,e.delete=Oe,e.nextTick=Ze,e.options=Object.create(null),D.forEach(function(t){e.options[t+"s"]=Object.create(null)}),e.options._base=e,O(e.options.components,yn),function(e){e.use=function(e){var t=this._installedPlugins||(this._installedPlugins=[]);if(t.indexOf(e)>-1)return this;var n=$(arguments,1);return n.unshift(this),"function"==typeof e.install?e.install.apply(e,n):"function"==typeof e&&e.apply(null,n),t.push(e),this}}(e),function(e){e.mixin=function(e){return this.options=Ie(this.options,e),this}}(e),fn(e),function(e){D.forEach(function(t){e[t]=function(e,n){return n?("component"===t&&c(n)&&(n.name=n.name||e,n=this.options._base.extend(n)),"directive"===t&&"function"==typeof n&&(n={bind:n,update:n}),this.options[t+"s"][e]=n,n):this.options[t+"s"][e]}})}(e)}(pn),Object.defineProperty(pn.prototype,"$isServer",{get:te}),Object.defineProperty(pn.prototype,"$ssrContext",{get:function(){return this.$vnode&&this.$vnode.ssrContext}}),Object.defineProperty(pn,"FunctionalRenderContext",{value:Qt}),pn.version="2.5.17";var bn=h("style,class"),_n=h("input,textarea,option,select,progress"),xn=h("contenteditable,draggable,spellcheck"),Cn=h("allowfullscreen,async,autofocus,autoplay,checked,compact,controls,declare,default,defaultchecked,defaultmuted,defaultselected,defer,disabled,enabled,formnovalidate,hidden,indeterminate,inert,ismap,itemscope,loop,multiple,muted,nohref,noresize,noshade,novalidate,nowrap,open,pauseonexit,readonly,required,reversed,scoped,seamless,selected,sortable,translate,truespeed,typemustmatch,visible"),wn="http://www.w3.org/1999/xlink",kn=function(e){return":"===e.charAt(5)&&"xlink"===e.slice(0,5)},En=function(e){return kn(e)?e.slice(6,e.length):""},Sn=function(e){return null==e||!1===e};function $n(e){for(var t=e.data,n=e,r=e;a(r.componentInstance);)(r=r.componentInstance._vnode)&&r.data&&(t=On(r.data,t));for(;a(n=n.parent);)n&&n.data&&(t=On(t,n.data));return function(e,t){if(a(e)||a(t))return Pn(e,An(t));return""}(t.staticClass,t.class)}function On(e,t){return{staticClass:Pn(e.staticClass,t.staticClass),class:a(e.class)?[e.class,t.class]:t.class}}function Pn(e,t){return e?t?e+" "+t:e:t||""}function An(e){return Array.isArray(e)?function(e){for(var t,n="",r=0,i=e.length;r-1?Zn(e,t,n):Cn(t)?Sn(n)?e.removeAttribute(t):(n="allowfullscreen"===t&&"EMBED"===e.tagName?"true":t,e.setAttribute(t,n)):xn(t)?e.setAttribute(t,Sn(n)||"false"===n?"false":"true"):kn(t)?Sn(n)?e.removeAttributeNS(wn,En(t)):e.setAttributeNS(wn,t,n):Zn(e,t,n)}function Zn(e,t,n){if(Sn(n))e.removeAttribute(t);else{if(K&&!J&&"TEXTAREA"===e.tagName&&"placeholder"===t&&!e.__ieph){var r=function(t){t.stopImmediatePropagation(),e.removeEventListener("input",r)};e.addEventListener("input",r),e.__ieph=!0}e.setAttribute(t,n)}}var er={create:Yn,update:Yn};function tr(e,t){var n=t.elm,r=t.data,o=e.data;if(!(i(r.staticClass)&&i(r.class)&&(i(o)||i(o.staticClass)&&i(o.class)))){var l=$n(t),s=n._transitionClasses;a(s)&&(l=Pn(l,An(s))),l!==n._prevClass&&(n.setAttribute("class",l),n._prevClass=l)}}var nr,rr={create:tr,update:tr},ir="__r",ar="__c";function or(e,t,n,r,i){var a;t=(a=t)._withTask||(a._withTask=function(){Je=!0;var e=a.apply(null,arguments);return Je=!1,e}),n&&(t=function(e,t,n){var r=nr;return function i(){null!==e.apply(null,arguments)&&lr(t,i,n,r)}}(t,e,r)),nr.addEventListener(e,t,Z?{capture:r,passive:i}:r)}function lr(e,t,n,r){(r||nr).removeEventListener(e,t._withTask||t,n)}function sr(e,t){if(!i(e.data.on)||!i(t.data.on)){var n=t.data.on||{},r=e.data.on||{};nr=t.elm,function(e){if(a(e[ir])){var t=K?"change":"input";e[t]=[].concat(e[ir],e[t]||[]),delete e[ir]}a(e[ar])&&(e.change=[].concat(e[ar],e.change||[]),delete e[ar])}(n),at(n,r,or,lr,t.context),nr=void 0}}var ur={create:sr,update:sr};function cr(e,t){if(!i(e.data.domProps)||!i(t.data.domProps)){var n,r,o=t.elm,l=e.data.domProps||{},s=t.data.domProps||{};for(n in a(s.__ob__)&&(s=t.data.domProps=O({},s)),l)i(s[n])&&(o[n]="");for(n in s){if(r=s[n],"textContent"===n||"innerHTML"===n){if(t.children&&(t.children.length=0),r===l[n])continue;1===o.childNodes.length&&o.removeChild(o.childNodes[0])}if("value"===n){o._value=r;var u=i(r)?"":String(r);pr(o,u)&&(o.value=u)}else o[n]=r}}}function pr(e,t){return!e.composing&&("OPTION"===e.tagName||function(e,t){var n=!0;try{n=document.activeElement!==e}catch(e){}return n&&e.value!==t}(e,t)||function(e,t){var n=e.value,r=e._vModifiers;if(a(r)){if(r.lazy)return!1;if(r.number)return v(n)!==v(t);if(r.trim)return n.trim()!==t.trim()}return n!==t}(e,t))}var fr={create:cr,update:cr},dr=_(function(e){var t={},n=/:(.+)/;return e.split(/;(?![^(]*\))/g).forEach(function(e){if(e){var r=e.split(n);r.length>1&&(t[r[0].trim()]=r[1].trim())}}),t});function vr(e){var t=hr(e.style);return e.staticStyle?O(e.staticStyle,t):t}function hr(e){return Array.isArray(e)?P(e):"string"==typeof e?dr(e):e}var mr,gr=/^--/,yr=/\s*!important$/,br=function(e,t,n){if(gr.test(t))e.style.setProperty(t,n);else if(yr.test(n))e.style.setProperty(t,n.replace(yr,""),"important");else{var r=xr(t);if(Array.isArray(n))for(var i=0,a=n.length;i-1?t.split(/\s+/).forEach(function(t){return e.classList.add(t)}):e.classList.add(t);else{var n=" "+(e.getAttribute("class")||"")+" ";n.indexOf(" "+t+" ")<0&&e.setAttribute("class",(n+t).trim())}}function Er(e,t){if(t&&(t=t.trim()))if(e.classList)t.indexOf(" ")>-1?t.split(/\s+/).forEach(function(t){return e.classList.remove(t)}):e.classList.remove(t),e.classList.length||e.removeAttribute("class");else{for(var n=" "+(e.getAttribute("class")||"")+" ",r=" "+t+" ";n.indexOf(r)>=0;)n=n.replace(r," ");(n=n.trim())?e.setAttribute("class",n):e.removeAttribute("class")}}function Sr(e){if(e){if("object"==typeof e){var t={};return!1!==e.css&&O(t,$r(e.name||"v")),O(t,e),t}return"string"==typeof e?$r(e):void 0}}var $r=_(function(e){return{enterClass:e+"-enter",enterToClass:e+"-enter-to",enterActiveClass:e+"-enter-active",leaveClass:e+"-leave",leaveToClass:e+"-leave-to",leaveActiveClass:e+"-leave-active"}}),Or=z&&!J,Pr="transition",Ar="animation",Tr="transition",jr="transitionend",Lr="animation",Mr="animationend";Or&&(void 0===window.ontransitionend&&void 0!==window.onwebkittransitionend&&(Tr="WebkitTransition",jr="webkitTransitionEnd"),void 0===window.onanimationend&&void 0!==window.onwebkitanimationend&&(Lr="WebkitAnimation",Mr="webkitAnimationEnd"));var Ir=z?window.requestAnimationFrame?window.requestAnimationFrame.bind(window):setTimeout:function(e){return e()};function Rr(e){Ir(function(){Ir(e)})}function Dr(e,t){var n=e._transitionClasses||(e._transitionClasses=[]);n.indexOf(t)<0&&(n.push(t),kr(e,t))}function Ur(e,t){e._transitionClasses&&g(e._transitionClasses,t),Er(e,t)}function Nr(e,t,n){var r=Fr(e,t),i=r.type,a=r.timeout,o=r.propCount;if(!i)return n();var l=i===Pr?jr:Mr,s=0,u=function(){e.removeEventListener(l,c),n()},c=function(t){t.target===e&&++s>=o&&u()};setTimeout(function(){s0&&(n=Pr,c=o,p=a.length):t===Ar?u>0&&(n=Ar,c=u,p=s.length):p=(n=(c=Math.max(o,u))>0?o>u?Pr:Ar:null)?n===Pr?a.length:s.length:0,{type:n,timeout:c,propCount:p,hasTransform:n===Pr&&Br.test(r[Tr+"Property"])}}function Hr(e,t){for(;e.length1}function Kr(e,t){!0!==t.data.show&&zr(t)}var Jr=function(e){var t,n,r={},s=e.modules,u=e.nodeOps;for(t=0;tv?b(e,i(n[g+1])?null:n[g+1].elm,n,d,g,r):d>g&&x(0,t,f,v)}(s,d,v,n,l):a(v)?(a(e.text)&&u.setTextContent(s,""),b(s,null,v,0,v.length-1,n)):a(d)?x(0,d,0,d.length-1):a(e.text)&&u.setTextContent(s,""):e.text!==t.text&&u.setTextContent(s,t.text),a(f)&&a(c=f.hook)&&a(c=c.postpatch)&&c(e,t)}}}function E(e,t,n){if(o(n)&&a(e.parent))e.parent.data.pendingInsert=t;else for(var r=0;r-1,o.selected!==a&&(o.selected=a);else if(L(ei(o),r))return void(e.selectedIndex!==l&&(e.selectedIndex=l));i||(e.selectedIndex=-1)}}function Zr(e,t){return t.every(function(t){return!L(t,e)})}function ei(e){return"_value"in e?e._value:e.value}function ti(e){e.target.composing=!0}function ni(e){e.target.composing&&(e.target.composing=!1,ri(e.target,"input"))}function ri(e,t){var n=document.createEvent("HTMLEvents");n.initEvent(t,!0,!0),e.dispatchEvent(n)}function ii(e){return!e.componentInstance||e.data&&e.data.transition?e:ii(e.componentInstance._vnode)}var ai={model:Xr,show:{bind:function(e,t,n){var r=t.value,i=(n=ii(n)).data&&n.data.transition,a=e.__vOriginalDisplay="none"===e.style.display?"":e.style.display;r&&i?(n.data.show=!0,zr(n,function(){e.style.display=a})):e.style.display=r?a:"none"},update:function(e,t,n){var r=t.value;!r!=!t.oldValue&&((n=ii(n)).data&&n.data.transition?(n.data.show=!0,r?zr(n,function(){e.style.display=e.__vOriginalDisplay}):Vr(n,function(){e.style.display="none"})):e.style.display=r?e.__vOriginalDisplay:"none")},unbind:function(e,t,n,r,i){i||(e.style.display=e.__vOriginalDisplay)}}},oi={name:String,appear:Boolean,css:Boolean,mode:String,type:String,enterClass:String,leaveClass:String,enterToClass:String,leaveToClass:String,enterActiveClass:String,leaveActiveClass:String,appearClass:String,appearActiveClass:String,appearToClass:String,duration:[Number,String,Object]};function li(e){var t=e&&e.componentOptions;return t&&t.Ctor.options.abstract?li(ft(t.children)):e}function si(e){var t={},n=e.$options;for(var r in n.propsData)t[r]=e[r];var i=n._parentListeners;for(var a in i)t[C(a)]=i[a];return t}function ui(e,t){if(/\d-keep-alive$/.test(t.tag))return e("keep-alive",{props:t.componentOptions.propsData})}var ci={name:"transition",props:oi,abstract:!0,render:function(e){var t=this,n=this.$slots.default;if(n&&(n=n.filter(function(e){return e.tag||pt(e)})).length){0;var r=this.mode;0;var i=n[0];if(function(e){for(;e=e.parent;)if(e.data.transition)return!0}(this.$vnode))return i;var a=li(i);if(!a)return i;if(this._leaving)return ui(e,i);var o="__transition-"+this._uid+"-";a.key=null==a.key?a.isComment?o+"comment":o+a.tag:l(a.key)?0===String(a.key).indexOf(o)?a.key:o+a.key:a.key;var s=(a.data||(a.data={})).transition=si(this),u=this._vnode,c=li(u);if(a.data.directives&&a.data.directives.some(function(e){return"show"===e.name})&&(a.data.show=!0),c&&c.data&&!function(e,t){return t.key===e.key&&t.tag===e.tag}(a,c)&&!pt(c)&&(!c.componentInstance||!c.componentInstance._vnode.isComment)){var p=c.data.transition=O({},s);if("out-in"===r)return this._leaving=!0,ot(p,"afterLeave",function(){t._leaving=!1,t.$forceUpdate()}),ui(e,i);if("in-out"===r){if(pt(a))return u;var f,d=function(){f()};ot(s,"afterEnter",d),ot(s,"enterCancelled",d),ot(p,"delayLeave",function(e){f=e})}}return i}}},pi=O({tag:String,moveClass:String},oi);function fi(e){e.elm._moveCb&&e.elm._moveCb(),e.elm._enterCb&&e.elm._enterCb()}function di(e){e.data.newPos=e.elm.getBoundingClientRect()}function vi(e){var t=e.data.pos,n=e.data.newPos,r=t.left-n.left,i=t.top-n.top;if(r||i){e.data.moved=!0;var a=e.elm.style;a.transform=a.WebkitTransform="translate("+r+"px,"+i+"px)",a.transitionDuration="0s"}}delete pi.mode;var hi={Transition:ci,TransitionGroup:{props:pi,render:function(e){for(var t=this.tag||this.$vnode.data.tag||"span",n=Object.create(null),r=this.prevChildren=this.children,i=this.$slots.default||[],a=this.children=[],o=si(this),l=0;l-1?In[e]=t.constructor===window.HTMLUnknownElement||t.constructor===window.HTMLElement:In[e]=/HTMLUnknownElement/.test(t.toString())},O(pn.options.directives,ai),O(pn.options.components,hi),pn.prototype.__patch__=z?Jr:A,pn.prototype.$mount=function(e,t){return function(e,t,n){var r;return e.$el=t,e.$options.render||(e.$options.render=ve),Ct(e,"beforeMount"),r=function(){e._update(e._render(),n)},new Tt(e,r,A,null,!0),n=!1,null==e.$vnode&&(e._isMounted=!0,Ct(e,"mounted")),e}(this,e=e&&z?function(e){if("string"==typeof e){var t=document.querySelector(e);return t||document.createElement("div")}return e}(e):void 0,t)},z&&setTimeout(function(){N.devtools&&ne&&ne.emit("init",pn)},0),t.a=pn},function(e,t,n){"use strict";function r(e,t,n,r,i,a,o,l){var s,u="function"==typeof e?e.options:e;if(t&&(u.render=t,u.staticRenderFns=n,u._compiled=!0),r&&(u.functional=!0),a&&(u._scopeId="data-v-"+a),o?(s=function(e){(e=e||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext)||"undefined"==typeof __VUE_SSR_CONTEXT__||(e=__VUE_SSR_CONTEXT__),i&&i.call(this,e),e&&e._registeredComponents&&e._registeredComponents.add(o)},u._ssrRegister=s):i&&(s=l?function(){i.call(this,this.$root.$options.shadowRoot)}:i),s)if(u.functional){u._injectStyles=s;var c=u.render;u.render=function(e,t){return s.call(t),c(e,t)}}else{var p=u.beforeCreate;u.beforeCreate=p?[].concat(p,s):[s]}return{exports:e,options:u}}n.d(t,"a",function(){return r})},function(e,t,n){"use strict";var r=n(67),i=n(35),a=n(1),o=Object(a.a)(i.default,r.a,r.b,!1,null,null,null);o.options.__file="Layout.vue",t.default=o.exports},function(e,t,n){var r=n(70)("wks"),i=n(26),a=n(4).Symbol,o="function"==typeof a;(e.exports=function(e){return r[e]||(r[e]=o&&a[e]||(o?a:i)("Symbol."+e))}).store=r},function(e,t){var n=e.exports="undefined"!=typeof window&&window.Math==Math?window:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")();"number"==typeof __g&&(__g=n)},function(e,t,n){var r=n(4),i=n(15),a=n(11),o=n(16),l=n(13),s=function(e,t,n){var u,c,p,f,d=e&s.F,v=e&s.G,h=e&s.S,m=e&s.P,g=e&s.B,y=v?r:h?r[t]||(r[t]={}):(r[t]||{}).prototype,b=v?i:i[t]||(i[t]={}),_=b.prototype||(b.prototype={});for(u in v&&(n=t),n)p=((c=!d&&y&&void 0!==y[u])?y:n)[u],f=g&&c?l(p,r):m&&"function"==typeof p?l(Function.call,p):p,y&&o(y,u,p,e&s.U),b[u]!=p&&a(b,u,f),m&&_[u]!=p&&(_[u]=p)};r.core=i,s.F=1,s.G=2,s.S=4,s.P=8,s.B=16,s.W=32,s.U=64,s.R=128,e.exports=s},function(e,t){e.exports=function(e){return"object"==typeof e?null!==e:"function"==typeof e}},function(e,t){e.exports=function(e){try{return!!e()}catch(e){return!0}}},function(e,t,n){var r=n(6);e.exports=function(e){if(!r(e))throw TypeError(e+" is not an object!");return e}},function(e,t,n){e.exports=!n(7)(function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a})},function(e,t,n){"use strict";function r(e){return function(e){if(Array.isArray(e)){for(var t=0,n=new Array(e.length);t=e.length?(this._t=void 0,i(1)):i(0,"keys"==t?n:"values"==t?e[n]:[n,e[n]])},"values"),a.Arguments=a.Array,r("keys"),r("values"),r("entries")},function(e,t,n){"use strict";var r=n(7);e.exports=function(e,t){return!!e&&r(function(){t?e.call(null,function(){},1):e.call(null)})}},function(e,t,n){"use strict";var r=n(5),i=n(32)(3);r(r.P+r.F*!n(22)([].some,!0),"Array",{some:function(e){return i(this,e,arguments[1])}})},function(e,t,n){var r=n(5);r(r.S+r.F,"Object",{assign:n(102)})},function(e,t){e.exports=function(e){if("function"!=typeof e)throw TypeError(e+" is not a function!");return e}},function(e,t){var n=0,r=Math.random();e.exports=function(e){return"Symbol(".concat(void 0===e?"":e,")_",(++n+r).toString(36))}},function(e,t,n){var r=n(78),i=n(63);e.exports=Object.keys||function(e){return r(e,i)}},function(e,t,n){var r=n(61),i=n(20);e.exports=function(e){return r(i(e))}},function(e,t,n){var r=n(20);e.exports=function(e){return Object(r(e))}},function(e,t,n){var r=n(29),i=n(27);n(81)("keys",function(){return function(e){return i(r(e))}})},function(e,t,n){"use strict";var r=n(5),i=n(32)(0),a=n(22)([].forEach,!0);r(r.P+r.F*!a,"Array",{forEach:function(e){return i(this,e,arguments[1])}})},function(e,t,n){var r=n(13),i=n(61),a=n(29),o=n(59),l=n(114);e.exports=function(e,t){var n=1==e,s=2==e,u=3==e,c=4==e,p=6==e,f=5==e||p,d=t||l;return function(t,l,v){for(var h,m,g=a(t),y=i(g),b=r(l,v,3),_=o(y.length),x=0,C=n?d(t,_):s?d(t,0):void 0;_>x;x++)if((f||x in y)&&(m=b(h=y[x],x,g),e))if(n)C[x]=m;else if(m)switch(e){case 3:return!0;case 5:return h;case 6:return x;case 2:C.push(h)}else if(c)return!1;return p?-1:u||c?c:C}}},function(e,t,n){"use strict";var r=n(5),i=n(32)(1);r(r.P+r.F*!n(22)([].map,!0),"Array",{map:function(e){return i(this,e,arguments[1])}})},function(e,t,n){n(66)("replace",2,function(e,t,n){return[function(r,i){"use strict";var a=e(this),o=null==r?void 0:r[t];return void 0!==o?o.call(r,a,i):n.call(String(a),r,i)},n]})},function(e,t,n){"use strict";var r=n(36),i=n.n(r);t.default=i.a},function(e,t,n){"use strict";var r=n(162).default,i=n(155).default;e.exports={components:{Layout:r,CarbonAds:i}}},function(e,t,n){},function(e,t,n){},function(e,t,n){},function(e,t,n){},function(e,t,n){},function(e,t,n){},function(e,t,n){},function(e,t,n){},function(e,t,n){},function(e,t,n){},function(e,t,n){},function(e,t,n){},function(e,t,n){},function(e,t,n){"use strict";n.r(t);var r=n(51),i=n.n(r);for(var a in r)"default"!==a&&function(e){n.d(t,e,function(){return r[e]})}(a);t.default=i.a},function(e,t,n){"use strict";e.exports={render:function(e){return e("div",{class:"carbon-ads",attrs:{id:"native-carbon"}})},mounted:function(){this.load()},watch:{$route:function(e,t){e.path!==t.path&&this.$el.querySelector("#carbonads")&&(this.$el.innerHTML="",this.load())}},methods:{initCarbon:function(){var e=window._bsa;void 0!==e&&e&&e.init("default","CK7I62QJ","placement:grapesjscomdocs",{target:"#native-carbon"})},load:function(){var e=this,t=document.createElement("script");t.src="//m.servedby-buysellads.com/monetization.js",t.onload=function(){return e.initCarbon()},this.$el.appendChild(t)}}}},function(e,t,n){},function(e,t,n){},function(e,t,n){},function(e,t,n){var r,i; /* NProgress, (c) 2013, 2014 Rico Sta. Cruz - http://ricostacruz.com/nprogress - * @license MIT */void 0===(i="function"==typeof(r=function(){var e,t,n={version:"0.2.0"},r=n.settings={minimum:.08,easing:"ease",positionUsing:"",speed:200,trickle:!0,trickleRate:.02,trickleSpeed:800,showSpinner:!0,barSelector:'[role="bar"]',spinnerSelector:'[role="spinner"]',parent:"body",template:'
'};function i(e,t,n){return en?n:e}function a(e){return 100*(-1+e)}n.configure=function(e){var t,n;for(t in e)void 0!==(n=e[t])&&e.hasOwnProperty(t)&&(r[t]=n);return this},n.status=null,n.set=function(e){var t=n.isStarted();e=i(e,r.minimum,1),n.status=1===e?null:e;var s=n.render(!t),u=s.querySelector(r.barSelector),c=r.speed,p=r.easing;return s.offsetWidth,o(function(t){""===r.positionUsing&&(r.positionUsing=n.getPositioningCSS()),l(u,function(e,t,n){var i;return(i="translate3d"===r.positionUsing?{transform:"translate3d("+a(e)+"%,0,0)"}:"translate"===r.positionUsing?{transform:"translate("+a(e)+"%,0)"}:{"margin-left":a(e)+"%"}).transition="all "+t+"ms "+n,i}(e,c,p)),1===e?(l(s,{transition:"none",opacity:1}),s.offsetWidth,setTimeout(function(){l(s,{transition:"all "+c+"ms linear",opacity:0}),setTimeout(function(){n.remove(),t()},c)},c)):setTimeout(t,c)}),this},n.isStarted=function(){return"number"==typeof n.status},n.start=function(){n.status||n.set(0);var e=function(){setTimeout(function(){n.status&&(n.trickle(),e())},r.trickleSpeed)};return r.trickle&&e(),this},n.done=function(e){return e||n.status?n.inc(.3+.5*Math.random()).set(1):this},n.inc=function(e){var t=n.status;return t?("number"!=typeof e&&(e=(1-t)*i(Math.random()*t,.1,.95)),t=i(t+e,0,.994),n.set(t)):n.start()},n.trickle=function(){return n.inc(Math.random()*r.trickleRate)},e=0,t=0,n.promise=function(r){return r&&"resolved"!==r.state()?(0===t&&n.start(),e++,t++,r.always(function(){0==--t?(e=0,n.done()):n.set((e-t)/e)}),this):this},n.render=function(e){if(n.isRendered())return document.getElementById("nprogress");u(document.documentElement,"nprogress-busy");var t=document.createElement("div");t.id="nprogress",t.innerHTML=r.template;var i,o=t.querySelector(r.barSelector),s=e?"-100":a(n.status||0),c=document.querySelector(r.parent);return l(o,{transition:"all 0 linear",transform:"translate3d("+s+"%,0,0)"}),r.showSpinner||(i=t.querySelector(r.spinnerSelector))&&f(i),c!=document.body&&u(c,"nprogress-custom-parent"),c.appendChild(t),t},n.remove=function(){c(document.documentElement,"nprogress-busy"),c(document.querySelector(r.parent),"nprogress-custom-parent");var e=document.getElementById("nprogress");e&&f(e)},n.isRendered=function(){return!!document.getElementById("nprogress")},n.getPositioningCSS=function(){var e=document.body.style,t="WebkitTransform"in e?"Webkit":"MozTransform"in e?"Moz":"msTransform"in e?"ms":"OTransform"in e?"O":"";return t+"Perspective"in e?"translate3d":t+"Transform"in e?"translate":"margin"};var o=function(){var e=[];function t(){var n=e.shift();n&&n(t)}return function(n){e.push(n),1==e.length&&t()}}(),l=function(){var e=["Webkit","O","Moz","ms"],t={};function n(n){return n=n.replace(/^-ms-/,"ms-").replace(/-([\da-z])/gi,function(e,t){return t.toUpperCase()}),t[n]||(t[n]=function(t){var n=document.body.style;if(t in n)return t;for(var r,i=e.length,a=t.charAt(0).toUpperCase()+t.slice(1);i--;)if((r=e[i]+a)in n)return r;return t}(n))}function r(e,t,r){t=n(t),e.style[t]=r}return function(e,t){var n,i,a=arguments;if(2==a.length)for(n in t)void 0!==(i=t[n])&&t.hasOwnProperty(n)&&r(e,n,i);else r(e,a[1],a[2])}}();function s(e,t){var n="string"==typeof e?e:p(e);return n.indexOf(" "+t+" ")>=0}function u(e,t){var n=p(e),r=n+t;s(n,t)||(e.className=r.substring(1))}function c(e,t){var n,r=p(e);s(e,t)&&(n=r.replace(" "+t+" "," "),e.className=n.substring(1,n.length-1))}function p(e){return(" "+(e.className||"")+" ").replace(/\s+/gi," ")}function f(e){e&&e.parentNode&&e.parentNode.removeChild(e)}return n})?r.call(t,n,t,e):r)||(e.exports=i)},function(e,t){e.exports=!1},function(e,t,n){var r=n(6),i=n(4).document,a=r(i)&&r(i.createElement);e.exports=function(e){return a?i.createElement(e):{}}},function(e,t){e.exports=function(e,t){return{enumerable:!(1&e),configurable:!(2&e),writable:!(4&e),value:t}}},function(e,t,n){var r=n(73),i=Math.min;e.exports=function(e){return e>0?i(r(e),9007199254740991):0}},function(e,t,n){var r=n(12).f,i=n(17),a=n(3)("toStringTag");e.exports=function(e,t,n){e&&!i(e=n?e:e.prototype,a)&&r(e,a,{configurable:!0,value:t})}},function(e,t,n){var r=n(14);e.exports=Object("z").propertyIsEnumerable(0)?Object:function(e){return"String"==r(e)?e.split(""):Object(e)}},function(e,t,n){var r=n(70)("keys"),i=n(26);e.exports=function(e){return r[e]||(r[e]=i(e))}},function(e,t){e.exports="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(",")},function(e,t,n){"use strict";var r=n(5),i=n(32)(2);r(r.P+r.F*!n(22)([].filter,!0),"Array",{filter:function(e){return i(this,e,arguments[1])}})},function(e,t,n){"use strict";var r=n(5),i=n(79)(!1),a=[].indexOf,o=!!a&&1/[1].indexOf(1,-0)<0;r(r.P+r.F*(o||!n(22)(a)),"Array",{indexOf:function(e){return o?a.apply(this,arguments)||0:i(this,e,arguments[1])}})},function(e,t,n){"use strict";var r=n(11),i=n(16),a=n(7),o=n(20),l=n(3);e.exports=function(e,t,n){var s=l(e),u=n(o,s,""[e]),c=u[0],p=u[1];a(function(){var t={};return t[s]=function(){return 7},7!=""[e](t)})&&(i(String.prototype,e,c),r(RegExp.prototype,s,2==t?function(e,t){return p.call(e,this,t)}:function(e){return p.call(e,this)}))}},function(e,t,n){"use strict";var r=function(){var e=this.$createElement,t=this._self._c||e;return t("Layout",[t("CarbonAds",{attrs:{slot:"sidebar-top"},slot:"sidebar-top"})],1)},i=[];n.d(t,"a",function(){return r}),n.d(t,"b",function(){return i})},function(e,t,n){"use strict";var r,i,a,o,l=n(56),s=n(4),u=n(13),c=n(69),p=n(5),f=n(6),d=n(25),v=n(89),h=n(90),m=n(94),g=n(74).set,y=n(96)(),b=n(76),_=n(97),x=n(98),C=n(99),w=s.TypeError,k=s.process,E=k&&k.versions,S=E&&E.v8||"",$=s.Promise,O="process"==c(k),P=function(){},A=i=b.f,T=!!function(){try{var e=$.resolve(1),t=(e.constructor={})[n(3)("species")]=function(e){e(P,P)};return(O||"function"==typeof PromiseRejectionEvent)&&e.then(P)instanceof t&&0!==S.indexOf("6.6")&&-1===x.indexOf("Chrome/66")}catch(e){}}(),j=function(e){var t;return!(!f(e)||"function"!=typeof(t=e.then))&&t},L=function(e,t){if(!e._n){e._n=!0;var n=e._c;y(function(){for(var r=e._v,i=1==e._s,a=0,o=function(t){var n,a,o,l=i?t.ok:t.fail,s=t.resolve,u=t.reject,c=t.domain;try{l?(i||(2==e._h&&R(e),e._h=1),!0===l?n=r:(c&&c.enter(),n=l(r),c&&(c.exit(),o=!0)),n===t.promise?u(w("Promise-chain cycle")):(a=j(n))?a.call(n,s,u):s(n)):u(r)}catch(e){c&&!o&&c.exit(),u(e)}};n.length>a;)o(n[a++]);e._c=[],e._n=!1,t&&!e._h&&M(e)})}},M=function(e){g.call(s,function(){var t,n,r,i=e._v,a=I(e);if(a&&(t=_(function(){O?k.emit("unhandledRejection",i,e):(n=s.onunhandledrejection)?n({promise:e,reason:i}):(r=s.console)&&r.error&&r.error("Unhandled promise rejection",i)}),e._h=O||I(e)?2:1),e._a=void 0,a&&t.e)throw t.v})},I=function(e){return 1!==e._h&&0===(e._a||e._c).length},R=function(e){g.call(s,function(){var t;O?k.emit("rejectionHandled",e):(t=s.onrejectionhandled)&&t({promise:e,reason:e._v})})},D=function(e){var t=this;t._d||(t._d=!0,(t=t._w||t)._v=e,t._s=2,t._a||(t._a=t._c.slice()),L(t,!0))},U=function(e){var t,n=this;if(!n._d){n._d=!0,n=n._w||n;try{if(n===e)throw w("Promise can't be resolved itself");(t=j(e))?y(function(){var r={_w:n,_d:!1};try{t.call(e,u(U,r,1),u(D,r,1))}catch(e){D.call(r,e)}}):(n._v=e,n._s=1,L(n,!1))}catch(e){D.call({_w:n,_d:!1},e)}}};T||($=function(e){v(this,$,"Promise","_h"),d(e),r.call(this);try{e(u(U,this,1),u(D,this,1))}catch(e){D.call(this,e)}},(r=function(e){this._c=[],this._a=void 0,this._s=0,this._d=!1,this._v=void 0,this._h=0,this._n=!1}).prototype=n(100)($.prototype,{then:function(e,t){var n=A(m(this,$));return n.ok="function"!=typeof e||e,n.fail="function"==typeof t&&t,n.domain=O?k.domain:void 0,this._c.push(n),this._a&&this._a.push(n),this._s&&L(this,!1),n.promise},catch:function(e){return this.then(void 0,e)}}),a=function(){var e=new r;this.promise=e,this.resolve=u(U,e,1),this.reject=u(D,e,1)},b.f=A=function(e){return e===$||e===o?new a(e):i(e)}),p(p.G+p.W+p.F*!T,{Promise:$}),n(60)($,"Promise"),n(77)("Promise"),o=n(15).Promise,p(p.S+p.F*!T,"Promise",{reject:function(e){var t=A(this);return(0,t.reject)(e),t.promise}}),p(p.S+p.F*(l||!T),"Promise",{resolve:function(e){return C(l&&this===o?$:this,e)}}),p(p.S+p.F*!(T&&n(101)(function(e){$.all(e).catch(P)})),"Promise",{all:function(e){var t=this,n=A(t),r=n.resolve,i=n.reject,a=_(function(){var n=[],a=0,o=1;h(e,!1,function(e){var l=a++,s=!1;n.push(void 0),o++,t.resolve(e).then(function(e){s||(s=!0,n[l]=e,--o||r(n))},i)}),--o||r(n)});return a.e&&i(a.v),n.promise},race:function(e){var t=this,n=A(t),r=n.reject,i=_(function(){h(e,!1,function(e){t.resolve(e).then(n.resolve,r)})});return i.e&&r(i.v),n.promise}})},function(e,t,n){var r=n(14),i=n(3)("toStringTag"),a="Arguments"==r(function(){return arguments}());e.exports=function(e){var t,n,o;return void 0===e?"Undefined":null===e?"Null":"string"==typeof(n=function(e,t){try{return e[t]}catch(e){}}(t=Object(e),i))?n:a?r(t):"Object"==(o=r(t))&&"function"==typeof t.callee?"Arguments":o}},function(e,t,n){var r=n(15),i=n(4),a=i["__core-js_shared__"]||(i["__core-js_shared__"]={});(e.exports=function(e,t){return a[e]||(a[e]=void 0!==t?t:{})})("versions",[]).push({version:r.version,mode:n(56)?"pure":"global",copyright:"© 2018 Denis Pushkarev (zloirock.ru)"})},function(e,t,n){e.exports=!n(9)&&!n(7)(function(){return 7!=Object.defineProperty(n(57)("div"),"a",{get:function(){return 7}}).a})},function(e,t,n){var r=n(6);e.exports=function(e,t){if(!r(e))return e;var n,i;if(t&&"function"==typeof(n=e.toString)&&!r(i=n.call(e)))return i;if("function"==typeof(n=e.valueOf)&&!r(i=n.call(e)))return i;if(!t&&"function"==typeof(n=e.toString)&&!r(i=n.call(e)))return i;throw TypeError("Can't convert object to primitive value")}},function(e,t){var n=Math.ceil,r=Math.floor;e.exports=function(e){return isNaN(e=+e)?0:(e>0?r:n)(e)}},function(e,t,n){var r,i,a,o=n(13),l=n(95),s=n(75),u=n(57),c=n(4),p=c.process,f=c.setImmediate,d=c.clearImmediate,v=c.MessageChannel,h=c.Dispatch,m=0,g={},y=function(){var e=+this;if(g.hasOwnProperty(e)){var t=g[e];delete g[e],t()}},b=function(e){y.call(e.data)};f&&d||(f=function(e){for(var t=[],n=1;arguments.length>n;)t.push(arguments[n++]);return g[++m]=function(){l("function"==typeof e?e:Function(e),t)},r(m),m},d=function(e){delete g[e]},"process"==n(14)(p)?r=function(e){p.nextTick(o(y,e,1))}:h&&h.now?r=function(e){h.now(o(y,e,1))}:v?(a=(i=new v).port2,i.port1.onmessage=b,r=o(a.postMessage,a,1)):c.addEventListener&&"function"==typeof postMessage&&!c.importScripts?(r=function(e){c.postMessage(e+"","*")},c.addEventListener("message",b,!1)):r="onreadystatechange"in u("script")?function(e){s.appendChild(u("script")).onreadystatechange=function(){s.removeChild(this),y.call(e)}}:function(e){setTimeout(o(y,e,1),0)}),e.exports={set:f,clear:d}},function(e,t,n){var r=n(4).document;e.exports=r&&r.documentElement},function(e,t,n){"use strict";var r=n(25);function i(e){var t,n;this.promise=new e(function(e,r){if(void 0!==t||void 0!==n)throw TypeError("Bad Promise constructor");t=e,n=r}),this.resolve=r(t),this.reject=r(n)}e.exports.f=function(e){return new i(e)}},function(e,t,n){"use strict";var r=n(4),i=n(12),a=n(9),o=n(3)("species");e.exports=function(e){var t=r[e];a&&t&&!t[o]&&i.f(t,o,{configurable:!0,get:function(){return this}})}},function(e,t,n){var r=n(17),i=n(28),a=n(79)(!1),o=n(62)("IE_PROTO");e.exports=function(e,t){var n,l=i(e),s=0,u=[];for(n in l)n!=o&&r(l,n)&&u.push(n);for(;t.length>s;)r(l,n=t[s++])&&(~a(u,n)||u.push(n));return u}},function(e,t,n){var r=n(28),i=n(59),a=n(103);e.exports=function(e){return function(t,n,o){var l,s=r(t),u=i(s.length),c=a(o,u);if(e&&n!=n){for(;u>c;)if((l=s[c++])!=l)return!0}else for(;u>c;c++)if((e||c in s)&&s[c]===n)return e||c||0;return!e&&-1}}},function(e,t){t.f={}.propertyIsEnumerable},function(e,t,n){var r=n(5),i=n(15),a=n(7);e.exports=function(e,t){var n=(i.Object||{})[e]||Object[e],o={};o[e]=t(n),r(r.S+r.F*a(function(){n(1)}),"Object",o)}},function(e,t,n){var r=n(14);e.exports=Array.isArray||function(e){return"Array"==r(e)}},function(e,t,n){var r=n(12).f,i=Function.prototype,a=/^\s*function ([^ (]*)/;"name"in i||n(9)&&r(i,"name",{configurable:!0,get:function(){try{return(""+this).match(a)[1]}catch(e){return""}}})},function(e,t,n){var r=n(6),i=n(14),a=n(3)("match");e.exports=function(e){var t;return r(e)&&(void 0!==(t=e[a])?!!t:"RegExp"==i(e))}},function(e,t,n){n(66)("match",1,function(e,t,n){return[function(n){"use strict";var r=e(this),i=null==n?void 0:n[t];return void 0!==i?i.call(n,r):new RegExp(n)[t](String(r))},n]})},function(e,t,n){"use strict";e.exports=function(e){e.Vue,e.options,e.router,e.siteData}},function(e,t){var n="Expected a function",r=NaN,i="[object Symbol]",a=/^\s+|\s+$/g,o=/^[-+]0x[0-9a-f]+$/i,l=/^0b[01]+$/i,s=/^0o[0-7]+$/i,u=parseInt,c="object"==typeof global&&global&&global.Object===Object&&global,p="object"==typeof self&&self&&self.Object===Object&&self,f=c||p||Function("return this")(),d=Object.prototype.toString,v=Math.max,h=Math.min,m=function(){return f.Date.now()};function g(e,t,r){var i,a,o,l,s,u,c=0,p=!1,f=!1,d=!0;if("function"!=typeof e)throw new TypeError(n);function g(t){var n=i,r=a;return i=a=void 0,c=t,l=e.apply(r,n)}function _(e){var n=e-u;return void 0===u||n>=t||n<0||f&&e-c>=o}function x(){var e=m();if(_(e))return C(e);s=setTimeout(x,function(e){var n=t-(e-u);return f?h(n,o-(e-c)):n}(e))}function C(e){return s=void 0,d&&i?g(e):(i=a=void 0,l)}function w(){var e=m(),n=_(e);if(i=arguments,a=this,u=e,n){if(void 0===s)return function(e){return c=e,s=setTimeout(x,t),p?g(e):l}(u);if(f)return s=setTimeout(x,t),g(u)}return void 0===s&&(s=setTimeout(x,t)),l}return t=b(t)||0,y(r)&&(p=!!r.leading,o=(f="maxWait"in r)?v(b(r.maxWait)||0,t):o,d="trailing"in r?!!r.trailing:d),w.cancel=function(){void 0!==s&&clearTimeout(s),c=0,i=u=a=s=void 0},w.flush=function(){return void 0===s?l:C(m())},w}function y(e){var t=typeof e;return!!e&&("object"==t||"function"==t)}function b(e){if("number"==typeof e)return e;if(function(e){return"symbol"==typeof e||function(e){return!!e&&"object"==typeof e}(e)&&d.call(e)==i}(e))return r;if(y(e)){var t="function"==typeof e.valueOf?e.valueOf():e;e=y(t)?t+"":t}if("string"!=typeof e)return 0===e?e:+e;e=e.replace(a,"");var n=l.test(e);return n||s.test(e)?u(e.slice(2),n?2:8):o.test(e)?r:+e}e.exports=function(e,t,r){var i=!0,a=!0;if("function"!=typeof e)throw new TypeError(n);return y(r)&&(i="leading"in r?!!r.leading:i,a="trailing"in r?!!r.trailing:a),g(e,t,{leading:i,maxWait:t,trailing:a})}},function(e,t,n){e.exports=n(163)},function(e,t){e.exports=function(e,t,n,r){if(!(e instanceof t)||void 0!==r&&r in e)throw TypeError(n+": incorrect invocation!");return e}},function(e,t,n){var r=n(13),i=n(91),a=n(92),o=n(8),l=n(59),s=n(93),u={},c={};(t=e.exports=function(e,t,n,p,f){var d,v,h,m,g=f?function(){return e}:s(e),y=r(n,p,t?2:1),b=0;if("function"!=typeof g)throw TypeError(e+" is not iterable!");if(a(g)){for(d=l(e.length);d>b;b++)if((m=t?y(o(v=e[b])[0],v[1]):y(e[b]))===u||m===c)return m}else for(h=g.call(e);!(v=h.next()).done;)if((m=i(h,y,v.value,t))===u||m===c)return m}).BREAK=u,t.RETURN=c},function(e,t,n){var r=n(8);e.exports=function(e,t,n,i){try{return i?t(r(n)[0],n[1]):t(n)}catch(t){var a=e.return;throw void 0!==a&&r(a.call(e)),t}}},function(e,t,n){var r=n(19),i=n(3)("iterator"),a=Array.prototype;e.exports=function(e){return void 0!==e&&(r.Array===e||a[i]===e)}},function(e,t,n){var r=n(69),i=n(3)("iterator"),a=n(19);e.exports=n(15).getIteratorMethod=function(e){if(null!=e)return e[i]||e["@@iterator"]||a[r(e)]}},function(e,t,n){var r=n(8),i=n(25),a=n(3)("species");e.exports=function(e,t){var n,o=r(e).constructor;return void 0===o||null==(n=r(o)[a])?t:i(n)}},function(e,t){e.exports=function(e,t,n){var r=void 0===n;switch(t.length){case 0:return r?e():e.call(n);case 1:return r?e(t[0]):e.call(n,t[0]);case 2:return r?e(t[0],t[1]):e.call(n,t[0],t[1]);case 3:return r?e(t[0],t[1],t[2]):e.call(n,t[0],t[1],t[2]);case 4:return r?e(t[0],t[1],t[2],t[3]):e.call(n,t[0],t[1],t[2],t[3])}return e.apply(n,t)}},function(e,t,n){var r=n(4),i=n(74).set,a=r.MutationObserver||r.WebKitMutationObserver,o=r.process,l=r.Promise,s="process"==n(14)(o);e.exports=function(){var e,t,n,u=function(){var r,i;for(s&&(r=o.domain)&&r.exit();e;){i=e.fn,e=e.next;try{i()}catch(r){throw e?n():t=void 0,r}}t=void 0,r&&r.enter()};if(s)n=function(){o.nextTick(u)};else if(!a||r.navigator&&r.navigator.standalone)if(l&&l.resolve){var c=l.resolve(void 0);n=function(){c.then(u)}}else n=function(){i.call(r,u)};else{var p=!0,f=document.createTextNode("");new a(u).observe(f,{characterData:!0}),n=function(){f.data=p=!p}}return function(r){var i={fn:r,next:void 0};t&&(t.next=i),e||(e=i,n()),t=i}}},function(e,t){e.exports=function(e){try{return{e:!1,v:e()}}catch(e){return{e:!0,v:e}}}},function(e,t,n){var r=n(4).navigator;e.exports=r&&r.userAgent||""},function(e,t,n){var r=n(8),i=n(6),a=n(76);e.exports=function(e,t){if(r(e),i(t)&&t.constructor===e)return t;var n=a.f(e);return(0,n.resolve)(t),n.promise}},function(e,t,n){var r=n(16);e.exports=function(e,t,n){for(var i in t)r(e,i,t[i],n);return e}},function(e,t,n){var r=n(3)("iterator"),i=!1;try{var a=[7][r]();a.return=function(){i=!0},Array.from(a,function(){throw 2})}catch(e){}e.exports=function(e,t){if(!t&&!i)return!1;var n=!1;try{var a=[7],o=a[r]();o.next=function(){return{done:n=!0}},a[r]=function(){return o},e(a)}catch(e){}return n}},function(e,t,n){"use strict";var r=n(27),i=n(104),a=n(80),o=n(29),l=n(61),s=Object.assign;e.exports=!s||n(7)(function(){var e={},t={},n=Symbol(),r="abcdefghijklmnopqrst";return e[n]=7,r.split("").forEach(function(e){t[e]=e}),7!=s({},e)[n]||Object.keys(s({},t)).join("")!=r})?function(e,t){for(var n=o(e),s=arguments.length,u=1,c=i.f,p=a.f;s>u;)for(var f,d=l(arguments[u++]),v=c?r(d).concat(c(d)):r(d),h=v.length,m=0;h>m;)p.call(d,f=v[m++])&&(n[f]=d[f]);return n}:s},function(e,t,n){var r=n(73),i=Math.max,a=Math.min;e.exports=function(e,t){return(e=r(e))<0?i(e+t,0):a(e,t)}},function(e,t){t.f=Object.getOwnPropertySymbols},function(e,t,n){var r=n(6),i=n(106).onFreeze;n(81)("freeze",function(e){return function(t){return e&&r(t)?e(i(t)):t}})},function(e,t,n){var r=n(26)("meta"),i=n(6),a=n(17),o=n(12).f,l=0,s=Object.isExtensible||function(){return!0},u=!n(7)(function(){return s(Object.preventExtensions({}))}),c=function(e){o(e,r,{value:{i:"O"+ ++l,w:{}}})},p=e.exports={KEY:r,NEED:!1,fastKey:function(e,t){if(!i(e))return"symbol"==typeof e?e:("string"==typeof e?"S":"P")+e;if(!a(e,r)){if(!s(e))return"F";if(!t)return"E";c(e)}return e[r].i},getWeak:function(e,t){if(!a(e,r)){if(!s(e))return!0;if(!t)return!1;c(e)}return e[r].w},onFreeze:function(e){return u&&p.NEED&&s(e)&&!a(e,r)&&c(e),e}}},function(e,t,n){var r=n(3)("unscopables"),i=Array.prototype;null==i[r]&&n(11)(i,r,{}),e.exports=function(e){i[r][e]=!0}},function(e,t){e.exports=function(e,t){return{value:t,done:!!e}}},function(e,t,n){"use strict";var r=n(56),i=n(5),a=n(16),o=n(11),l=n(19),s=n(110),u=n(60),c=n(113),p=n(3)("iterator"),f=!([].keys&&"next"in[].keys()),d=function(){return this};e.exports=function(e,t,n,v,h,m,g){s(n,t,v);var y,b,_,x=function(e){if(!f&&e in E)return E[e];switch(e){case"keys":case"values":return function(){return new n(this,e)}}return function(){return new n(this,e)}},C=t+" Iterator",w="values"==h,k=!1,E=e.prototype,S=E[p]||E["@@iterator"]||h&&E[h],$=S||x(h),O=h?w?x("entries"):$:void 0,P="Array"==t&&E.entries||S;if(P&&(_=c(P.call(new e)))!==Object.prototype&&_.next&&(u(_,C,!0),r||"function"==typeof _[p]||o(_,p,d)),w&&S&&"values"!==S.name&&(k=!0,$=function(){return S.call(this)}),r&&!g||!f&&!k&&E[p]||o(E,p,$),l[t]=$,l[C]=d,h)if(y={values:w?$:x("values"),keys:m?$:x("keys"),entries:O},g)for(b in y)b in E||a(E,b,y[b]);else i(i.P+i.F*(f||k),t,y);return y}},function(e,t,n){"use strict";var r=n(111),i=n(58),a=n(60),o={};n(11)(o,n(3)("iterator"),function(){return this}),e.exports=function(e,t,n){e.prototype=r(o,{next:i(1,n)}),a(e,t+" Iterator")}},function(e,t,n){var r=n(8),i=n(112),a=n(63),o=n(62)("IE_PROTO"),l=function(){},s=function(){var e,t=n(57)("iframe"),r=a.length;for(t.style.display="none",n(75).appendChild(t),t.src="javascript:",(e=t.contentWindow.document).open(),e.write(" + + + + + + + + + diff --git a/docs/getting-started.html b/docs/getting-started.html new file mode 100644 index 000000000..e0e466526 --- /dev/null +++ b/docs/getting-started.html @@ -0,0 +1,584 @@ + + + + + + Getting Started | GrapesJS + + + + + + + + + +

Getting Started

This is a step-by-step guide for anyone who wants to create their own builder with GrapesJS. This is not a comprehensive guide, just a concise overview of most common modules. Follow along to create a page builder from scratch. Skip to the end of this page to see the final result

Import the library

Before you start using GrapesJS, you'll have to import it. Let's import the latest version

<link rel="stylesheet" href="//unpkg.com/grapesjs/dist/css/grapes.min.css">
+<script src="//unpkg.com/grapesjs"></script>
+<!--
+If you need plugins, put them below the main grapesjs script
+<script src="/path/to/some/plugin.min.js"></script>
+-->
+

or if you're in a Node environment

import 'grapesjs/dist/css/grapes.min.css';
+import grapesjs from 'grapesjs';
+// If you need plugins, put them below the main grapesjs script
+// import 'grapesjs-some-plugin';
+

Start from the canvas

The first step is to define the interface of our editor. For this purpose we gonna start with basic HTML layouts. Finding a common structure for the UI of any project is not an easy task. That's why GrapesJS prefers to keep this process as simple as possible. We provide a few helpers, but let the user define the interface. This guarantees maximum flexibility. +The main part of the GrapesJS editor is the canvas, this is where you create the structure of your templates and you can't miss it. Let's try to initiate the editor with the canvas and no panels.

<div id="gjs">
+  <h1>Hello World Component!</h1>
+</div>
+
const editor = grapesjs.init({
+  // Indicate where to init the editor. You can also pass an HTMLElement
+  container: '#gjs',
+  // Get the content for the canvas directly from the element
+  // As an alternative we could use: `components: '<h1>Hello World Component!</h1>'`,
+  fromElement: true,
+  // Size of the editor
+  height: '300px',
+  width: 'auto',
+  // Disable the storage manager for the moment
+  storageManager: { type: null },
+  // Avoid any default panel
+  panels: { defaults: [] },
+});
+
/* Let's highlight canvas boundaries */
+#gjs {
+  border: 3px solid #444;
+}
+
+/* Reset some default styling */
+.gjs-cv-canvas {
+  top: 0;
+  width: 100%;
+  height: 100%;
+}
+

Hello World Component!

With just the canvas you're already able to move, copy and delete components from the structure. For now, we see the example template taken from the container. Next let's look at how to create and drag custom blocks into our canvas.

Add Blocks

The block in GrapesJS is just a reusable piece of HTML that you can drop in the canvas. A block can be an image, a button, or an entire section with videos, forms and iframes. Let's start by creating another container and append a few basic blocks inside of it. Later we can use this technique to build more complex structures.




 

<div id="gjs">
+  ...
+</div>
+<div id="blocks"></div>
+
const editor = grapesjs.init({
+  // ...
+  blockManager: {
+    appendTo: '#blocks',
+    blocks: [
+      {
+        id: 'section', // id is mandatory
+        label: '<b>Section</b>', // You can use HTML/SVG inside labels
+        attributes: { class:'gjs-block-section' },
+        content: `<section>
+          <h1>This is a simple title</h1>
+          <div>This is just a Lorem text: Lorem ipsum dolor sit amet</div>
+        </section>`,
+      }, {
+        id: 'text',
+        label: 'Text',
+        content: '<div data-gjs-type="text">Insert your text here</div>',
+      }, {
+        id: 'image',
+        label: 'Image',
+        // Select the component once it's dropped
+        select: true,
+        // You can pass components as a JSON instead of a simple HTML string,
+        // in this case we also use a defined component type `image`
+        content: { type: 'image' },
+        // This triggers `active` event on dropped components and the `image`
+        // reacts by opening the AssetManager
+        activate: true,
+      }
+    ]
+  },
+});
+
.gjs-block {
+  width: auto;
+  height: auto;
+  min-height: auto;
+}
+

Hello World Component!

As you can see we add our blocks via the initial configuration. Obviously there might be a case in which you would like to add them dynamically, in this case you have to use the Block Manager API

editor.BlockManager.add('my-block-id', {
+  label: '...',
+  category: '...',
+  // ...
+})
+

TIP

If you want to learn more about blocks we suggest to read its dedicated article: Block Manager Module

Define Components

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.

GrapesJS comes with a few 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, 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.

As we have seen before you can create Blocks directly as Components

editor.BlockManager.add('my-block-id', {
+  // ...
+  content: {
+    tagName: 'div',
+    draggable: false,
+    attributes: { 'some-attribute': 'some-value' },
+    components: [
+      {
+        tagName: 'span',
+        content: '<b>Some static content</b>',
+      }, {
+        tagName: 'div',
+        // use `content` for static strings, `components` string will be parsed
+        // and transformed in Components
+        components: '<span>HTML at some point</span>',
+      }      
+    ]
+  }
+})
+

TIP

Check out the Components API to learn how to interact with components dynamically

An example on how to select some inner component and replace its children with new contents

// The wrapper is the root Component
+const wrapper = editor.DomComponents.getWrapper();
+const myComponent = wrapper.find('div.my-component')[0];
+myComponent.components().forEach(component => /* ... do something ... */);
+myComponent.components('<div>New content</div>');
+

Panels & Buttons

Now that we have a canvas and custom blocks let's see how to create a new custom panel with some buttons inside (using Panels API) which trigger commands (the core one or custom).

 
 
 





<div class="panel__top">
+    <div class="panel__basic-actions"></div>
+</div>
+<div id="gjs">
+  ...
+</div>
+<div id="blocks"></div>
+
.panel__top {
+  padding: 0;
+  width: 100%;
+  display: flex;
+  position: initial;
+  justify-content: center;
+  justify-content: space-between;
+}
+.panel__basic-actions {
+  position: initial;
+}
+
editor.Panels.addPanel({
+  id: 'panel-top',
+  el: '.panel__top',
+});
+editor.Panels.addPanel({
+  id: 'basic-actions',
+  el: '.panel__basic-actions',
+  buttons: [
+    {
+      id: 'visibility',
+      active: true, // active by default
+      className: 'btn-toggle-borders',
+      label: '<u>B</u>',
+      command: 'sw-visibility', // Built-in command
+    }, {
+      id: 'export',
+      className: 'btn-open-export',
+      label: 'Exp',
+      command: 'export-template',
+      context: 'export-template', // For grouping context of buttons from the same panel
+    }, {
+      id: 'show-json',
+      className: 'btn-show-json',
+      label: 'JSON',
+      context: 'show-json',
+      command(editor) {
+        editor.Modal.setTitle('Components JSON')
+          .setContent(`<textarea style="width:100%; height: 250px;">
+            ${JSON.stringify(editor.getComponents())}
+          </textarea>`)
+          .open();
+      },
+    }
+  ],
+});
+

Hello World Component!

We have defined where to render the panel with el: '#basic-panel' and then for each button we added a command property. The command could be the id, an object with run and stop functions or simply a single function. +Try to use Commands when possible, they allow you to track actions globally. Commands also execute callbacks before and after their execution (you can even interrupt them).

editor.on('run:export-template:before', opts => {
+  console.log('Before the command run');
+  if (0 /* some condition */) {
+    opts.abort = 1;
+  }
+});
+editor.on('run:export-template', () => console.log('After the command run'));
+editor.on('abort:export-template', () => console.log('Command aborted'));
+

TIP

Check out the Panels API to see all the available methods

Layers

Another utility tool you might find useful when working with web elements is the layer manger. It's a tree overview of the structure nodes and enables you to manage it easier. To enable it you just have to specify where you want to render it




 
 
 
 
 
 
 
 


<div class="panel__top">
+    <div class="panel__basic-actions"></div>
+</div>
+<div class="editor-row">
+  <div class="editor-canvas">
+    <div id="gjs">...</div>
+  </div>
+  <div class="panel__right">
+    <div class="layers-container"></div>
+  </div>
+</div>
+<div id="blocks"></div>
+
.editor-row {
+  display: flex;
+  justify-content: flex-start;
+  align-items: stretch;
+  flex-wrap: nowrap;
+  height: 300px;
+}
+
+.editor-canvas {
+  flex-grow: 1;
+}
+
+.panel__right {
+  flex-basis: 230px;
+  position: relative;
+  overflow-y: auto;
+}
+
const editor = grapesjs.init({
+  // ...
+  layerManager: {
+    appendTo: '.layers-container'
+  },
+  // We define a default panel as a sidebar to contain layers
+  panels: {
+    defaults: [{
+      id: 'layers',
+      el: '.panel__right',
+      // Make the panel resizable
+      resizable: {
+        maxDim: 350,
+        minDim: 200,
+        tc: 0, // Top handler
+        cl: 1, // Left handler
+        cr: 0, // Right handler
+        bc: 0, // Bottom handler
+        // Being a flex child we need to change `flex-basis` property
+        // instead of the `width` (default)
+        keyWidth: 'flex-basis',
+      },
+    }]
+  }
+});
+

Hello World Component!

Style Manager

Once you have defined the structure of the template the next step is the ability to style it. To meet this need GrapesJS includes the Style Manager module which is composed by CSS style properties and sectors. To make it more clear, let's see how to define a basic set.

Let's start by adding one more panel inside the panel__right and another one in panel__top which will contain a Layer/Style Manager switcher



 




 



<div class="panel__top">
+    <div class="panel__basic-actions"></div>
+    <div class="panel__switcher"></div>
+</div>
+...
+  <div class="panel__right">
+    <div class="layers-container"></div>
+    <div class="styles-container"></div>
+  </div>
+...
+
.panel__switcher {
+  position: initial;
+}
+
const editor = grapesjs.init({
+  // ...
+  panels: {
+    defaults: [
+      // ...
+      {
+        id: 'panel-switcher',
+        el: '.panel__switcher',
+        buttons: [{
+            id: 'show-layers',
+            active: true,
+            label: 'Layers',
+            command: 'show-layers',
+            // Once activated disable the possibility to turn it off
+            togglable: false,
+          }, {
+            id: 'show-style',
+            active: true,
+            label: 'Styles',
+            command: 'show-styles',
+            togglable: false,
+        }],
+      }
+    ]
+  },
+  // The Selector Manager allows to assign classes and
+  // different states (eg. :hover) on components.
+  // Generally, it's used in conjunction with Style Manager
+  // but it's not mandatory
+  selectorManager: {
+    appendTo: '.styles-container'
+  },
+  styleManager: {
+    appendTo: '.styles-container',
+    sectors: [{
+        name: 'Dimension',
+        open: false,
+        // Use built-in properties
+        buildProps: ['width', 'min-height', 'padding'],
+        // Use `properties` to define/override single property
+        properties: [
+          {
+            // Type of the input,
+            // options: integer | radio | select | color | slider | file | composite | stack
+            type: 'integer',
+            name: 'The width', // Label for the property
+            property: 'width', // CSS property (if buildProps contains it will be extended)
+            units: ['px', '%'], // Units, available only for 'integer' types
+            defaults: 'auto', // Default value
+            min: 0, // Min value, available only for 'integer' types
+          }
+        ]
+      },{
+        name: 'Extra',
+        open: false,
+        buildProps: ['background-color', 'box-shadow', 'custom-prop'],
+        properties: [
+          {
+            id: 'custom-prop',
+            name: 'Custom Label',
+            property: 'font-size',
+            type: 'select',
+            defaults: '32px',
+            // List of options, available only for 'select' and 'radio'  types
+            options: [
+              { value: '12px', name: 'Tiny' },
+              { value: '18px', name: 'Medium' },
+              { value: '32px', name: 'Big' },
+            ],
+         }
+        ]
+      }]
+  },
+});
+
+// Define commands
+editor.Commands.add('show-layers', {
+  getRowEl(editor) { return editor.getContainer().closest('.editor-row'); },
+  getLayersEl(row) { return row.querySelector('.layers-container') },
+
+  run(editor, sender) {
+    const lmEl = this.getLayersEl(this.getRowEl(editor));
+    lmEl.style.display = '';
+  },
+  stop(editor, sender) {
+    const lmEl = this.getLayersEl(this.getRowEl(editor));
+    lmEl.style.display = 'none';
+  },
+});
+editor.Commands.add('show-styles', {
+  getRowEl(editor) { return editor.getContainer().closest('.editor-row'); },
+  getStyleEl(row) { return row.querySelector('.styles-container') },
+
+  run(editor, sender) {
+    const smEl = this.getStyleEl(this.getRowEl(editor));
+    smEl.style.display = '';
+  },
+  stop(editor, sender) {
+    const smEl = this.getStyleEl(this.getRowEl(editor));
+    smEl.style.display = 'none';
+  },
+});
+

Hello World Component!

Inside Style Manager definition we use buildProps which helps us create common properties from available built-in objects then in properties we can override same objects (eg. passing another name to change the label) identified by property name. As you can see from custom-prop example it's a matter of defining the CSS property and the input type. We suggest to check a more complete example of Style Manager properties usage from the webpage preset demo

TIP

Check the Style Manager API to see how to update sectors and properties dynamically

Traits

Most of the time you would style your components and place them somewhere in the structure, but sometimes your components might need custom attributes or even custom behaviors and for this need you can make use of traits. Traits are commonly used to update HTML element attributes (eg. placeholder for inputs or alt for images), but you can also define your own custom traits. Access the selected Component model and do whatever you want. For this guide, we going to show you how to render available traits, for more details on how to extend them we suggest you read the Trait Manager Module page.

Let's create a new container for traits. Tell the editor where to render it and update the sidebar switcher





 



...
+  <div class="panel__right">
+    <div class="layers-container"></div>
+    <div class="styles-container"></div>
+    <div class="traits-container"></div>
+  </div>
+...
+
const editor = grapesjs.init({
+  // ...
+  panels: {
+    defaults: [
+      // ...
+      {
+        id: 'panel-switcher',
+        el: '.panel__switcher',
+        buttons: [
+          // ...
+          {
+            id: 'show-traits',
+            active: true,
+            label: 'Traits',
+            command: 'show-traits',
+            togglable: false,
+        }],
+      }
+    ]
+  },
+  traitManager: {
+    appendTo: '.traits-container',
+  },
+});
+
+// Define command
+// ...
+editor.Commands.add('show-traits', {
+  getTraitsEl(editor) {
+    const row = editor.getContainer().closest('.editor-row');
+    return row.querySelector('.traits-container');
+  },
+  run(editor, sender) {
+    this.getTraitsEl(editor).style.display = '';
+  },
+  stop(editor, sender) {
+    this.getTraitsEl(editor).style.display = 'none';
+  },
+});
+

Hello World Component!

Now if you switch to the Trait panel and select one of the inner components you should see its default traits.

Responsive templates

GrapesJS implements a module which allows you to work with responsive templates easily. Let's see how to define different devices and a button for device switching



 




<div class="panel__top">
+    <div class="panel__basic-actions"></div>
+    <div class="panel__devices"></div>
+    <div class="panel__switcher"></div>
+</div>
+...
+
.panel__devices {
+  position: initial;
+}
+
const editor = grapesjs.init({
+  // ...
+  deviceManager: {
+    devices: [{
+        name: 'Desktop',
+        width: '', // default size
+      }, {
+        name: 'Mobile',
+        width: '320px', // this value will be used on canvas width
+        widthMedia: '480px', // this value will be used in CSS @media
+    }]
+  },
+  // ...
+  panels: {
+    defaults: [
+      // ...
+      {
+        id: 'panel-devices',
+        el: '.panel__devices',
+        buttons: [{
+            id: 'device-desktop',
+            label: 'D',
+            command: 'set-device-desktop',
+            active: true,
+            togglable: false,
+          }, {
+            id: 'device-mobile',
+            label: 'M',
+            command: 'set-device-mobile',
+            togglable: false,
+        }],
+      }
+    ]
+  },
+});
+
+// Commands
+editor.Commands.add('set-device-desktop', {
+  run: editor => editor.setDevice('Desktop')
+});
+editor.Commands.add('set-device-mobile', {
+  run: editor => editor.setDevice('Mobile')
+});
+

Hello World Component!

As you can see from the commands definition we use the editor.setDevice method to change the size of the viewport. In case you need to trigger an action on device change you can setup a listener like this:

editor.on('change:device', () => console.log('Current device: ', editor.getDevice()));
+

What about the mobile-first approach? You can achieve it by changing your configurations in this way:

const editor = grapesjs.init({
+  // ...
+  mediaCondition: 'min-width', // default is `max-width`
+  deviceManager: {
+    devices: [{
+        name: 'Mobile',
+        width: '320',
+        widthMedia: '',
+      }, {
+        name: 'Desktop',
+        width: '',
+        widthMedia:'1024',
+    }]
+  },
+  // ...
+});
+
+// Set initial device as Mobile
+editor.setDevice('Mobile');
+

TIP

Check out the Device Manager API to see all the available methods

Store & load data

Once you have finished with defining your builder interface the next step would be to setup the storing and loading process. +GrapesJS implements 2 simple type of storages inside its Storage Manager: The local (by using localStorage, active by default) and the remote one. Those are enough to cover most of the cases, but it's also possible to add new implementations (grapesjs-indexeddb is a good example). +Let's see how the default options work

grapesjs.init({
+    // ...
+    storageManager: {
+      id: 'gjs-',             // Prefix identifier that will be used inside storing and loading
+      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
+      storeComponents: true,  // Enable/Disable storing of components in JSON format
+      storeStyles: true,      // Enable/Disable storing of rules in JSON format
+      storeHtml: true,        // Enable/Disable storing of components as HTML string
+      storeCss: true,         // Enable/Disable storing of rules as CSS string
+    }
+});
+

It is worth noting that the default id parameter adds a prefix for all keys to store. If you check the localStorage inside the devtool panel you'll see something like { 'gjs-components': '....' ...} this way it lessens the risk of collisions.

Let's look at the configuration required to setup the remote storage

grapesjs.init({
+    // ...
+    storageManager: {
+      type: 'remote',
+      stepsBeforeSave: 10,
+      urlStore: 'http://store/endpoint',
+      urlLoad: 'http://load/endpoint',
+      params: {}, // Custom parameters to pass with the remote storage request, eg. CSRF token
+      headers: {}, // Custom headers for the remote storage request
+    }
+});
+

As you might noticed, we've left some default options unchanged: Increased changes necessary for autosave triggering and passed remote endpoints. +If you prefer you could also disable the autosaving and you can do so using a custom command

// ...
+  storageManager: {
+    type: 'remote',
+    autosave: false,
+    // ...
+  },
+  // ...
+  commands: {
+    defaults: [
+      // ...
+      {
+        id: 'store-data',
+        run(editor) {
+          editor.store();
+        },
+      }
+    ]
+  }
+// ...
+

To get a better overview of the Storage Manager and how you should store/load the template, or how to define new storages you should read the Storage Manager Module page

Theming

One last step that might actually improve a lot your editor personality is how it's look visually. To achieve an easy theming we have adapted an atomic design for this purpose. To customize the main palette of colors all you have to do is to change few CSS rules. Alternatively if you include GrapesJS styles via SCSS you can make use of its internal variables and declare your variables before the import

// Put your variables before the GrapesJS style import
+
+// Palette variables
+$primaryColor: #444;
+$secondaryColor: #ddd;
+$tertiaryColor: #804f7b;
+$quaternaryColor: #d278c9;
+
+// ...
+
+@import "grapesjs/src/styles/scss/main.scss";
+

In case of a simple CSS you'll have to put your rules after the GrapesJS styles. +To complete our builder let's customize its color palette and to make it more visually "readable" we can replace all button labels with SVG icons

/* We can remove the border we've set at the beginnig */
+#gjs {
+  border: none;
+}
+/* Theming */
+
+/* Primary color for the background */
+.gjs-one-bg {
+  background-color: #78366a;
+}
+
+/* Secondary color for the text color */
+.gjs-two-color {
+  color: rgba(255, 255, 255, 0.7);
+}
+
+/* Tertiary color for the background */
+.gjs-three-bg {
+  background-color: #ec5896;
+  color: white;
+}
+
+/* Quaternary color for the text color */
+.gjs-four-color,
+.gjs-four-color-h:hover {
+  color: #ec5896;
+}
+

and here is our final result

Hello World Component!

Last Updated: 10/26/2018, 1:39:09 PM
+ + + diff --git a/docs/guides/Custom-CSS-parser.html b/docs/guides/Custom-CSS-parser.html new file mode 100644 index 000000000..f137f8021 --- /dev/null +++ b/docs/guides/Custom-CSS-parser.html @@ -0,0 +1,214 @@ + + + + + + Use Custom CSS Parser | GrapesJS + + + + + + + + + +

Use Custom CSS Parser

If you just use GrapesJS for building templates from scratch, so you start from an empty canvas and for editing you strictly rely on the generated JSON (final HTML/CSS only for end-users) then, probably, you might skip this guide. On the other hand, if you import templates from already defined HTML/CSS or let the user embed custom codes (eg. using the grapesjs-custom-code plugin), then you have to know that you might face strange behaviors.

WARNING

This guide requires GrapesJS v0.14.33 or higher

Import HTML/CSS

Importing already defined HTML/CSS is a really good feature as it lets you start editing immediately any kind of template and obviously, GrapesJS itself promotes this kind of approach

<div id="gjs">
+  <div class="txt-red">Hello world!</div>
+  <style>.txt-red{color: red}</style>
+</div>
+
+<script type="text/javascript">
+  const editor = grapesjs.init({
+      container : '#gjs',
+      fromElement: true,
+  });
+</script>
+

To work fast and easier GrapesJS needs to compile a simple string (HTML/CSS) into structured nodes (nested JS objects). Fortunately, most of the hard work (parsing) is already done by the browser itself which translates that string into its own objects (DOM/CSSOM) and so we just rely on those, by traversing them and creating our nodes (unfortunately browser's objects are not enough). The fact we're able to parse our strings just by using the browser itself it's very cool, we can enable the import feature without requiring any third-party library, so... where is the problem? Well, while the generated DOM is performing quite well, as we're able to extract what we need, unfortunately, it's not the same for the CSSOM, so let's see in the next paragraph what is wrong with it.

CSSOM results are inconsistent

Unfortunately, we have discovered that the CSSOM generated by browsers are highly inconsistent from what we ask to parse. To demonstrate it, we gonna create a simple example by using the built-in parser and we'll check its result. +So, for our case we just take in account a simple rule, we'll parse it and print the CSSOM result on screen.

<h1>To parse</h1>
+<pre id="css-to-parse">
+  .simple-class {
+    background-image:url("https://image1.png"), url("https://image2.jpg");
+    background-attachment: fixed, scroll;
+    background-position:left top, center center;
+    background-repeat:repeat-y, no-repeat;
+    background-size: contain, cover;
+    box-shadow: 0 0 5px #9d7aa5, 0 0 10px #e6c3ee;
+    border: 2px solid #FF0000;
+  }
+</pre>
+
+<h1>Result</h1>
+<pre id="result"></pre>
+
+<script>
+  // We use ES5 just to make it more cross-browser, without the need of being compiled
+
+  function parse(str) {
+    var result = [];
+    // Create the element which will contain the style to parse
+    var el = document.createElement('style');
+    el.innerHTML = str;
+    // We have to append the style to get its CSSOM
+    document.head.appendChild(el);
+    var sheet = el.sheet;
+    // Now we can remove it
+    document.head.removeChild(el);
+
+    return sheet;
+  }
+
+  function CSSOMToString(root) {
+    // For the sake of brevity we just print what we need
+    var styleStr = '';
+    var rule = root.cssRules[0];
+    var style = rule.style;
+    // The only way we have to iterate over CSSStyleDeclaration
+    for (var i = 0, len = style.length; i < len; i++) {
+      var property = style[i];
+      var value = style.getPropertyValue(property);
+      styleStr += "\t" + property + ': ' + value + ";\n";
+    }
+    var result = document.getElementById('result');
+    result.innerHTML = rule.selectorText + " {\n" + styleStr + "}";
+  }
+
+  var css = document.getElementById('css-to-parse').innerText;
+  CSSOMToString(parse(css));
+</script>
+

Results

Here some results (using latest versions + IE11)

As you see, this is what we get for asking only 7 properties, who adds more or less, someone converts colors to rgba functions and someone else changes the order of our values (eg. box-shadow). Webkit-based browsers attach also properties they self don't understand

So it's clear that we can't rely on CSSOM objects, that's why we added the possibility to set custom CSS parser via editor.setCustomParserCss method or config.Parser.parserCss option to use on initialization. Let's see in detail how it's expected to work

Set CSS parser

The custom parser you have to use it's just a function receiving 2 arguments: css, as the CSS string to parse, and editor, the instance of the current editor. As the result, you should return an array containing valid rule objects, the syntax of those objects are explained below. This is how you can set the custom parser

const parserCss = (css, editor) => {
+  const result = [];
+  // ... parse the CSS string
+    result.push({
+      selectors: '.someclass, div .otherclass',
+      style: { color: 'red' }
+    })
+  // ...
+  return result; // Result should be ALWAYS an array
+};
+
+// On initialization
+// This is the recommended way, as you gonna use the parser from the beginning
+const editor = grapesjs.init({
+  //...
+  parser: {
+    parserCss,
+  }
+});
+
+// Or later, via editor API
+editor.setCustomParserCss(parserCss);
+

Rule Objects

The syntax of rule objects is pretty straightforward, each object might contain following keys

KeyDescriptionExample
selectorsSelectors of the rule.
REQUIRED return an empty string in case the rule has no selectors
.class1, div > #someid
styleStyle declarations as an object{ color: 'red' }
atRuleAt-rule namemedia
paramsParameters of the at-rulescreen and (min-width: 480px)

To make it more clear let's see a few examples

// Input
+`
+@font-face {
+  font-family: "Font Name";
+  src: url("https://font-url.eot");
+}
+`
+// Output
+[
+  {
+    selectors: '',
+    atRule: 'font-face',
+    style: {
+      'font-family': '"Font Name"',
+      src: 'url("https://font-url.eot")',
+    },
+  }
+]
+
+// Input
+`
+@keyframes keyframe-name {
+  from { opacity: 0; }
+  to { opacity: 1; }
+}
+`
+// Output
+[
+  {
+    params: 'keyframe-name',
+    selectors: 'from',
+    atRule: 'keyframes',
+    style: {
+      opacity: '0',
+    },
+  }, {
+    params: 'keyframe-name',
+    selectors: 'to',
+    atRule: 'keyframes',
+    style: {
+      opacity: '1',
+    },
+  }
+]
+
+// Input
+`
+@media screen and (min-width: 480px) {
+    body {
+        background-color: lightgreen;
+    }
+
+    .class-test, .class-test2:hover {
+      color: blue !important;
+    }
+}
+`
+// Output
+[
+  {
+    params: 'screen and (min-width: 480px)',
+    selectors: 'body',
+    atRule: 'media',
+    style: {
+      'background-color': 'lightgreen',
+    },
+  }, {
+    params: 'screen and (min-width: 480px)',
+    selectors: '.class-test, .class-test2:hover',
+    atRule: 'media',
+    style: {
+      color: 'blue !important',
+    },
+  }
+]
+
+// Input
+`
+:root {
+  --some-color: red;
+  --some-width: 55px;
+}
+`
+// Output
+[
+  {
+    selectors: ':root',
+    style: {
+      '--some-color': 'red',
+      '--some-width': '55px',
+    },
+  },
+]
+

Plugins

Below the list of current available CSS parsers as plugins, if you need to create your own we highly suggest to explore their sources

Last Updated: 9/20/2018, 8:20:29 AM
+ + + diff --git a/docs/guides/Replace-Rich-Text-Editor.html b/docs/guides/Replace-Rich-Text-Editor.html new file mode 100644 index 000000000..c310e8a93 --- /dev/null +++ b/docs/guides/Replace-Rich-Text-Editor.html @@ -0,0 +1,106 @@ + + + + + + Replace the built-in Rich Text Editor | GrapesJS + + + + + + + + + +

Replace the built-in Rich Text Editor

As you might have noticed the default Rich Text Editor (RTE) is really tiny and so doesn't seem like a complete solution as a text editor. Instead of showing how to add new commands inside the default one we'll show how to completely replace it with another one.

In the following guide we'll integrate the CKEditor and to accomplish this task we just need to provide few functions to the GrapesJS API method setCustomRte as an interface.

Interface

Enable

The first step is to indicate how to enable the third-party library and so for we gonna start with the enable() function. This method should take care of the first initialization of our custom RTE but also for the next time is called on the same element, this is why there is the rte argument.

var editor = grapesjs.init({...});
+editor.setCustomRte({
+  /**
+   * Enabling the custom RTE
+   * @param  {HTMLElement} el This is the HTML node which was selected to be edited
+   * @param  {Object} rte It's the instance you'd return from the first call of enable().
+   *                      At the first call it'd be undefined. This is useful when you need
+   *                      to check if the RTE is already enabled on the component
+   * @return {Object} The return should be the RTE initialized instance
+   */
+  enable: function(el, rte) {
+    // If already exists just focus
+    if (rte) {
+      this.focus(el, rte); // implemented later
+      return rte;
+    }
+
+    // CKEditor initialization
+    rte = CKEDITOR.inline(el, {
+      // Your configurations...
+      toolbar: [...],
+      // IMPORTANT
+      // Generally, inline editors are attached exactly at the same position of
+      // the selected element but in this case it'd work until you start to scroll
+      // the canvas. For this reason you have to move the RTE's toolbar inside the
+      // one from GrapesJS. For this purpose we used a plugin which simplify
+      // this process and move all next CKEditor's toolbars inside our indicated
+      // element
+      sharedSpaces: {
+        top: editor.RichTextEditor.getToolbarEl(),
+      }
+    });
+
+    this.focus(el, rte); // implemented later
+    return rte;
+  },
+});
+

Disable

Once we know how to enable the RTE let's implement the method which disable it, so let's create the disable() function.

editor.setCustomRte({
+  // ...
+  /**
+   * The signature of the function is the same of the enable
+   */
+  disable: function(el, rte) {
+    el.contentEditable = false;
+    if (rte && rte.focusManager) {
+      rte.focusManager.blur(true);
+    }
+  },
+});
+

Focus

The focus() method is just a helper used inside enable() and not required by the interface

editor.setCustomRte({
+  // ...
+  focus: function (el, rte) {
+    // Do nothing if already focused
+    if (rte && rte.focusManager.hasFocus) {
+      return;
+    }
+    el.contentEditable = true;
+    rte && rte.focus();
+  },
+});
+

Toolbar position

Sometimes the default top-left position of the toolbar is not always what you need. For example, when you scroll the canvas and the toolbar reaches the top, you'd like to move it down. For this purpose, you can add a listener which applies your logic in this way:

editor.on('rteToolbarPosUpdate', (pos) => {
+  if (pos.top <= pos.canvasTop) {
+    pos.top = pos.elementTop + pos.elementHeight;
+  }
+});
+

The built-in vs third-party

The only one thing you have to keep in mind when using a custom RTE is that all the content and its behavior are handled by the library itself, the GrapesJS's component will just store the content as it is. +For example, when you create a link using the built-in RTE then you'll be able to select it and edit its href via Component Settings. With a custom RTE, it will be its own task to show the proper modal for the link editing. +Obviously, each third-party library has its own APIs and can present some limitations and drawbacks, so, a minimal knowledge of the library is a plus.

Plugins

For the CKEditor, you can find a complete plugin here grapesjs-plugin-ckeditor.

Last Updated: 7/7/2018, 4:23:32 PM
+ + + diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 000000000..6bd86840f --- /dev/null +++ b/docs/index.html @@ -0,0 +1,42 @@ + + + + + + Introduction | GrapesJS + + + + + + + + + +

Introduction

What is GrapesJS?

At first glance might think this is just another page/HTML builder, but it's something more. GrapesJS is a multi-purpose, Web Builder Framework, which means it allows you to easily create a drag & drop enabled builder of "things". By "things" we mean anything with HTML-like structure, which entails much more than web pages. We use HTML-like structure basically everywhere: Newsletters (eg. MJML), Native Mobile Applications (eg. React Native), Native Desktop Applications (eg. Vuido), PDFs (eg. React PDF), etc. So, for everything you can imagine as a set of elements like <tag some="attribute">... other nested elements ...</tag> you can create easily a GrapesJS builder around it and then use it independently your applications. +GrapesJS ships with features and tools that enable you to craft easy to use builders. Which allows your users to create complex HTML-like templates without any knowledge of coding.

Why GrapesJS?

GrapesJS was designed primarily to for use inside Content Management Systems to speed up the creation of dynamic templates and replace common WYSIWYG editors. Which are good for content editing, but inappropriate for creating HTML structures. Instead of creating an application we decided to create an extensible framework that could be used by anyone for any purpose.

Quick Start

To showcase the power of GrapesJS we have created some presets.

You can actually use them as a starting point for your editors, so, just follow the instructions on their repositories to get a quick start for your builder.

Download

Latest version: npm

You can download GrapesJS from one of these sources

  • CDNs +
    • unpkg +
      • https://unpkg.com/grapesjs
      • https://unpkg.com/grapesjs/dist/css/grapes.min.css
    • cdnjs +
      • https://cdnjs.cloudflare.com/ajax/libs/grapesjs/0.12.17/grapes.min.js
      • https://cdnjs.cloudflare.com/ajax/libs/grapesjs/0.12.17/css/grapes.min.css
  • npm +
    • npm i grapesjs
  • git +
    • git clone https://github.com/artf/grapesjs.git

Changelog

To track changes made in the library we rely on Github Releases

Last Updated: 10/25/2018, 11:55:39 PM
+ + + diff --git a/docs/modules/Assets.html b/docs/modules/Assets.html new file mode 100644 index 000000000..3f5c65493 --- /dev/null +++ b/docs/modules/Assets.html @@ -0,0 +1,372 @@ + + + + + + Asset Manager | GrapesJS + + + + + + + + + +

Asset Manager

GrapesJS - Asset Manager

In this section, you will see how to setup and take the full advantage of built-in Asset Manager in GrapesJS. The Asset Manager is 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.

Configuration

To change default configurations you'll have to pass assetManager property with the main configuration object

const editor = grapesjs.init({
+  ...
+  assetManager: {
+    assets: [...],
+    ...
+  }
+});
+

You can update most of them later by using getConfig inside of the module

const amConfig = editor.AssetManager.getConfig();
+

Below is a list of currently available options

  // 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 <b>assets</b> 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: {},
+
+  // The credentials setting for the upload request, eg. 'include', 'omit'
+  credentials: 'include',
+
+  // Allow uploading multiple files per request.
+  // If disabled filename will not have '[]' appended
+  multiUpload: true,
+
+  // 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',
+

Sometimes the code gets ahead of the docs, therefore we'd suggest to keep an eye at the current state of configurations by checking the dedicated source file Asset Manager Config

Initialization

The Asset Manager is ready to work by default, so pass few URLs to see them loaded

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 out the source AssetImage Model

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)

Making the modal appear is registered with a command, so you can make it appear with this

// 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)

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

// 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 = [...])

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 an argument, you will see all the assets rendered

// without any argument
+am.render();
+
+am.getAll().length // <- 3
+am.getAllVisible().length // <- 3
+

Ok, now let's show only assets form the first category

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
+

You can also mix arrays of assets

am.render([...assets1, ...assets2, ...assets3]);
+

If you want to customize the asset manager container you can get its HTMLElement

am.getContainer().insertAdjacentHTML('afterbegin', '<div><button type="button">Click</button></div>');
+

For more APIs methods check out the API Reference

Define new Asset type

Generally speaking, images aren't the only asset you'll use, it could be a video, svg-icon, or any other kind of document. Each type of 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. However In case of a svg-icon, its not the same, you might want to replace the element with a new <svg> content. Besides this you also have to deal with the presentation/preview of the asset inside the panel/modal. 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 over by the editor at any addition of the asset and tries to associate the correct type.

am.add('https://.../image.png');
+// string, url, ends with '.png' -> it's an 'image' type
+
+am.add('<svg ...');
+// string and starts with '<svg...' -> 'svg' type
+
+am.add({type: 'video', src: '...'});
+// an object, has 'video' type key -> 'video' type
+

It's up to you tell the editor how to recognize your type and for this purpose you should to use isType() method. +Let's see now an example of how we'd start to defining a type like svg-icon

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) == '<svg ') {
+      return {
+        type: 'svg-icon',
+        svgContent: value
+      };
+    }
+    // Maybe you pass the `svg-icon` object already
+    else if (typeof value == 'object' && value.type == 'svg-icon') {
+      return value;
+    }
+  }
+})
+

With this snippet you can already add SVGs, the asset manager will assign the appropriate type.

// Add some random SVG
+am.add(`<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
+  <path d="M22,9 C22,8.4 21.5,8 20.75,8 L3.25,8 C2.5,8 2,8.4 2,9 L2,15 C2,15.6 2.5,16 3.25,16 L20.75,16 C21.5,16 22,15.6 22,15 L22,9 Z M21,15 L3,15 L3,9 L21,9 L21,15 Z"></path>
+  <polygon points="4 10 5 10 5 14 4 14"></polygon>
+</svg>`);
+

The default open-assets command shows only image assets, so to render svg-icon run this

am.render(am.getAll().filter(
+  asset => asset.get('type') == 'svg-icon'
+));
+

You should see something like this

The SVG asset is not rendered correctly and this is because we haven't yet configured its view

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 `<div style="text-align: center">${this.model.get('svgContent')}</div>`;
+    },
+    getInfo() {
+      // You can use model's properties if you passed them:
+      // am.add({
+      //  type: 'svg-icon',
+      //  svgContent: '<svg ...',
+      //  name: 'Some name'
+      //  })
+      //  ... then
+      //  this.model.get('name');
+      return '<div>SVG description</div>';
+    },
+  },
+  isType(value) {...}
+})
+

This is the result

Now we have to deal with how to assign our svgContent to the selected element

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,<svg ...` but you
+        // have to escape few chars
+        target.set('src', `data:mime/type;base64,${btoa(svg)}`);
+      } else {
+        target.set('content', svg);
+      }
+    },
+    ...
+  },
+  isType(value) {...}
+})
+

Our custom svg-icon asset is ready to use. You can also add a model to the addType definition to group the business logic of your asset, but usually it's optional.

// Just an example of model use
+am.addType('svg-icon', {
+  model: {
+    // With `default` you define model's default properties
+    defaults: {
+      type:  'svg-icon',
+      svgContent: '',
+      name: 'Default SVG Name',
+    },
+
+    // You can call model's methods inside views:
+    // const name = this.model.getName();
+    getName() {
+      return this.get('name');
+    }
+  },
+  view: {...},
+  isType(value) {...}
+})
+

Extend Asset Types

Extending asset types is basically the same as adding them, you can choose what type to extend and how.

// svgIconType will contain the definition (model, view, isType)
+const svgIconType = am.getType('svg-icon');
+
+// Add new type and extend another one
+am.addType('svg-icon2', {
+  view: svgIconType.view.extend({
+    getInfo() {
+      return '<div>SVG2 description</div>';
+    },
+  }),
+  // 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)

// 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 with a few UI helpers. The default uploader is already visible when you open the Asset Manager.

You can click on the uploader to select your files or just drag them directly from your computer to trigger the uploader. Obviously, before it will work you have to setup your server to receive your assets and specify the upload endpoint in your configuration

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 an action before/after the uploading process (eg. loading animation) or even on response, you can make use of these listeners

// 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 blob 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:

{
+  data: [
+    'https://.../image.png',
+    // ...
+    {
+      src: 'https://.../image2.png',
+      type: 'image',
+      height: 100,
+      width: 200,
+    },
+    // ...
+  ]
+}
+

Setup Dropzone

There is another helper which improves the uploading of assets: A full-width editor dropzone.

All you have to do is to activate it and possibly set a custom content (you might also want to hide the default uploader)

const editor = grapesjs.init({
+  ...
+  assetManager: {
+    ...,
+    dropzone: 1,
+    dropzoneContent: '<div class="dropzone-inner">Drop here your assets</div>'
+  }
+});
+

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
Last Updated: 10/26/2018, 1:52:41 PM
+ + + diff --git a/docs/modules/Blocks.html b/docs/modules/Blocks.html new file mode 100644 index 000000000..95aab6543 --- /dev/null +++ b/docs/modules/Blocks.html @@ -0,0 +1,94 @@ + + + + + + Block Manager | GrapesJS + + + + + + + + + +

Block Manager

GrapesJS - Block Manager

The Block is a group of Components and can be easily reused inside templates.

The difference between components and blocks: The component is more atomic, so a single image, a text box or a map is a component. 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 out the 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

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: '<div class="my-block">This is a simple block</div>',
+});
+

With this snippet a new block will be added to the collection. You can also update existent blocks

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.

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 v0.3.70 it's also possible to pass the HTML string with Component's properties as attributes.

blockManager.add('the-row-block', {
+  label: '2 Columns',
+  content: '<div class="row" data-gjs-droppable=".row-cell" data-gjs-custom-name="Row">' +
+      '<div class="row-cell" data-gjs-draggable=".row"></div>' +
+      '<div class="row-cell" data-gjs-draggable=".row"></div>' +
+    '</div>',
+});
+

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

Custom render 0.14.55+

If you need to customize the aspect of each block you can pass a render callback function in the block definition. Let's see how it works.

As a first option, you can return a simple HTML string, which will be used as a new inner content of the block. As an argument of the callback you will get an object containing the following properties:

  • model - Block's model (so you can use any passed property to it)
  • el - Current rendered HTMLElement of the block
  • className - The base class name used for blocks (useful if you follow BEM, so you can create classes like ${className}__elem)
blockManager.add('some-block-id', {
+  label: `<div>
+      <img src="https://picsum.photos/70/70"/>
+      <div class="my-label-block">Label block</div>
+    </div>`,
+  content: '<div>...</div>',
+  render: ({ model, className }) => `<div class="${className}__my-wrap">
+      Before label
+      ${model.get('label')}
+      After label
+    </div>`,
+});
+

Another option would be to avoid returning from the callback (in that case nothing will be replaced) and edit only the current el block element

blockManager.add('some-block-id', {
+  // ...
+  render: ({ el }) => {
+    const btn = document.createElement('button');
+    btn.innerHTML = 'Click me';
+    btn.addEventListener('click', () => alert('Do something'))
+    el.appendChild(btn);
+  },
+});
+
Last Updated: 2/24/2019, 4:18:50 PM
+ + + diff --git a/docs/modules/Commands.html b/docs/modules/Commands.html new file mode 100644 index 000000000..7cfbd8289 --- /dev/null +++ b/docs/modules/Commands.html @@ -0,0 +1,169 @@ + + + + + + Commands | GrapesJS + + + + + + + + + +

Commands

A basic command in GrapesJS it's a simple function, but you will see in this guide how powerful they can be. The main goal of the Command module is to centralize functions and be easily reused across the editor. Another big advantage of using commands is the ability to track them, extend or even interrupt beside some conditions.

WARNING

This guide is referring to GrapesJS v0.14.61 or higher

Basic configuration

You can create your commands already from the initialization step by passing them in the commands.defaults options:

const editor = grapesjs.init({
+  ...
+  commands: {
+    defaults: [
+      {
+        // id and run are mandatory in this case
+        id: 'my-command-id',
+        run() {
+          alert('This is my command');
+        },
+      }, {
+        id: '...',
+        // ...
+      }
+    ],
+  }
+});
+

For all other available options check directly the configuration source file.

Most commonly commands are created dynamically post-initialization, in that case, you'll need to use the Commands API (eg. this is what you need if you create a plugin)

const commands = editor.Commands;
+commands.add('my-command-id', editor => {
+  alert('This is my command');
+});
+
+// or it would be the same...
+commands.add('my-command-id', {
+  run(editor) {
+    alert('This is my command');
+  },
+});
+

As you see the definition is quite easy, you just add an ID and the callback function. The Editor instance is passed as the first argument to the callback so you can access any other module or API method.

Now if you want to call that command you should just run this

editor.runCommand('my-command-id');
+

TIP

The method editor.runCommand is an alias of editor.Commands.run

You could also pass options if you need

editor.runCommand('my-command-id', { some: 'option' });
+

Then you can get the same object as a third argument of the callback.

commands.add('my-command-id', (editor, sender, options = {}) => {
+  alert(`This is my command ${options.some}`);
+});
+

The second argument, sender, just indicates who requested the command, in our case will be always the editor

Until now there is nothing exciting except a common entry point for functions, but we'll see later its real advantages.

Default commands

GrapesJS comes along with some default set of commands and you can get a list of all currently available commands via editor.Commands.getAll(). This will give you an object of all available commands, so, also those added later, like via plugins. You can recognize default commands by their namespace core:*, we also recommend to use namespaces in your own custom commands, but let's get a look more in detail here:

Stateful commands

As we've already seen the command is just a function and once executed nothing is left behind, but in some cases, we'd like to keep a track of executed commands. GrapesJS can handle by default this case and to enable it you just need to declare a command as an object with the run and stop methods

commands.add('my-command-state', {
+  run(editor) {
+    alert('This command is now active');
+  },
+  stop(editor) {
+    alert('This command is disabled');
+  },
+});
+

So if we now run editor.runCommand('my-command-state') the command will be registered as active. To check the state of the command you can use commands.isActive('my-command-state') or you can even get the list of all active commands via commands.getActive(), in our case the result would be something like this

{
+  ...
+  'my-command-state': undefined
+}
+

The key of the result object tells you the active command, the value is the last return of the run command, in our case is undefined because we didn't return anything, but it's up to your implementation decide what to return and if you actually need it.

// Let's return something
+...
+run(editor) {
+    alert('This command is now active');
+    return {
+      activated: new Date(),
+    }
+},
+...
+// Now instead of the `undefined` you'll see the object from the run method
+

To disable the command use editor.stopCommand method, so in our case it'll be editor.stopCommand('my-command-state'). As for the runCommand you can pass an options object as a second argument and use them in your stop method.

Once the command is active, if you try to run editor.runCommand('my-command-state') again you'll notice that that the run is not triggering. This behavior is useful to prevent executing multiple times the activation process which might lead to an inconsistent state (think about, for instance, having a counter, which should be increased on run and decreased on stop). If you need to run a command multiple times probably you're dealing with a not stateful command, so try to use it without the stop method, but in case you're aware of your application state you can actually force the execution with editor.runCommand('my-command-state', { force: true }). The same logic applies to the stopCommand method.


WARNING

If you deal with UI in your stateful commands, be careful to keep the state coherent with your logic. Let's take, for example, the use of a modal as an indicator of the command state.

commands.add('my-command-modal', {
+  run(editor) {
+    editor.Modal.open({
+      title: 'Modal example',
+      content: 'My content',
+    });
+  },
+  stop(editor) {
+    editor.Modal.close();
+  },
+});
+

If you run it, close the modal (eg. by clicking the 'x' on top) and then try to run it again you'll see that the modal is not opening anymore. This happens because the command is still active (you should see it in commands.getActive()) and to fix it you have to disable it once the modal is closed.

...
+  run(editor) {
+    editor.Modal.open({
+      title: 'Modal example',
+      content: 'My content',
+    }).onceClose(() => this.stopCommand());
+  },
+...
+

In the example above, we make use of few helper methods from the Modal module (onceClose) and the command itself (stopCommand) but obviously, the logic might be different due to your requirements and specific UI.

Extending

Another big advantage of commands is the possibility to easily extend or override them with another command. +Let's take a simple example

commands.add('my-command-1', editor => {
+  alert('This is command 1');
+});
+

If you need to overwrite this command with another one, just add it and keep the same id.

commands.add('my-command-1', editor => {
+  alert('This is command 1 overwritten');
+});
+

Let's see now instead how can we extend one

commands.add('my-command-2', {
+  someFunction1() {
+    alert('This is function 1');
+  },
+  someFunction2() {
+    alert('This is function 2');
+  },
+  run() {
+    this.someFunction1();
+    this.someFunction2();
+  },
+});
+

to extend it just use extend method by passing the id

commands.extend('my-command-2', {
+  someFunction2() {
+    alert('This is function 2 extended');
+  },
+});
+

Events

The Commands module offers also a set of events that you can use to intercept the command flow for adding more functionality or even interrupting it.

Intercept run and stop

By using our previosly created my-command-modal command let's see which events we can listen to

editor.on('run:my-command-modal', () => {
+  console.log('After `my-command-modal` execution');
+  // For example, you can add extra content to the modal
+  const modalContent = editor.Modal.getContentEl();
+  modalContent.insertAdjacentHTML('beforeEnd', '<div>Some content</div>');
+});
+editor.on('run:my-command-modal:before', () => {
+  console.log('Before `my-command-modal` execution');
+});
+// for stateful commands
+editor.on('stop:my-command-modal', () => {
+  console.log('After `my-command-modal` is stopped');
+});
+editor.on('stop:my-command-modal:before', () => {
+  console.log('Before `my-command-modal` is stopped');
+});
+

If you need, you can also listen to all commands

editor.on('run', commandId => {
+  console.log('Run', commandId);
+});
+
+editor.on('stop', commandId => {
+  console.log('Stop', commandId);
+});
+

Interrupt command flow

Sometimes you might need to interrupt the execution of an existant command due to some condition. In that case, you have to use run:{COMMAND-ID}:before event and set to true the abort option

const condition = 1;
+
+editor.on('run:my-command-modal:before', options => {
+  if (condition) {
+    options.abort = true;
+    console.log('Prevent `my-command-modal` from execution');
+  }
+});
+

Conclusion

The Commands module is quite simple but, at the same time, really powerful if used correctly. So, if you're creating a plugin for GrapesJS, use commands as much as possible, this will allow higher reusability and control over your logic.

Last Updated: 5/2/2019, 12:58:23 AM
+ + + diff --git a/docs/modules/Components-js.html b/docs/modules/Components-js.html new file mode 100644 index 000000000..abff60e9b --- /dev/null +++ b/docs/modules/Components-js.html @@ -0,0 +1,168 @@ + + + + + + Components & JS | GrapesJS + + + + + + + + + +

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.)

Basic scripts

Let's see how to create a component with scripts using Blocks.

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 and the message in console, as you might expect. +One thing worth noting is that this context is bound to the component element, so if you wanted to change a property you'd do this.innerHTML = 'inner content'.

One thing you should take in account is how the script is bound 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:

<div id="c764"></div>
+<script>
+  var items = document.querySelectorAll('#c764');
+  for (var i = 0, len = items.length; i < len; i++) {
+    (function(){
+      // START component code
+      alert('Hi');
+      console.log('the element', this)
+      // END component code
+    }.bind(items[i]))();
+  }
+</script>
+

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:

<div id="c764"></div>
+<div id="c765"></div>
+<script>
+  var items = document.querySelectorAll('#c764, #c765');
+  for (var i = 0, len = items.length; i < len; i++) {
+    (function(){
+      // START component code
+      alert('Hi');
+      console.log('the element', this)
+      // END component code
+    }.bind(items[i]))();
+  }
+</script>
+

Keep in mind that all component scripts are executed only inside the iframe of the canvas (isolated, just like your final template), and therefore are NOT part of the current document. All your external libraries (eg. jQuery) are not there, but you'll see later 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:

editor.BlockManager.add('test-block', {
+  ...
+  content: {
+    script: function () {
+      alert('Hi');
+      console.log('the element', this);
+    },
+    ...
+  }
+});
+

This is much better, but be aware of a string conversion, you can't use variables outside of the function scope. Take a look at this scenario:

var myVar = 'John';
+
+editor.BlockManager.add('test-block', {
+...
+  script: function () {
+    alert('Hi ' + myVar);
+    console.log('the element', this);
+  },
+...
+});
+

Unfortunately, this won't work. You'll get an undefined myVar error. The final HTML, with script functions converted to string, will look like this:

<div id="c764"></div>
+<script>
+  var items = document.querySelectorAll('#c764');
+  for (var i = 0, len = items.length; i < len; i++) {
+    (function(){
+      // START component code
+      alert('Hi ' + myVar); // <- ERROR: undefined myVar
+      console.log('the element', this);
+      // END component code
+    }.bind(items[i]))();
+  }
+</script>
+

There is a solution to make your scripts behave dynamically. You can interpolate properties of the component model.

editor.BlockManager.add('test-block', {
+  ...
+  content: {
+    myModelPropName: 'John',
+    script: function () {
+      alert('Hi {[ myModelPropName ]}');
+      console.log('the element', this);
+    },
+    ...
+  }
+});
+

The final HTML will be:

<div id="c764"></div>
+<script>
+  var items = document.querySelectorAll('#c764');
+  for (var i = 0, len = items.length; i < len; i++) {
+    (function(){
+      alert('Hi John');
+      console.log('the element', this);
+    }.bind(items[i]))();
+  }
+</script>
+

You can even change the tags used for the interpolation

var editor = grapesjs.init({
+  ...
+  // Default values
+  tagVarStart: '{[ ',
+  tagVarEnd: ' ]}',
+  ...
+});
+

You can use this technique with property Traits to create custom components.

Dependencies

As we mentioned above, scripts are executed independently inside the iframe of the canvas, without any dependencies, so exactly as the final HTML generated by the editor. +If you want to make use of external libraries you have two approaches: component-related and template-related.

If you're building 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, the component-related approach is the perfect one as it's loading external libraries dynamically. +All you have to do is to require the dependency when it is needed and then call your script.

...
+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);
+  }
+},
+...
+

A dependency might be used along all your components (eg. JQuery) so instead requiring it inside each script you might want to inject it directly inside the canvas:

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

Last Updated: 10/29/2018, 4:01:56 AM
+ + + diff --git a/docs/modules/Components.html b/docs/modules/Components.html new file mode 100644 index 000000000..dffdc2f5c --- /dev/null +++ b/docs/modules/Components.html @@ -0,0 +1,315 @@ + + + + + + Component Manager | GrapesJS + + + + + + + + + +

Component Manager

The Component is the base element for template composition. It is atomic, so elements like images, text boxes, maps, etc. fit the definition of a Component. The concept of the component was made to allow the developer to bind different behaviors to different elements. Like for example, opening the Asset Manager on double click of the image.

Built-in components

  • default (Basic)
  • wrapper
  • text
  • textnode
  • svg
  • script
  • image
  • video
  • label
  • 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:

<div>
+  <img src="https://path/image" />
+  <span title="foo">bar</span>
+</div>
+

For each DOM element (div, img, span, etc.) the editor will create and store an object representation. Every future change to the template will be made on top of this structure, which will then reflect on the canvas. So each object, usually called Model (or state/store), will be the source of truth for the template, but what exactly does that mean?

In more practical example, once the template is rendered on the canvas, if you try to remove one of its elements (eg. by using using the browser inspector) and ask the editor to print the HTML (using editor.getHtml()) you'll see that the element will still be there. This is because the editor relies on Models and not on the DOM elements inside the canvas. This approach allows us to be extremely flexible on how we generate the final code (from the Model) and how to render it inside the canvas (from the View).

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

/**
+ * @param {HTMLElement} el
+ * @return {Object}
+ */
+isComponent: function(el) {
+  ...
+}
+

This method gives us the possibility to recognize and bind component types to each HTMLElement (div, img, iframe, etc.). Each HTML string/element introduced inside the canvas will be processed by isComponent of all available types and if it matches, the object represented the type should be returned. The method isComponentis skipped if you add the component object ({ type: 'my-custom-type', tagName: 'div', attribute: {...}, ...}) or declare the type explicitly on the element (<div data-gjs-type="my-custom-type">...</div>)

For example, with the image component this method looks like:

// Image component
+isComponent: function(el) {
+  if(el.tagName == 'IMG')
+    return {type: 'image'};
+}
+

Let's try with something that might look a little bit tricky. What about a Google Map?!? Google Maps are generally embedded as iframes, but the template can be composed by a lot of different iframes. How can I tell the editor that a particular iframe is actually a Google's Map? Well, you'll have to figure out the right pattern, you have the HTMLElement so you can make all the checks you want. In this particular case this pattern is used:

// Map component
+isComponent: function(el) {
+	if(el.tagName == 'IFRAME' && /maps\.google\.com/.test(el.src)) {
+		return {type: 'map', src: el.src};
+	}
+},
+

In addition to tagName check, we also used the src property, but you can actually override it with your own logic by extending the built-in component.

Define new Component

Let's see an example with another HTML element that is not handled by default Component types. What about input elements?

With the default GrapesJS configuration inputs are treated like any other element; you can move it around, style it, etc. However, we'd like to handle this type of element more specifically. In this case, we have to create a new Component type.

Let's define few specs for our new Input type:

  • Can be dropped only inside form elements
  • Can't drop other elements inside it
  • Can change the type of the input (text, password, email, etc.)
  • Can make it required for the form

To define a new Component type you need to choose from which built-in Component inherit its properties, in our case we just gonna choose the default one. Let's see a complete example of the new type definition

// 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.

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

var originalMap = comps.getType('map');
+
+comps.addType('map', {
+  model: originalMap.model.extend({
+    // Override how the component is rendered to HTML
+    toHTML: function() {
+      return '<div>My Custom Map</div>';
+    },
+  }, {
+    isComponent: function(el) {
+      // ... new logic for isComponent
+		},
+  }),
+  view: originalMap.view
+});
+

Improvement over addType 0.14.50+

Now, with the 0.14.50 release, defining new components or extending them is a bit easier (without breaking the old process)

  • If you don't specify the type to extend, the default one will be used. In that case, you just +use objects for model and view
  • The defaults property, in the model, will be merged automatically with defaults of the parent component
  • If you use an object in model you can specify isComponent outside or omit it. In this case, +the isComponent is not mandatory but without it means the parser won't be able to identify the component +if not explicitly declared (eg. <div data-gjs-type="new-component">...</div>)

Before

const defaultType = comps.getType('default');
+
+comps.addType('new-component', {
+  model: defaultType.model.extend({
+    defaults: {
+      ...defaultType.model.prototype.defaults,
+      someprop: 'somevalue',
+    },
+    ...
+  }, {
+    // Even if it returns false, declaring isComponent is mandatory
+    isComponent(el) {
+      return false;
+    },
+  }),
+  view: defaultType.view.extend({ ... });
+});
+

After

comps.addType('new-component', {
+  // We can even omit isComponent here, as `false` return will be the default behavior
+  isComponent: el => false,
+  model: {
+    defaults: {
+      someprop: 'somevalue',
+    },
+    ...
+  },
+  view: { ... };
+});
+
  • If you need to extend some component, you can use extend and extendView property.
  • You can now omit view property if you don't need to change it

Before

const originalMap = comps.getType('map');
+
+comps.addType('map', {
+  model: originalMap.model.extend({
+    ...
+  }, {
+    isComponent(el) {
+      // ... usually, you'd reuse the same logic
+    },
+  }),
+  // Even if I do nothing in view, I have to specify it
+  view: originalMap.view
+});
+

After

The map type is already defined, so it will be used as a base for the model and view. +We can skip isComponent if the recognition logic is the same of the extended component.

comps.addType('map', {
+  model: { ... },
+});
+

Extend the model and view with some other, already defined, components.

comps.addType('map', {
+  extend: 'other-defined-component',
+  model: { ... }, // Will extend 'other-defined-component'
+  view: { ... }, // Will extend 'other-defined-component'
+  // `isComponent` will be taken from `map`
+});
+
comps.addType('map', {
+  extend: 'other-defined-component',
+  model: { ... }, // Will extend 'other-defined-component'
+  extendView: 'other-defined-component-2',
+  view: { ... }, // Will extend 'other-defined-component-2'
+  // `isComponent` will be taken from `map`
+});
+

Extend parent functions 0.14.60+

When you need to reuse functions, of the parent you're extending, you can avoid writing something like this in any function:

domc.getType('parent-type').model.prototype.init.apply(this, arguments);
+

by using extendFn and extendFnView arrays:

domc.addType('new-type', {
+  extend: 'parent-type',
+  extendFn: ['init'], // array of model functions to extend
+  model: {
+    init() {
+      // do something;
+    },
+  }
+});
+

The same would be for the view by using extendFnView

Lifecycle Hooks

Each component triggers different lifecycle hooks, which allows you to add custom actions at their specific stages. +We can distinguish 2 different types of hooks: global and local. +You define local hooks when you create/extend a component type (usually via some model/view method) and the reason is to react to an event of that +particular component type. Instead, the global one, will be called indistinctly on any component (you listen to them via editor.on) and you can make +use of them for a more generic use case or also listen to them inside other components.

Let's see below the flow of all hooks:

  • Local hook: model.init() method, executed once the model of the component is initiliazed
  • Global hook: component:create event, called right after model.init(). The model is passed as an argument to the callback function. +Es. editor.on('component:create', model => console.log('created', model))
  • Local hook: view.init() method, executed once the view of the component is initiliazed
  • Local hook: view.onRender() method, executed once the component is rendered on the canvas
  • Global hook: component:mount event, called right after view.onRender(). The model is passed as an argument to the callback function.
  • Local hook: model.updated() method, executes when some property of the model is updated.
  • Global hook: component:update event, called after model.updated(). The model is passed as an argument to the callback function. +You can also listen to specific property change via component:update:{propertyName}
  • Local hook: model.removed() method, executed when the component is removed.
  • Global hook: component:remove event, called after model.removed(). The model is passed as an argument to the callback function.

Below you can find an example usage of all the hooks

editor.DomComponents.addType('test-component', {
+  model: {
+    defaults: {
+      testprop: 1,
+    },
+    init() {
+      console.log('Local hook: model.init');
+      this.listenTo(this, 'change:testprop', this.handlePropChange);
+      // Here we can listen global hooks with editor.on('...')
+    },
+    updated(property, value, prevValue) {
+      console.log('Local hook: model.updated',
+        'property', property, 'value', value, 'prevValue', prevValue);
+    },
+    removed() {
+      console.log('Local hook: model.removed');
+    },
+    handlePropChange() {
+      console.log('The value of testprop', this.get('testprop'));
+    }
+  },
+  view: {
+    init() {
+      console.log('Local hook: view.init');
+    },
+    onRender() {
+      console.log('Local hook: view.onRender');
+    },
+  },
+});
+
+// A block for the custom component
+editor.BlockManager.add('test-component', {
+  label: 'Test Component',
+  content: '<div data-gjs-type="test-component">Test Component</div>',
+});
+
+// Global hooks
+editor.on(`component:create`, model => console.log('Global hook: component:create', model.get('type')));
+editor.on(`component:mount`, model => console.log('Global hook: component:mount', model.get('type')));
+editor.on(`component:update:testprop`, model => console.log('Global hook: component:update:testprop', model.get('type')));
+editor.on(`component:remove`, model => console.log('Global hook: component:remove', model.get('type')));
+

Components & JS

If you want to know how to create Components with javascript attached (eg. counters, galleries, slideshows, etc.) check the dedicated page +Components & JS

Hints

<div id="gjs">
+ ...
+ <cutom-element></cutom-element>
+ ...
+</div>
+
+<script>
+ var editor = grapesjs.init({
+      container : '#gjs',
+      fromElement: true,
+  });
+
+  editor.DomComponents.addType('cutom-element-type', {...});
+</script>
+

In the example above the editor will not get the new type from the HTML because the content is already parsed and appended, so it'll get it only with new components (eg. from Blocks)

Solution 1: turn off autorender

<script>
+ var editor = grapesjs.init({
+      autorender: 0,
+      container : '#gjs',
+      fromElement: true,
+  });
+
+  editor.DomComponents.addType('cutom-element-type', {...});
+
+  // after all new types
+  editor.render();
+</script>
+

Solution 2: put all the stuff inside a plugin (Creating plugins)

Last Updated: 4/26/2019, 10:31:58 PM
+ + + diff --git a/docs/modules/Plugins.html b/docs/modules/Plugins.html new file mode 100644 index 000000000..d98daa5ba --- /dev/null +++ b/docs/modules/Plugins.html @@ -0,0 +1,105 @@ + + + + + + Plugins | GrapesJS + + + + + + + + + +

Plugins

Creating plugins in GrapesJS is pretty straightforward and here you'll get how to achieve it.

Basic plugin

The most simple plugins are just functions that are run when the editor is being built.

  function myPlugin(editor){
+      editor.BlockManager.add('my-first-block', {
+        label: 'Simple block',
+        content: '<div class="my-block">This is a simple block</div>',
+      });
+  }
+
+  var editor = grapesjs.init({
+      container : '#gjs',
+      plugins: [myPlugin]
+  });
+

This means that plugins can be moved to separate folders to keep thing cleaner or imported from NPM.

  import myPlugin from './plugins/myPlugin'
+  import npmPackage from '@npm/package'
+
+  var editor = grapesjs.init({
+      container : '#gjs',
+      plugins: [myPlugin, npmPackage]
+  });
+

Named plugin

If you're distributing your plugin globally, you may want to make a named plugin. To keep thing cleaner, so you'll probably get a similar structure:

/your/path/to/grapesjs.min.js
+/your/path/to/grapesjs-plugin.js
+

Important: The order that you load files matters. GrapesJS has to be loaded before the plugin. This sets up the grapejs global variable.

So, in your grapesjs-plugin.js file:

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:

<script src="http://code.jquery.com/jquery-2.2.0.min.js"></script>
+<link rel="stylesheet" href="path/to/grapes.min.css">
+<script src="path/to/grapes.min.js"></script>
+<script src="path/to/grapesjs-plugin.js"></script>
+
+<div id="gjs"></div>
+
+<script type="text/javascript">
+  var editor = grapesjs.init({
+      container : '#gjs',
+      plugins: ['my-plugin-name']
+  });
+</script>
+

Plugins with options

It's also possible to pass custom parameters to plugins in to make them more flexible.

  var editor = grapesjs.init({
+      container : '#gjs',
+      plugins: ['my-plugin-name'],
+      pluginsOpts: {
+        'my-plugin-name': {
+          customField: 'customValue'
+        }
+      }
+  });
+

Inside you plugin you'll get those options via options argument

export default grapesjs.plugins.add('my-plugin-name', (editor, options) => {
+  console.log(options);
+  //{ customField: 'customValue' }
+})
+

This also works with plugins that aren't named.

  import myPlugin from '../plugin'
+
+  var editor = grapesjs.init({
+      container : '#gjs',
+      plugins: [myPlugin],
+      pluginsOpts: {
+        [myPlugin]: {
+          customField: 'customValue'
+        }
+      }
+  });
+

Named Plugins vs Non-Named Plugins

When you use a named plugin, then that name must be unique across all other plugins.

grapesjs.plugins.add('my-plugin-name', fn);
+

In this example, the plugin name is my-plugin-name and can't be used by other plugins. To avoid namespace restrictions use basic plugins that are purely functional.

Boilerplate

If you want to start with a production-ready boilerplate for a named plugin, you might want to try grapesjs-plugin-boilerplate which you can clone and start developing a named plugin immediately. For more informations check the repository

  • https://github.com/artf/grapesjs-preset-webpage
  • https://github.com/artf/grapesjs-preset-newsletter
Last Updated: 8/2/2018, 1:42:17 AM
+ + + diff --git a/docs/modules/Storage.html b/docs/modules/Storage.html new file mode 100644 index 000000000..c268b1bb3 --- /dev/null +++ b/docs/modules/Storage.html @@ -0,0 +1,197 @@ + + + + + + Storage Manager | GrapesJS + + + + + + + + + +

Storage Manager

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

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:

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:

...
+storageManager: { type: null },
+

For all other available options check directly the configuration source file.

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).

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).

// 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: `<div>...</div>`,
+  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

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

...
+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)

{
+  // `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:

header('Content-Type: application/json');
+echo json_encode([
+  'gjs-components': [...],
+  'gjs-style': [...],
+]);
+

Storage API

The Storage module has also its own set of API 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 and allows more space usage (each browser implements its own rules, for a better understanding on how browser storage limits work, check here). +IndexedDB configuration 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.

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:

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

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

Events

Another way to extend storage capabilities is to make use of GrapesJS's event hooks, you can check here 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
editor.on('storage:start', startLoading);
+editor.on('storage:end', endLoading);
+
  • Error handling
editor.on('storage:error', (err) => {
+    alert(`Error: ${err}`);
+});
+
  • Extend parameters to store
editor.on('storage:start:store', (objectToStore) => {
+    if (needToAddExtraParam) {
+      objectToStore.customHtml = `<div>...${editor.getHtml()}...</div>`;
+    }
+});
+
  • Do stuff post load
editor.on('storage:end:load', (resultObject) => {
+    if (resultObject.hasSomeKey) {
+      // do stuff
+    }
+});
+
Last Updated: 7/10/2018, 2:27:13 AM
+ + + diff --git a/docs/modules/Style-manager.html b/docs/modules/Style-manager.html new file mode 100644 index 000000000..fd515094a --- /dev/null +++ b/docs/modules/Style-manager.html @@ -0,0 +1,50 @@ + + + + + + Style Manager | GrapesJS + + + + + + + + + +

Style Manager

Coming soon

Built-in properties

Here you can find all the available built-in properties that you can use inside Style Manager via buildProps:

float, position, text-align, display, font-family, font-weight, border, border-style, border-color, border-width, box-shadow, background-repeat, background-position, background-attachment, background-size, transition, transition-duration, transition-property, transition-timing-function, top, right, bottom, left, margin, margin-top, margin-right, margin-bottom, margin-left, padding, padding-top, padding-right, padding-bottom, padding-left, width, height, min-width, min-height, max-width, max-height, font-size, letter-spacing, line-height, text-shadow, border-radius, border-top-left-radius, border-top-right-radius, border-bottom-left-radius, border-bottom-right-radius, perspective, transform, transform-rotate-x, transform-rotate-y, transform-rotate-z, transform-scale-x, transform-scale-y, transform-scale-z, color, background-color, background, background-image, cursor, flex-direction, flex-wrap, justify-content, align-items, align-content, order, flex-basis, flex-grow, flex-shrink, align-self, overflow, overflow-x, overflow-y

Example usage:

...
+  styleManager : {
+    sectors: [{
+      name: 'Dimension',
+      buildProps: ['width', 'min-height']
+    },{
+      name: 'Extra',
+      buildProps: ['background-color', 'box-shadow']
+    }]
+  }
+...
+
Last Updated: 1/21/2019, 2:30:30 PM
+ + + diff --git a/docs/modules/Traits.html b/docs/modules/Traits.html new file mode 100644 index 000000000..f71ba4574 --- /dev/null +++ b/docs/modules/Traits.html @@ -0,0 +1,130 @@ + + + + + + Trait Manager | GrapesJS + + + + + + + + + +

Trait Manager

In GrapesJS, Traits can define different parameters and behaviors of a component. The user generally will see traits as the Settings of a component. A common use of traits is to customize element attributes (eg. placeholder for inputs) and in this case the editor comes already with some built-in, easy configurable, types.

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 two traits: id and title (at the moment of writing). So, if you select an input and open the Settings panel you will see this:

In this example we are going to create a new Component. (Check here for more details about the creation of new components with a new set of traits

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

Traits modify attributes of the model (which than reflected in canvas), but you can also have traits which change the property

...
+traits: [{
+    type: 'text',
+    label: 'Test',
+    name: 'model-prop-name',
+    changeProp: 1,
+}],
+...
+

In this way you're able to listen for changes and react with your own logic

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.

// 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',
+}],
+...
+
Last Updated: 12/26/2018, 7:29:34 PM
+ + +