Browse Source

Merge pull request #1 from artf/dev

fork
pull/1367/head
tangkikodo 8 years ago
committed by GitHub
parent
commit
9ceefed6dc
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 10
      .editorconfig
  2. 19
      .eslintrc
  3. 26
      .github/ISSUE_TEMPLATE.md
  4. 13
      .github/no-response.yml
  5. 11
      .gitignore
  6. 4
      .travis.yml
  7. 46
      CODE_OF_CONDUCT.md
  8. 52
      CONTRIBUTING.md
  9. 252
      Gruntfile.js
  10. 4
      LICENSE
  11. 197
      README.md
  12. 53
      bower.json
  13. 8
      dist/css/grapes.min.css
  14. 52882
      dist/grapes.js
  15. 30
      dist/grapes.min.js
  16. 1
      dist/grapes.min.js.map
  17. 14
      docs/.vuepress/components/Demo.vue
  18. 28
      docs/.vuepress/components/DemoBasicBlocks.vue
  19. 14
      docs/.vuepress/components/DemoCanvasOnly.vue
  20. 47
      docs/.vuepress/components/DemoCustomPanels.vue
  21. 55
      docs/.vuepress/components/DemoDevices.vue
  22. 39
      docs/.vuepress/components/DemoLayers.vue
  23. 50
      docs/.vuepress/components/DemoStyle.vue
  24. 79
      docs/.vuepress/components/DemoTheme.vue
  25. 51
      docs/.vuepress/components/DemoTraits.vue
  26. 11
      docs/.vuepress/components/demos/DemoCanvasOnly.css
  27. 3
      docs/.vuepress/components/demos/DemoCanvasOnly.html
  28. 14
      docs/.vuepress/components/demos/DemoCanvasOnly.js
  29. 17
      docs/.vuepress/components/demos/DemoLayers.css
  30. 374
      docs/.vuepress/components/demos/utils.js
  31. 103
      docs/.vuepress/config.js
  32. 9
      docs/.vuepress/enhanceApp.js
  33. 72
      docs/.vuepress/override.styl
  34. BIN
      docs/.vuepress/public/assets-builtin-modal.png
  35. BIN
      docs/.vuepress/public/assets-empty-view.png
  36. BIN
      docs/.vuepress/public/assets-full-dropzone.gif
  37. BIN
      docs/.vuepress/public/assets-svg-view.png
  38. BIN
      docs/.vuepress/public/assets-uploader.png
  39. BIN
      docs/.vuepress/public/blocks3.jpg
  40. BIN
      docs/.vuepress/public/btn-clicked.png
  41. BIN
      docs/.vuepress/public/canvas-panels.jpg
  42. BIN
      docs/.vuepress/public/default-gjs.jpg
  43. BIN
      docs/.vuepress/public/default-sm.jpg
  44. BIN
      docs/.vuepress/public/default-traits.png
  45. BIN
      docs/.vuepress/public/demo-view.png
  46. BIN
      docs/.vuepress/public/empty-gjs.png
  47. BIN
      docs/.vuepress/public/enabled-sm.jpg
  48. 12
      docs/.vuepress/public/grapes.min.js
  49. BIN
      docs/.vuepress/public/input-custom-traits.png
  50. BIN
      docs/.vuepress/public/logo-icon.png
  51. BIN
      docs/.vuepress/public/logo.png
  52. BIN
      docs/.vuepress/public/new-btn.png
  53. BIN
      docs/.vuepress/public/new-panel.png
  54. BIN
      docs/.vuepress/public/style-comp.jpg
  55. 59
      docs/.vuepress/theme/CarbonAds.vue
  56. 17
      docs/.vuepress/theme/Layout.vue
  57. 458
      docs/Home.md
  58. 49
      docs/README.md
  59. 39
      docs/api.js
  60. 7
      docs/api/README.md
  61. 255
      docs/api/assets.md
  62. 189
      docs/api/block_manager.md
  63. 93
      docs/api/commands.md
  64. 176
      docs/api/components.md
  65. 228
      docs/api/css_composer.md
  66. 87
      docs/api/device_manager.md
  67. 512
      docs/api/editor.md
  68. 130
      docs/api/keymaps.md
  69. 111
      docs/api/modal_dialog.md
  70. 207
      docs/api/panels.md
  71. 139
      docs/api/rich_text_editor.md
  72. 139
      docs/api/selector_manager.md
  73. 222
      docs/api/storage_manager.md
  74. 318
      docs/api/style_manager.md
  75. 236
      docs/api/undo_manager.md
  76. 28
      docs/deploy.sh
  77. 7
      docs/faq.md
  78. 725
      docs/getting-started.md
  79. 125
      docs/guides/Replace-Rich-Text-Editor.md
  80. 584
      docs/modules/Assets.md
  81. 74
      docs/modules/Blocks.md
  82. 222
      docs/modules/Components-js.md
  83. 270
      docs/modules/Components.md
  84. 151
      docs/modules/Plugins.md
  85. 298
      docs/modules/Storage.md
  86. 31
      docs/modules/Style-manager.md
  87. 149
      docs/modules/Traits.md
  88. 803
      index.html
  89. 19938
      package-lock.json
  90. 132
      package.json
  91. 94
      src/asset_manager/config/config.js
  92. 351
      src/asset_manager/index.js
  93. 241
      src/asset_manager/main.js
  94. 53
      src/asset_manager/model/Asset.js
  95. 20
      src/asset_manager/model/AssetImage.js
  96. 85
      src/asset_manager/model/Assets.js
  97. 10
      src/asset_manager/template/assetImage.html
  98. 17
      src/asset_manager/template/assets.html
  99. 5
      src/asset_manager/template/fileUploader.html
  100. 158
      src/asset_manager/view/AssetImageView.js

10
.editorconfig

@ -0,0 +1,10 @@
root = true
[*]
end_of_line = lf
insert_final_newline = true
[*.js]
charset = utf-8
indent_style = space
indent_size = 2

19
.eslintrc

@ -0,0 +1,19 @@
{
"env": {
"browser": true,
"node": true
},
"parserOptions": {
"sourceType": "module",
"ecmaFeatures": {
"experimentalObjectRestSpread": true
}
},
"rules": {
"strict": 0,
"quotes": [0, "single"],
"eol-last": [0],
"no-mixed-requires": [0],
"no-underscore-dangle": [0]
}
}

26
.github/ISSUE_TEMPLATE.md

@ -0,0 +1,26 @@
## WARNING
READ and FOLLOW next 5 steps, then REMOVE them before posting the issue
1. Follow CONTRIBUTING Guidelines https://github.com/artf/grapesjs/blob/master/CONTRIBUTING.md
2. Use the GitHub Issues EXCLUSIVELY for BUGS, FEATURE REQUESTS or QUESTIONS. Prefix the title of the issue with its context, eg. `[Bug]: ....`
3. Do a quick SEARCH first, to see if someone else didn't open the same issue
4. DON'T ASK to create examples/code for you, read DOCS and APIs first, then you can post what you have tried (we'd like to see your code) and what you're unable to achieve
5. All relative statements/questions have to be filled/answered, otherwise, the issue might be CLOSED
## You're submitting a BUG
1. Are you using the latest release (older versions are NOT supported)?
1. If you're not sure, type `grapesjs.version` in console and press ENTER
1. Are you facing the bug with your local copy of GrapesJS or with the current demo?
1. If a local copy
1. Indicate all informations about your OS, browser and GrapesJS version.
1. Are you able to reproduce the bug from the demo?
1. What is the expected behavior?
1. What happens instead?
1. If you're able to reproduce the bug indicate all the necessary steps
1. Attach screenshots (using KAP/LICEcap), screencasts or live demo
1. JSFiddle Starter template https://jsfiddle.net/szLp8h4n
1. CodeSandbox Starter template https://codesandbox.io/s/1r0w2pk1vl
## You're submitting a FEATURE REQUEST
1. Be sure to work on the latest version, as the feature might be already there
1. Keep in mind that the feature should be considered valid to use for everyone, not only for your case

13
.github/no-response.yml

@ -0,0 +1,13 @@
# Configuration for probot-no-response - https://github.com/probot/no-response
# Number of days of inactivity before an Issue is closed for lack of response
daysUntilClose: 10
# Label requiring a response
responseRequiredLabel: more-information-needed
# Comment to post when closing an Issue for lack of response. Set to `false` to disable
closeComment: >
This issue has been automatically closed because there has been no response
to our request for more information from the original author. With only the
information that is currently in the issue, we don't have enough information
to take action. Please reach out if you have or find the answers we need so
that we can investigate further.

11
.gitignore

@ -2,15 +2,20 @@
.settings/
.sass-cache/
.project
.eslintrc
npm-debug.log
.idea
npm-debug.log*
yarn-error.log
yarn.lock
style/.sass-cache/
stats.json
img/
images/
private/
docs/
vendor/
coverage/
node_modules/
bower_components/
grapesjs-*.tgz
_index.html
docs/.vuepress/dist

4
.travis.yml

@ -1,5 +1,3 @@
language: node_js
node_js:
- "5.6"
before_script:
- npm run build
- "7.6"

46
CODE_OF_CONDUCT.md

@ -0,0 +1,46 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at artur.catch@hotmail.it. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/

52
CONTRIBUTING.md

@ -0,0 +1,52 @@
# Contribute
## Introduction
First of all, thank you for considering contributing to GrapesJS!
We welcome any type of contribution, not only code. Like for example:
- **QA**: file bug reports, the more details you can give the better (e.g. screenshots with the console open)
- **Marketing**: writing blog posts, howto's, tutorials, etc.
- **Community**: presenting the project at meetups, organizing a dedicated meetup for the local community, etc.
- **Money**: We welcome financial contributions in full transparency on our [Open Collective].
## Your First Contribution
Working on your first Pull Request? You can learn how from this **free** series, [How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github).
## Submitting code
Any code change should be submitted as a pull request. Before start working on something make always a search in opened issues and pull requests, this might help you to avoid waisting time.
A pull request could be a bug fix, new feature and much more, but in all cases, **open a new issue** and talk about what you want to do. Often happens to work on something already fixed (ready to release) or in progress.
The title should be brief but comprehensive, the description contains a link to the opened issue and the proposed solution. The pull request should contain tests whenever possible. Keep in mind that the bigger is the pull request, the longer it will take to review and merge. Try to break down large pull requests in smaller chunks that are easier to review and merge.
## Styleguide
The code is auto formatted with [prettier](https://github.com/prettier/prettier) on any commit, therefore you can write in any style you prefer
## Expenses
Anyone can file an expense (code, marketing, etc.) via our [Open Collective]. If the expense makes sense for the development of the community, it will be "merged" in the ledger of our open collective by the core contributors and the person who filed the expense will be reimbursed.
Before submitting an expense contact core contributors via the current active chat room ([Discord](https://discord.gg/QAbgGXq)) and explain your intents
## Questions
If you have any questions, create an [issue](https://github.com/artf/grapesjs/issues) (protip: do a quick search first to see if someone else didn't ask the same question before!).
## Credits
Thank you to all the people who have already contributed to GrapesJS!
<a href="/artf/grapesjs/graphs/contributors"><img src="https://opencollective.com/grapesjs/contributors.svg?width=890" /></a>
[Open Collective]: <https://opencollective.com/grapesjs>

252
Gruntfile.js

@ -1,252 +0,0 @@
module.exports = function(grunt) {
var appPath = 'src',
buildPath = 'dist',
stylePath = 'styles',
configPath = 'config/require-config.js',
port = grunt.option('port') || 8000;
grunt.loadNpmTasks('grunt-contrib-requirejs');
grunt.loadNpmTasks('grunt-contrib-connect');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-cssmin');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks('grunt-bowercopy');
grunt.loadNpmTasks('grunt-mocha');
grunt.loadNpmTasks('grunt-sass');
grunt.initConfig({
appDir: appPath,
builtDir: buildPath,
styleDir: stylePath,
pkg: grunt.file.readJSON("package.json"),
requirejs:{
compile:{
options: {
mainConfigFile: '<%= appDir %>/' + configPath,
appDir: '<%= appDir %>',
dir: '<%= builtDir %>',
baseUrl: './',
name: 'main',
include: ["./../node_modules/almond/almond"],
removeCombined: true,
findNestedDependencies: true,
keepBuildDir: true,
inlineText: true,
optimize: 'none',
wrap: {
start: "(function (root, factory) {"+
"if (typeof define === 'function' && define.amd)"+
"define([], factory);"+
"else if(typeof exports === 'object' && typeof module === 'object')"+
"module.exports = factory();"+
"else "+
"root.<%= pkg.name %> = root.GrapesJS = factory();"+
"}(this, function () {",
end: "return require('grapesjs/main'); }));"
},
paths: {
"jquery": "wrappers/jquery",
}
}
}
},
jshint: {
all: [
'Gruntfile.js',
'<%= appDir %>/**/*.js',
]
},
uglify: {
options: {
banner: '/*! <%= pkg.name %> - v<%= pkg.version %> */\n'
},
build:{
files: {
'<%= builtDir %>/grapes.min.js': ['<%= builtDir %>/main.js']
}
}
},
sass: {
dist: {
files: [{
expand: true,
cwd: '<%= styleDir %>/scss',
src: ['**/*.scss'],
dest: '<%= styleDir %>/css',
ext: '.css'
}]
}
},
cssmin: {
target: {
files: [{
expand: true,
flatten: true,
src: [
'<%= styleDir %>/css/main.css',
'node_modules/codemirror/lib/codemirror.css',
'node_modules/codemirror/theme/hopscotch.css'
],
dest: '<%= builtDir %>',
ext: '.min.css'
}]
}
},
concat: {
css: {
src: ['<%= builtDir %>/*.min.css'],
dest: '<%= builtDir %>/css/grapes.min.css'
}
},
mocha: {
test: {
src: ['test/index.html'],
options: { log: true, },
},
},
connect: {
server: {
options: {
port: port,
open: true
}
},
},
/*
bowercopy: {
options: {
srcPrefix: 'bower_components'
},
scripts: {
options: {
destPrefix: 'vendor'
},
files: {
'almond/almond.js' : 'almond/almond.js',
'jquery/jquery.js' : 'jquery/dist/jquery.min.js',
'underscore/underscore.js' : 'underscore/underscore-min.js',
'backbone/backbone.js' : 'backbone/backbone-min.js',
'backbone-undo/backbone-undo.js' : 'Backbone.Undo/Backbone.Undo.js',
'keymaster/keymaster.js' : 'keymaster/keymaster.js',
'require/require.js' : 'requirejs/require.js',
'require-text/text.js' : 'requirejs-text/text.js',
'spectrum/spectrum.js' : 'spectrum/spectrum.js',
'codemirror' : 'codemirror',
'codemirror-formatting' : 'codemirror-formatting/formatting.js',
'mocha' : 'mocha',
'chai' : 'chai/chai.js',
'sinon' : 'sinonjs/sinon.js',
},
}
},
*/
watch: {
script: {
files: [ '<%= appDir %>/**/*.js' ],
tasks: ['jshint']
},
css: {
files: '**/*.scss',
tasks: ['sass']
},
test: {
files: ['test/specs/**/*.js'],
tasks: ['mocha'],
//options: { livereload: true }, //default port 35729
}
},
clean: {
all: ["<%= builtDir %>/*", "!<%= builtDir %>/grapes.min.js", "!<%= builtDir %>/css"]
},
copy: {
fonts: {
cwd: '<%= styleDir %>/fonts',
src: '**/*',
dest: '<%= builtDir %>/fonts',
expand: true
}
}
});
/**
* Have to copy require configs cause r.js will try to load them from the path indicated inside
* main.js file. This is the only way I have found to do it
* */
grunt.registerTask('before-rjs', function() {
if(grunt.file.exists(buildPath))
grunt.file.delete(buildPath);
grunt.file.mkdir(buildPath);
grunt.file.copy(appPath + '/' + configPath, buildPath + '/' + appPath + '/' + configPath);
});
grunt.registerTask('webfont-custom', function() {
var dir = './styles/fonts/';
var destName = 'main-fonts';
var donePromise = this.async();
var svg2ttf = {
cmd: 'svg2ttf',
args: [dir + destName + '.svg', dir + destName + '.ttf'],
};
var ttf2woff = {
cmd: 'ttf2woff',
args: [dir + destName + '.ttf', dir + destName + '.woff'],
};
var ttf2woff2 = {
cmd: 'cat',
//args: [dir + destName + '.ttf', dir + destName + '.woff2'],
args: [dir + destName + '.ttf', '|', 'ttf2woff2', '>>', dir + destName + '.woff2'],
};
var ttf2eot = {
cmd: 'ttf2eot',
args: [dir + destName + '.ttf', dir + destName + '.eot'],
};
grunt.util.spawn(svg2ttf, function done(error, result, code) {
grunt.log.ok('.ttf file created');
grunt.util.spawn(ttf2woff, function done(error, result, code) {
grunt.log.ok('.woff file created');
grunt.util.spawn(ttf2eot, function done(error, result, code) {
grunt.log.ok('.eot file created');
donePromise();
/*
grunt.util.spawn(ttf2woff2, function done(error, result, code) {
grunt.log.ok('.woff2 file created');
donePromise();
});
*/
});
});
});
});
grunt.registerTask('dev', ['connect', 'watch']);
grunt.registerTask('test', ['jshint', 'mocha']);
grunt.registerTask('build:fonts', ['webfont-custom']);
grunt.registerTask('build', ['jshint', 'sass', 'before-rjs', 'requirejs', 'uglify', 'cssmin', 'concat', 'clean', 'copy']);
grunt.registerTask('default', ['dev']);
};

4
LICENSE

@ -1,4 +1,4 @@
Copyright (c) 2016, Artur Arseniev
Copyright (c) 2017-current, Artur Arseniev
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
@ -22,4 +22,4 @@ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

197
README.md

@ -1,12 +1,16 @@
# [GrapesJS](http://grapesjs.com)
[![Build Status](https://travis-ci.org/artf/grapesjs.svg?branch=master)](https://travis-ci.org/artf/grapesjs)
[![Chat](https://img.shields.io/badge/chat-discord-7289da.svg)](https://discord.gg/QAbgGXq)
[![CDNJS](https://img.shields.io/cdnjs/v/grapesjs.svg)](https://cdnjs.com/libraries/grapesjs)
[![npm](https://img.shields.io/npm/v/grapesjs.svg)](https://www.npmjs.com/package/grapesjs)
[![BrowserStack Status](https://www.browserstack.com/automate/badge.svg?badge_key=QksxaStYaGI3eE5VMDlPTEh0Z3hYOXEwRWNMc1ZYT0lNbEJxMWdOZWFDZz0tLWlqcFVWb05PMmlQMmU3emFIZkFNWVE9PQ==--e89345be5e303d515276e3accd6f1316dfa857ab)](https://www.browserstack.com/automate/public-build/QksxaStYaGI3eE5VMDlPTEh0Z3hYOXEwRWNMc1ZYT0lNbEJxMWdOZWFDZz0tLWlqcFVWb05PMmlQMmU3emFIZkFNWVE9PQ==--e89345be5e303d515276e3accd6f1316dfa857ab)
<p align="center"><img src="http://grapesjs.com/img/grapesjs-front-page-m.jpg" alt="GrapesJS" width="500" align="center"/></p>
<br/>
GrapesJS is a free and open source Web Builder Framework which helps you building HTML templates to be used inside sites, newsletters and mobile apps.
Mainly GrapesJS was designed to be used inside a [CMS] to speed up a creation of dynamic templates. To better understand this concept check the image below
GrapesJS is a free and open source Web Builder Framework which helps building HTML templates, faster and easily, to be delivered in sites, newsletters or mobile apps. Mainly, GrapesJS was designed to be used inside a [CMS] to speed up the creation of dynamic templates. To better understand this concept check the image below
<br/>
<p align="center"><img src="http://grapesjs.com/img/gjs-concept.png" alt="GrapesJS - Style Manager" height="400" align="center"/></p>
@ -19,6 +23,27 @@ Webpage Demo - http://grapesjs.com/demo.html
Newsletter Demo - http://grapesjs.com/demo-newsletter-editor.html
## Table of contents
* [Features](#features)
* [Download](#download)
* [Usage](#usage)
* [Development](#development)
* [Documentation](#documentation)
* [API](#api)
* [Testing](#testing)
* [Plugins](#plugins)
* [Support](#support)
* [Changelog](https://github.com/artf/grapesjs/releases)
* [Contributing](https://github.com/artf/grapesjs/blob/master/CONTRIBUTING.md)
* [License](#license)
## Features
@ -42,56 +67,31 @@ Newsletter Demo - http://grapesjs.com/demo-newsletter-editor.html
* Default built-in commands (basically for creating and managing different components)
## Installation
You can get GrapesJS with `npm install grapesjs` or via `git clone https://github.com/artf/grapesjs.git`.
For development purpose you should follow instructions below.
## Development
GrapesJS uses [RequireJS](http://requirejs.org/) to organize its files inside `src` folder and [Grunt](http://gruntjs.com/) for build them to `dist`
Clone the repository and enter inside the folder
```sh
$ npm install -g grunt-cli
$ git clone https://github.com/artf/grapesjs.git
$ cd grapesjs
```
Install all necessary dependencies
```sh
$ npm install
```
Build GrapesJS
## Download
```sh
$ npm run build
```
* CDNs
* UNPKG (resolves to the latest version)
* `https://unpkg.com/grapesjs`
* `https://unpkg.com/grapesjs/dist/css/grapes.min.css`
* CDNJS (replace `X.X.X` with the current version)
* `https://cdnjs.cloudflare.com/ajax/libs/grapesjs/X.X.X/grapes.min.js`
* `https://cdnjs.cloudflare.com/ajax/libs/grapesjs/X.X.X/css/grapes.min.css`
* NPM
* `npm i grapesjs`
* GIT
* `git clone https://github.com/artf/grapesjs.git`
Launch server, which also gonna watch some files, and try out the demo on `localhost:8000`
For the development purpose you should follow instructions below.
```sh
$ npm start
```
Tests are already available inside browser on `localhost:8000/test`
If [Grunt](http://gruntjs.com/) is already installed globally you could change the port by using `grunt dev --port 9000`
## Usage
JQuery is the only hard dependency so you have to include it before using GrapesJS
```html
<script src="http://code.jquery.com/jquery-2.2.0.min.js"></script>
```
After that include scripts from GrapesJS with all your configurations
```html
<link rel="stylesheet" href="path/to/grapes.min.css">
<script src="path/to/grapes.min.js"></script>
@ -126,14 +126,44 @@ You could also grab the content directly from the element with `fromElement` pro
For more practical example I suggest to look up the code inside this demo: http://grapesjs.com/demo.html
## Configuration
## Development
GrapesJS uses [Webpack](https://github.com/webpack/webpack) as a module bundler and [Babel](https://github.com/babel/babel) as a compiler.
Clone the repository and install all the necessary dependencies
```sh
$ git clone https://github.com/artf/grapesjs.git
$ cd grapesjs
$ npm i
```
Start the dev server
```sh
$ npm start
```
Once the development server is started you should be able to reach the demo page (eg. `http://localhost:8080`)
## Documentation
Check the getting started guide here: [Documentation]
Check the getting started guide here: [wiki]
## API
API References (draft) could be found here: [API-Reference]
API References could be found here: [API-Reference]
## Testing
@ -142,43 +172,82 @@ API References (draft) could be found here: [API-Reference]
$ npm test
```
## Acknowledgements
GrapesJS is built on top of this amazing open source projects:
* [Backbone] - gives Backbone to web applications
* [Backbone.Undo] - a simple Backbone undo-manager
* [Keymaster] - keyboard shortcuts
* [CodeMirror] - versatile text editor
* [Spectrum] - no hassle colorpicker
* [FontAwesome] - the iconic font and CSS framework
## Sponsors
## Plugins
### Extensions
* [grapesjs-plugin-export](https://github.com/artf/grapesjs-plugin-export) - Export GrapesJS templates in a zip archive
* [grapesjs-plugin-filestack](https://github.com/artf/grapesjs-plugin-filestack) - Add Filestack uploader in Asset Manager
* [grapesjs-plugin-ckeditor](https://github.com/artf/grapesjs-plugin-ckeditor) - Replaces the built-in RTE with CKEditor
* [grapesjs-aviary](https://github.com/artf/grapesjs-aviary) - Add the Aviary Image Editor
* [grapesjs-blocks-basic](https://github.com/artf/grapesjs-blocks-basic) - Basic set of blocks
* [grapesjs-plugin-forms](https://github.com/artf/grapesjs-plugin-forms) - Set of form components and blocks
* [grapesjs-navbar](https://github.com/artf/grapesjs-navbar) - Simple navbar component
* [grapesjs-component-countdown](https://github.com/artf/grapesjs-component-countdown) - Simple countdown component
* [grapesjs-style-gradient](https://github.com/artf/grapesjs-style-gradient) - Add a gradient type input
* [grapesjs-blocks-flexbox](https://github.com/artf/grapesjs-blocks-flexbox) - Add the flexbox block
* [grapesjs-lory-slider](https://github.com/artf/grapesjs-lory-slider) - Slider component by using [lory](https://github.com/meandmax/lory)
* [grapesjs-tabs](https://github.com/artf/grapesjs-tabs) - Simple tabs component
* [grapesjs-indexeddb](https://github.com/artf/grapesjs-indexeddb) - Storage wrapper for IndexedDB
* [grapesjs-firestore](https://github.com/artf/grapesjs-firestore) - Storage wrapper for [Cloud Firestore](https://firebase.google.com/docs/firestore)
### Presets
* [grapesjs-preset-webpage](https://github.com/artf/grapesjs-preset-webpage) - Webpage Builder
* [grapesjs-preset-newsletter](https://github.com/artf/grapesjs-preset-newsletter) - Newsletter Builder
* [grapesjs-mjml](https://github.com/artf/grapesjs-mjml) - Newsletter Builder with MJML components
Find out more about plugins here: [Creating plugins](https://github.com/artf/grapesjs/wiki/Creating-plugins)
The project is sponsored by
[![Sendloop](http://grapesjs.com/img/sendloop-logo-l.png)](https://sendloop.com)
## Support
If you like the project support it with a donation of your choice.
If you like the project support it with a donation of your choice or become a backer/sponsor via [Open Collective](https://opencollective.com/grapesjs)
[![PayPalMe](http://grapesjs.com/img/ppme.png)](https://paypal.me/grapesjs)
<a href="https://opencollective.com/grapesjs/sponsors/0/website"><img src="https://opencollective.com/grapesjs/sponsors/0/avatar"></a>
<a href="https://opencollective.com/grapesjs/sponsors/1/website"><img src="https://opencollective.com/grapesjs/sponsors/1/avatar"></a>
<a href="https://opencollective.com/grapesjs/sponsors/2/website"><img src="https://opencollective.com/grapesjs/sponsors/2/avatar"></a>
<a href="https://opencollective.com/grapesjs/sponsors/3/website"><img src="https://opencollective.com/grapesjs/sponsors/3/avatar"></a>
<a href="https://opencollective.com/grapesjs/sponsors/4/website"><img src="https://opencollective.com/grapesjs/sponsors/4/avatar"></a>
<a href="https://opencollective.com/grapesjs/sponsors/5/website"><img src="https://opencollective.com/grapesjs/sponsors/5/avatar"></a>
<a href="https://opencollective.com/grapesjs/sponsors/6/website"><img src="https://opencollective.com/grapesjs/sponsors/6/avatar"></a>
<a href="https://opencollective.com/grapesjs/sponsors/7/website"><img src="https://opencollective.com/grapesjs/sponsors/7/avatar"></a>
<a href="https://opencollective.com/grapesjs/sponsors/8/website"><img src="https://opencollective.com/grapesjs/sponsors/8/avatar"></a>
<a href="https://opencollective.com/grapesjs/sponsors/9/website"><img src="https://opencollective.com/grapesjs/sponsors/9/avatar"></a>
<a href="https://opencollective.com/grapesjs/backers/0/website"><img src="https://opencollective.com/grapesjs/backers/0/avatar"></a>
<a href="https://opencollective.com/grapesjs/backers/1/website"><img src="https://opencollective.com/grapesjs/backers/1/avatar"></a>
<a href="https://opencollective.com/grapesjs/backers/2/website"><img src="https://opencollective.com/grapesjs/backers/2/avatar"></a>
<a href="https://opencollective.com/grapesjs/backers/3/website"><img src="https://opencollective.com/grapesjs/backers/3/avatar"></a>
<a href="https://opencollective.com/grapesjs/backers/4/website"><img src="https://opencollective.com/grapesjs/backers/4/avatar"></a>
<a href="https://opencollective.com/grapesjs/backers/5/website"><img src="https://opencollective.com/grapesjs/backers/5/avatar"></a>
<a href="https://opencollective.com/grapesjs/backers/6/website"><img src="https://opencollective.com/grapesjs/backers/6/avatar"></a>
<a href="https://opencollective.com/grapesjs/backers/7/website"><img src="https://opencollective.com/grapesjs/backers/7/avatar"></a>
<a href="https://opencollective.com/grapesjs/backers/8/website"><img src="https://opencollective.com/grapesjs/backers/8/avatar"></a>
<a href="https://opencollective.com/grapesjs/backers/9/website"><img src="https://opencollective.com/grapesjs/backers/9/avatar"></a>
<a href="https://opencollective.com/grapesjs/backers/10/website"><img src="https://opencollective.com/grapesjs/backers/10/avatar"></a>
<a href="https://opencollective.com/grapesjs/backers/11/website"><img src="https://opencollective.com/grapesjs/backers/11/avatar"></a>
<a href="https://opencollective.com/grapesjs/backers/12/website"><img src="https://opencollective.com/grapesjs/backers/12/avatar"></a>
<br>
[![BrowserStack](https://user-images.githubusercontent.com/11614725/39406324-4ef89c40-4bb5-11e8-809a-113d9432e5a5.png)](https://www.browserstack.com)<br/>
Thanks to [BrowserStack](https://www.browserstack.com) for providing us browser testing services
## License
BSD 3-clause
[Backbone]: <http://backbonejs.org/>
[Backbone.Undo]: <http://backbone.undojs.com/>
[Keymaster]: <https://github.com/madrobby/keymaster>
[CodeMirror]: <http://codemirror.net/>
[Spectrum]: <https://github.com/bgrins/spectrum>
[FontAwesome]: <https://fortawesome.github.io/Font-Awesome/>
[wiki]: <https://github.com/artf/grapesjs/wiki>
[API-Reference]: <https://github.com/artf/grapesjs/wiki/API-Reference>
[Documentation]: <https://grapesjs.com/docs/>
[API-Reference]: <https://grapesjs.com/docs/api/>
[CMS]: <https://it.wikipedia.org/wiki/Content_management_system>

53
bower.json

@ -1,53 +0,0 @@
{
"name": "grapesjs",
"description": "Open source Web Template Editor",
"version": "0.5.4",
"author": "Artur Arseniev",
"homepage": "http://grapesjs.com",
"main": [
"dist/grapes.min.js",
"dist/grapes.min.css"
],
"keywords": [
"grapes",
"wte",
"web template editor",
"site builder",
"newsletter builder",
"wysiwyg",
"template",
"editor"
],
"license": "BSD-3-Clause",
"ignore": [
"**/.*",
"styles",
"src",
"test",
"Gruntfile.js",
"index.html",
"README.md",
"package.json"
],
"dependencies": {
"jquery": "~2.2.0"
},
"devDependencies": {
"mocha": "~2.3.4",
"chai": "~3.4.2",
"sinonjs": "~1.17.1",
"backbone": "~1.2.3",
"Backbone.Undo": "~0.2.5",
"keymaster": "~1.6.3",
"requirejs": "~2.1.22",
"requirejs-text": "~2.0.14",
"spectrum": "~1.8.0",
"underscore": "~1.8.3",
"codemirror": "~5.10.0",
"codemirror-formatting": "*",
"almond": "~0.3.1"
},
"resolutions": {
"backbone": "~1.2.3"
}
}

8
dist/css/grapes.min.css

File diff suppressed because one or more lines are too long

52882
dist/grapes.js

File diff suppressed because it is too large

30
dist/grapes.min.js

File diff suppressed because one or more lines are too long

1
dist/grapes.min.js.map

File diff suppressed because one or more lines are too long

14
docs/.vuepress/components/Demo.vue

@ -0,0 +1,14 @@
<template>
<div class="demo-container">
<slot/>
</div>
</template>
<style scoped>
.demo-container {
min-height: 20px;
border: 1px solid #eee;
border-radius: 2px;
padding: 25px 35px;
}
</style>

28
docs/.vuepress/components/DemoBasicBlocks.vue

@ -0,0 +1,28 @@
<template>
<div>
<div class="gjs" id="gjs2">
<h1>Hello World Component!</h1>
</div>
<div id="blocks2"></div>
</div>
</template>
<script>
module.exports = {
mounted() {
const utils = require('./demos/utils.js');
window.editor2 = grapesjs.init(utils.gjsConfigBlocks);
}
}
</script>
<style>
.gjs {
border: 3px solid #444;
}
.gjs-block {
width: auto;
height: auto;
min-height: auto;
}
</style>

14
docs/.vuepress/components/DemoCanvasOnly.vue

@ -0,0 +1,14 @@
<template src="./demos/DemoCanvasOnly.html">
</template>
<script>
module.exports = {
mounted() {
const utils = require('./demos/utils.js');
const editor = grapesjs.init(utils.gjsConfigStart);
}
}
</script>
<style src="./demos/DemoCanvasOnly.css">
</style>

47
docs/.vuepress/components/DemoCustomPanels.vue

@ -0,0 +1,47 @@
<template>
<div>
<div class="panel__top" id="panel__top3">
<div class="panel__basic-actions" id="panel__basic-actions3"></div>
</div>
<div class="gjs" id="gjs3">
<h1>Hello World Component!</h1>
</div>
<div id="blocks3"></div>
</div>
</template>
<script>
module.exports = {
mounted() {
const utils = require('./demos/utils.js');
const editor3 = grapesjs.init(utils.gjsConfigPanels);
editor3.Panels.addPanel(Object.assign({}, utils.panelTop, {
el: '#panel__top3'
}));
editor3.Panels.addPanel(Object.assign({}, utils.panelBasicActions, {
el: '#panel__basic-actions3'
}));
window.editor3 = editor3;
}
}
</script>
<style>
.panel__top {
padding: 0;
width: 100%;
display: flex;
position: initial;
justify-content: center;
justify-content: space-between;
}
.panel__basic-actions {
position: initial;
}
.content pre {
padding-top: 0;
padding-bottom: 0;
margin-top: 0;
margin-bottom: 0;
}
</style>

55
docs/.vuepress/components/DemoDevices.vue

@ -0,0 +1,55 @@
<template>
<div>
<div class="panel__top" id="panel__top7">
<div class="panel__basic-actions" id="panel__basic-actions7"></div>
<div class="panel__devices" id="panel__devices7"></div>
<div class="panel__switcher" id="panel__switcher7"></div>
</div>
<div class="editor-row">
<div class="editor-canvas">
<div class="gjs" id="gjs7">
<h1>Hello World Component!</h1>
</div>
</div>
<div class="panel__right" id="panel__right7">
<div class="layers-container" id="layers-container7"></div>
<div class="styles-container" id="styles-container7"></div>
<div class="traits-container" id="traits-container7"></div>
</div>
</div>
<div id="blocks7"></div>
</div>
</template>
<script>
module.exports = {
mounted() {
const utils = require('./demos/utils.js');
const editor7 = grapesjs.init(utils.gjsConfigDevices);
editor7.Panels.addPanel(Object.assign({}, utils.panelTop, {
el: '#panel__top7'
}));
editor7.Panels.addPanel(Object.assign({}, utils.panelBasicActions, {
el: '#panel__basic-actions7'
}));
editor7.Panels.addPanel(Object.assign({}, utils.panelSidebar, {
el: '#panel__right7'
}));
editor7.Panels.addPanel(Object.assign({}, utils.panelSwitcherTraits, {
el: '#panel__switcher7'
}));
editor7.Panels.addPanel(Object.assign({}, utils.panelDevices, {
el: '#panel__devices7'
}));
window.editor7 = editor7;
}
}
</script>
<style>
.panel__devices {
position: initial;
}
</style>

39
docs/.vuepress/components/DemoLayers.vue

@ -0,0 +1,39 @@
<template>
<div>
<div class="panel__top" id="panel__top4">
<div class="panel__basic-actions" id="panel__basic-actions4"></div>
</div>
<div class="editor-row">
<div class="editor-canvas">
<div class="gjs" id="gjs4">
<h1>Hello World Component!</h1>
</div>
</div>
<div class="panel__right" id="panel__right4">
<div id="layers-container"></div>
</div>
</div>
<div id="blocks4"></div>
</div>
</template>
<script>
module.exports = {
mounted() {
const utils = require('./demos/utils.js');
const editor4 = grapesjs.init(utils.gjsConfigLayers);
editor4.Panels.addPanel(Object.assign({}, utils.panelTop, {
el: '#panel__top4'
}));
editor4.Panels.addPanel(Object.assign({}, utils.panelBasicActions, {
el: '#panel__basic-actions4'
}));
window.editor4 = editor4;
}
}
</script>
<style src="./demos/DemoLayers.css">
</style>

50
docs/.vuepress/components/DemoStyle.vue

@ -0,0 +1,50 @@
<template>
<div>
<div class="panel__top" id="panel__top5">
<div class="panel__basic-actions" id="panel__basic-actions5"></div>
<div class="panel__switcher" id="panel__switcher5"></div>
</div>
<div class="editor-row">
<div class="editor-canvas">
<div class="gjs" id="gjs5">
<h1>Hello World Component!</h1>
</div>
</div>
<div class="panel__right" id="panel__right5">
<div class="layers-container" id="layers-container5"></div>
<div class="styles-container" id="styles-container5"></div>
</div>
</div>
<div id="blocks5"></div>
</div>
</template>
<script>
module.exports = {
mounted() {
const utils = require('./demos/utils.js');
const editor5 = grapesjs.init(utils.gjsConfigStyle);
editor5.Panels.addPanel(Object.assign({}, utils.panelTop, {
el: '#panel__top5'
}));
editor5.Panels.addPanel(Object.assign({}, utils.panelBasicActions, {
el: '#panel__basic-actions5'
}));
editor5.Panels.addPanel(Object.assign({}, utils.panelSidebar, {
el: '#panel__right5'
}));
editor5.Panels.addPanel(Object.assign({}, utils.panelSwitcher, {
el: '#panel__switcher5'
}));
window.editor5 = editor5;
}
}
</script>
<style>
.panel__switcher {
position: initial;
}
</style>

79
docs/.vuepress/components/DemoTheme.vue

@ -0,0 +1,79 @@
<template>
<div class="gjs__themed">
<div class="panel__top" id="panel__top8">
<div class="panel__basic-actions" id="panel__basic-actions8"></div>
<div class="panel__devices" id="panel__devices8"></div>
<div class="panel__switcher" id="panel__switcher8"></div>
</div>
<div class="editor-row">
<div class="editor-canvas">
<div class="gjs" id="gjs8">
<h1>Hello World Component!</h1>
</div>
</div>
<div class="panel__right" id="panel__right8">
<div class="layers-container" id="layers-container8"></div>
<div class="styles-container" id="styles-container8"></div>
<div class="traits-container" id="traits-container8"></div>
</div>
</div>
<div id="blocks8"></div>
</div>
</template>
<script>
module.exports = {
mounted() {
const utils = require('./demos/utils.js');
const editor8 = grapesjs.init(utils.gjsConfigTheme);
editor8.Panels.addPanel(Object.assign({}, utils.panelTop, {
el: '#panel__top8'
}));
editor8.Panels.addPanel(Object.assign({}, utils.panelBasicActionsIcons, {
el: '#panel__basic-actions8'
}));
editor8.Panels.addPanel(Object.assign({}, utils.panelSidebar, {
el: '#panel__right8'
}));
editor8.Panels.addPanel(Object.assign({}, utils.panelSwitcherTraitsIcons, {
el: '#panel__switcher8'
}));
editor8.Panels.addPanel(Object.assign({}, utils.panelDevicesIcons, {
el: '#panel__devices8'
}));
window.editor8 = editor8;
}
}
</script>
<style lang="stylus">
.gjs__themed {
/* 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;
}
.gjs {
border: none;
}
}
</style>

51
docs/.vuepress/components/DemoTraits.vue

@ -0,0 +1,51 @@
<template>
<div>
<div class="panel__top" id="panel__top6">
<div class="panel__basic-actions" id="panel__basic-actions6"></div>
<div class="panel__switcher" id="panel__switcher6"></div>
</div>
<div class="editor-row">
<div class="editor-canvas">
<div class="gjs" id="gjs6">
<h1>Hello World Component!</h1>
</div>
</div>
<div class="panel__right" id="panel__right6">
<div class="layers-container" id="layers-container6"></div>
<div class="styles-container" id="styles-container6"></div>
<div class="traits-container" id="traits-container6"></div>
</div>
</div>
<div id="blocks6"></div>
</div>
</template>
<script>
module.exports = {
mounted() {
const utils = require('./demos/utils.js');
const editor6 = grapesjs.init(utils.gjsConfigTraits);
editor6.Panels.addPanel(Object.assign({}, utils.panelTop, {
el: '#panel__top6'
}));
editor6.Panels.addPanel(Object.assign({}, utils.panelBasicActions, {
el: '#panel__basic-actions6'
}));
editor6.Panels.addPanel(Object.assign({}, utils.panelSidebar, {
el: '#panel__right6'
}));
editor6.Panels.addPanel(Object.assign({}, utils.panelSwitcherTraits, {
el: '#panel__switcher6'
}));
window.editor6 = editor6;
}
}
</script>
<style>
.panel__switcher {
position: initial;
}
</style>

11
docs/.vuepress/components/demos/DemoCanvasOnly.css

@ -0,0 +1,11 @@
/* Let's highlight canvas boundaries */
#gjs {
border: 3px solid #444;
}
/* Reset some default styling */
.gjs-cv-canvas {
top: 0;
width: 100%;
height: 100%;
}

3
docs/.vuepress/components/demos/DemoCanvasOnly.html

@ -0,0 +1,3 @@
<div id="gjs">
<h1>Hello World Component!</h1>
</div>

14
docs/.vuepress/components/demos/DemoCanvasOnly.js

@ -0,0 +1,14 @@
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: [] },
});

17
docs/.vuepress/components/demos/DemoLayers.css

@ -0,0 +1,17 @@
.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;
}

374
docs/.vuepress/components/demos/utils.js

@ -0,0 +1,374 @@
// Don't know yet why but can't use ES6
var blockManager = {
appendTo: '#blocks2',
blocks: [
{
id: 'section', // id is mandatory
label: '<b>Section</b>',
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, consectetur adipiscing elit</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 dropped in canavas
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` on dropped components
activate: true,
}
]
};
var blockManagerIcons = Object.assign({}, blockManager, {
blocks: [
Object.assign({}, blockManager.blocks[0], {
label: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 3H5c-1.11 0-2 .89-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5a2 2 0 0 0-2-2m0 2v14H5V5h14z"></path></svg>',
}),
Object.assign({}, blockManager.blocks[1], {
label: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M18.5 4l1.16 4.35-.96.26c-.45-.87-.91-1.74-1.44-2.18C16.73 6 16.11 6 15.5 6H13v10.5c0 .5 0 1 .33 1.25.34.25 1 .25 1.67.25v1H9v-1c.67 0 1.33 0 1.67-.25.33-.25.33-.75.33-1.25V6H8.5c-.61 0-1.23 0-1.76.43-.53.44-.99 1.31-1.44 2.18l-.96-.26L5.5 4h13z"></path></svg>',
}),
Object.assign({}, blockManager.blocks[2], {
label: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 5c1.1 0 2 .9 2 2v10c0 1.1-.9 2-2 2H4a2 2 0 0 1-2-2V7c0-1.11.89-2 2-2h16M5 16h14l-4.5-6-3.5 4.5-2.5-3L5 16z"></path></svg>',
}),
]
});
var styleManager = {
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' },
],
}
]
}]
};
var layerManager = { scrollLayers: 0 };
var selectorManager = {};
var traitManager = {};
var 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
}]
};
var panelTop = { id: 'panel-top' };
var panelBasicActions = {
id: 'panel-basic',
buttons: [
{
id: 'visibility',
// active by default
active: true,
className: 'btn-toggle-borders',
label: '<u>B</u>',
// Built-in command
command: 'sw-visibility',
}, {
id: 'export',
className: 'btn-open-export',
label: 'Exp',
command: 'export-template',
// For grouping context of buttons in the same panel
context: 'export-template',
}, {
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();
},
}
],
};
var panelBasicActionsIcons = Object.assign({}, panelBasicActions, {
buttons: [
Object.assign({}, panelBasicActions.buttons[0], {
label: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M15 5h2V3h-2m0 18h2v-2h-2M11 5h2V3h-2m8 2h2V3h-2m0 6h2V7h-2m0 14h2v-2h-2m0-6h2v-2h-2m0 6h2v-2h-2M3 5h2V3H3m0 6h2V7H3m0 6h2v-2H3m0 6h2v-2H3m0 6h2v-2H3m8 2h2v-2h-2m-4 2h2v-2H7M7 5h2V3H7v2z"></path></svg>',
}),
Object.assign({}, panelBasicActions.buttons[1], {
label: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M5 20h14v-2H5m14-9h-4V3H9v6H5l7 7 7-7z"></path></svg>',
}),
Object.assign({}, panelBasicActions.buttons[2], {
label: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M8 3c-1.1 0-2 .9-2 2v4c0 1.1-.9 2-2 2H3v2h1c1.1 0 2 .9 2 2v4c0 1.1.9 2 2 2h2v-2H8v-5c0-1.1-.9-2-2-2 1.1 0 2-.9 2-2V5h2V3m6 0c1.1 0 2 .9 2 2v4c0 1.1.9 2 2 2h1v2h-1c-1.1 0-2 .9-2 2v4c0 1.1-.9 2-2 2h-2v-2h2v-5c0-1.1.9-2 2-2-1.1 0-2-.9-2-2V5h-2V3h2z"></path></svg>',
}),
]
});
var panelSidebar = {
el: '#panel__right4',
id: 'layers',
// 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',
},
};
var buttonShowLayers = {
id: 'show-layers',
active: true,
togglable: false,
label: 'Layers',
command: {
getRowEl(editor) { return editor.getContainer().parentNode.parentNode; },
getLayersEl(row) { return row.querySelector('.layers-container') },
getStyleEl(row) { return row.querySelector('.styles-container') },
run(editor, sender) {
const row = this.getRowEl(editor);
const lmEl = this.getLayersEl(row);
lmEl.style.display = '';
},
stop(editor, sender) {
const row = this.getRowEl(editor);
const lmEl = this.getLayersEl(row);
lmEl.style.display = 'none';
},
},
};
var buttonShowStyle = {
id: 'show-style',
label: 'Styles',
togglable: false,
active: true,
command: {
getRowEl(editor) { return editor.getContainer().parentNode.parentNode; },
getLayersEl(row) { return row.querySelector('.layers-container') },
getStyleEl(row) { return row.querySelector('.styles-container') },
run(editor, sender) {
const row = this.getRowEl(editor);
const smEl = this.getStyleEl(row);
smEl.style.display = '';
},
stop(editor, sender) {
const row = this.getRowEl(editor);
const smEl = this.getStyleEl(row);
smEl.style.display = 'none';
},
},
};
var buttonShowTraits = {
id: 'show-traits',
label: 'Traits',
togglable: false,
active: true,
command: {
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';
},
},
};
var panelSwitcher = {
id: 'panel-switcher',
buttons: [
buttonShowLayers,
buttonShowStyle,
],
};
var panelSwitcherTraits = {
id: 'panel-switcher',
buttons: [
buttonShowLayers,
buttonShowStyle,
buttonShowTraits,
],
};
var panelSwitcherTraitsIcons = {
id: 'panel-switcher',
buttons: [
Object.assign({}, buttonShowLayers, {
label: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 16l7.36-5.73L21 9l-9-7-9 7 1.63 1.27M12 18.54l-7.38-5.73L3 14.07l9 7 9-7-1.63-1.27L12 18.54z"></path></svg>',
}),
Object.assign({}, buttonShowStyle, {
label: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M17.5 12c-.83 0-1.5-.67-1.5-1.5S16.67 9 17.5 9s1.5.67 1.5 1.5-.67 1.5-1.5 1.5m-3-4c-.83 0-1.5-.67-1.5-1.5S13.67 5 14.5 5s1.5.67 1.5 1.5S15.33 8 14.5 8m-5 0C8.67 8 8 7.33 8 6.5S8.67 5 9.5 5s1.5.67 1.5 1.5S10.33 8 9.5 8m-3 4c-.83 0-1.5-.67-1.5-1.5S5.67 9 6.5 9 8 9.67 8 10.5 7.33 12 6.5 12M12 3a9 9 0 0 0 0 18c.83 0 1.5-.67 1.5-1.5 0-.39-.15-.74-.39-1-.23-.27-.38-.62-.38-1 0-.83.67-1.5 1.5-1.5H16c2.76 0 5-2.24 5-5 0-4.42-4.03-8-9-8z"></path></svg>',
}),
Object.assign({}, buttonShowTraits, {
label: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 15.5c-1.93 0-3.5-1.57-3.5-3.5s1.57-3.5 3.5-3.5 3.5 1.57 3.5 3.5-1.57 3.5-3.5 3.5m7.43-2.53c.04-.32.07-.64.07-.97 0-.33-.03-.66-.07-1l2.11-1.63c.19-.15.24-.42.12-.64l-2-3.46c-.12-.22-.39-.31-.61-.22l-2.49 1c-.52-.39-1.06-.73-1.69-.98l-.37-2.65A.506.506 0 0 0 14 2h-4c-.25 0-.46.18-.5.42l-.37 2.65c-.63.25-1.17.59-1.69.98l-2.49-1c-.22-.09-.49 0-.61.22l-2 3.46c-.13.22-.07.49.12.64L4.57 11c-.04.34-.07.67-.07 1 0 .33.03.65.07.97l-2.11 1.66c-.19.15-.25.42-.12.64l2 3.46c.12.22.39.3.61.22l2.49-1.01c.52.4 1.06.74 1.69.99l.37 2.65c.04.24.25.42.5.42h4c.25 0 .46-.18.5-.42l.37-2.65c.63-.26 1.17-.59 1.69-.99l2.49 1.01c.22.08.49 0 .61-.22l2-3.46c.12-.22.07-.49-.12-.64l-2.11-1.66z"></path></svg>',
}),
],
};
var panelDevices = {
id: 'panel-devices',
buttons: [{
id: 'device-desktop',
label: 'D',
command: { run: editor => editor.setDevice('Desktop') },
active: true,
togglable: false,
}, {
id: 'device-mobile',
label: 'M',
command: { run: editor => editor.setDevice('Mobile') },
togglable: false,
}],
};
var panelDevicesIcons = Object.assign({}, panelDevices, {
buttons: [
Object.assign({}, panelDevices.buttons[0], {
label: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M21 14H3V4h18m0-2H3c-1.11 0-2 .89-2 2v12c0 1.1.9 2 2 2h7l-2 3v1h8v-1l-2-3h7c1.1 0 2-.9 2-2V4a2 2 0 0 0-2-2z"></path></svg>',
}),
Object.assign({}, panelDevices.buttons[1], {
label: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M16 18H7V4h9m-4.5 18c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5m4-21h-8A2.5 2.5 0 0 0 5 3.5v17A2.5 2.5 0 0 0 7.5 23h8a2.5 2.5 0 0 0 2.5-2.5v-17A2.5 2.5 0 0 0 15.5 1z"></path></svg>',
}),
]
});
var gjsConfigStart = {
// Indicate where to init the editor. It's also possible to pass an HTMLElement
container: '#gjs',
// Get the content for the canvas direectly 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: [] },
};
var gjsConfigBlocks = Object.assign({}, gjsConfigStart, {
container: '#gjs2',
blockManager,
});
var gjsConfigPanels = Object.assign({}, gjsConfigBlocks, {
container: '#gjs3',
blockManager: Object.assign({}, blockManager, { appendTo: '#blocks3' }),
});
var gjsConfigLayers = Object.assign({}, gjsConfigBlocks, {
container: '#gjs4',
blockManager: Object.assign({}, blockManager, { appendTo: '#blocks4' }),
layerManager: { appendTo: '#layers-container', scrollLayers: 0 },
panels: { defaults: [panelSidebar] }
});
var gjsConfigStyle = Object.assign({}, gjsConfigBlocks, {
container: '#gjs5',
blockManager: Object.assign({}, blockManager, { appendTo: '#blocks5' }),
layerManager: { appendTo: '#layers-container5', scrollLayers: 0 },
styleManager: Object.assign({}, styleManager, { appendTo: '#styles-container5' }),
selectorManager: Object.assign({}, selectorManager, { appendTo: '#styles-container5' }),
});
var gjsConfigTraits = Object.assign({}, gjsConfigBlocks, {
container: '#gjs6',
blockManager: Object.assign({}, blockManager, { appendTo: '#blocks6' }),
layerManager: Object.assign({}, layerManager, { appendTo: '#layers-container6' }),
styleManager: Object.assign({}, styleManager, { appendTo: '#styles-container6' }),
traitManager: Object.assign({}, traitManager, { appendTo: '#traits-container6' }),
selectorManager: Object.assign({}, selectorManager, { appendTo: '#styles-container6' }),
});
var gjsConfigDevices = Object.assign({}, gjsConfigBlocks, {
container: '#gjs7',
blockManager: Object.assign({}, blockManager, { appendTo: '#blocks7' }),
layerManager: Object.assign({}, layerManager, { appendTo: '#layers-container7' }),
styleManager: Object.assign({}, styleManager, { appendTo: '#styles-container7' }),
traitManager: Object.assign({}, traitManager, { appendTo: '#traits-container7' }),
selectorManager: Object.assign({}, selectorManager, { appendTo: '#styles-container7' }),
deviceManager,
});
var gjsConfigTheme = Object.assign({}, gjsConfigBlocks, {
container: '#gjs8',
blockManager: Object.assign({}, blockManagerIcons, { appendTo: '#blocks8' }),
layerManager: Object.assign({}, layerManager, { appendTo: '#layers-container8' }),
styleManager: Object.assign({}, styleManager, { appendTo: '#styles-container8' }),
traitManager: Object.assign({}, traitManager, { appendTo: '#traits-container8' }),
selectorManager: Object.assign({}, selectorManager, { appendTo: '#styles-container8' }),
deviceManager,
});
module.exports = {
gjsConfigStart,
gjsConfigBlocks,
gjsConfigPanels,
gjsConfigLayers,
gjsConfigStyle,
gjsConfigTraits,
gjsConfigDevices,
gjsConfigTheme,
panelTop,
panelBasicActions,
panelBasicActionsIcons,
panelSidebar,
panelSwitcher,
panelSwitcherTraits,
panelSwitcherTraitsIcons,
panelDevices,
panelDevicesIcons,
};

103
docs/.vuepress/config.js

@ -0,0 +1,103 @@
const version = require('./../../package.json').version;
const isDev = process.argv[2] === 'dev';
const devPath = 'https://localhost:8080/dist';
module.exports = {
title: 'GrapesJS',
description: 'GrapesJS documentation',
base: '/docs/',
ga: 'UA-74284223-1',
serviceWorker: false, // Enable Service Worker for offline usage
head: [
['link', { rel: 'icon', href: '/logo-icon.png' }],
['link', { rel: 'stylesheet', href: isDev ? `${devPath}/css/grapes.min.css` : `../stylesheets/grapes.min.css?v${version}` }],
['script', { src: isDev ? `${devPath}/grapes.min.js` : `../js/grapes.min.js?v${version}` }],
],
localesSKIP: {
'/': {
lang: 'en-US',
},
'/it/': {
lang: 'it-IT',
description: 'GrapesJS documentazione',
}
},
themeConfig: {
editLinks: true,
docsDir: 'docs',
docsBranch: 'dev',
repo: 'artf/grapesjs',
editLinkText: 'Edit this page on GitHub',
logo: '/logo.png',
lastUpdated: 'Last Updated',
locales: {
'/': {
selectText: 'EN',
label: 'English',
},
'/it/': {
selectText: 'IT',
label: 'Italiano',
nav: [
{ text: 'Supportaci', link: 'https://opencollective.com/grapesjs' },
],
sidebar: [
'/',
['/getting-started', 'Getting Started'],
]
}
},
nav: [
{ text: 'Docs', link: '/' },
{ text: 'API Reference', link: '/api/' },
{ text: 'Support Us', link: 'https://opencollective.com/grapesjs' },
{ text: 'Twitter', link: 'https://twitter.com/grapesjs' },
],
sidebar: {
'/api/': [
'',
['/api/editor', 'Editor'],
['/api/assets', 'Asset Manager'],
['/api/block_manager', 'Block Manager'],
['/api/commands', 'Commands'],
['/api/components', 'DOM Components'],
['/api/panels', 'Panels'],
['/api/style_manager', 'Style Manager'],
['/api/storage_manager', 'Storage Manager'],
['/api/device_manager', 'Device Manager'],
['/api/selector_manager', 'Selector Manager'],
['/api/css_composer', 'CSS Composer'],
['/api/modal_dialog', 'Modal'],
['/api/rich_text_editor', 'Rich Text Editor'],
['/api/keymaps', 'Keymaps'],
['/api/undo_manager', 'Undo Manager'],
],
'/': [
'',
['/getting-started', 'Getting Started'],
// ['/faq', 'FAQ'],
{
title: 'Modules',
collapsable: false,
children: [
['/modules/Assets', 'Asset Manager'],
['/modules/Blocks', 'Block Manager'],
['/modules/Components', 'Component Manager'],
['/modules/Components-js', 'Components & JS'],
['/modules/Traits', 'Trait Manager'],
['/modules/Style-manager', 'Style Manager'],
['/modules/Storage', 'Storage Manager'],
['/modules/Plugins', 'Plugins'],
]
}, {
title: 'Guides',
collapsable: false,
children: [
['/guides/Replace-Rich-Text-Editor', 'Replace Rich Text Editor'],
]
}
],
}
},
}

9
docs/.vuepress/enhanceApp.js

@ -0,0 +1,9 @@
// We can use this hook to install additional Vue plugins, register global components, or add additional router hooks
module.exports = ({
Vue, // the version of Vue being used in the VuePress app
options, // the options for the root Vue instance
router, // the router instance for the app
siteData // site metadata
}) => {
// ...apply enhancements to the app
}

72
docs/.vuepress/override.styl

@ -0,0 +1,72 @@
$accentColor = #e2627f
$accentColor = #e67891
$navBarColor = white
$scrollBarSize = 8px
$pageWidth = 900px
.navbar {
background-color: rgb(111, 41, 67);
background-image: linear-gradient(120deg, rgb(217, 131, 166), rgb(77, 17, 79));
color: $navBarColor;
border: none;
.logo {
min-width: auto;
}
.site-name {
color: $navBarColor;
}
}
.token.string {
color: $accentColor;
}
@media (min-width: 719px) {
.nav-links a:hover,
.nav-links a.router-link-active {
color: #ffeff2;
}
}
.search-box input {
border: 1px solid transparent;
transition: border 0.25s;
}
.page-nav,
.page-edit,
.content:not(.custom) {
max-width: $pageWidth;
}
.page__getting-started {
.language-js .language-js {
max-height: 300px;
}
}
// Scrollbars
* {
::-webkit-scrollbar-track {}
::-webkit-scrollbar-thumb {
background-color: alpha(black, 0.1);
}
::-webkit-scrollbar {
width: $scrollBarSize;
}
}
.language-js {
::-webkit-scrollbar {
height: $scrollBarSize;
}
::-webkit-scrollbar-thumb {
background-color: alpha(white, 0.3);
}
}

BIN
docs/.vuepress/public/assets-builtin-modal.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

BIN
docs/.vuepress/public/assets-empty-view.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

BIN
docs/.vuepress/public/assets-full-dropzone.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

BIN
docs/.vuepress/public/assets-svg-view.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
docs/.vuepress/public/assets-uploader.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
docs/.vuepress/public/blocks3.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

BIN
docs/.vuepress/public/btn-clicked.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

BIN
docs/.vuepress/public/canvas-panels.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

BIN
docs/.vuepress/public/default-gjs.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
docs/.vuepress/public/default-sm.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
docs/.vuepress/public/default-traits.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
docs/.vuepress/public/demo-view.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

BIN
docs/.vuepress/public/empty-gjs.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
docs/.vuepress/public/enabled-sm.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

12
docs/.vuepress/public/grapes.min.js

File diff suppressed because one or more lines are too long

BIN
docs/.vuepress/public/input-custom-traits.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
docs/.vuepress/public/logo-icon.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
docs/.vuepress/public/logo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
docs/.vuepress/public/new-btn.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
docs/.vuepress/public/new-panel.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
docs/.vuepress/public/style-comp.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

59
docs/.vuepress/theme/CarbonAds.vue

@ -0,0 +1,59 @@
<script>
module.exports = {
render (h) {
return h('div', { class: 'carbon-ads' })
},
mounted () {
this.load()
},
watch: {
'$route' (to, from) {
if (
to.path !== from.path &&
this.$el.querySelector('#carbonads')
) {
this.$el.innerHTML = '';
this.load();
}
}
},
methods: {
load () {
const s = document.createElement('script');
s.id = '_carbonads_js';
s.src = `//cdn.carbonads.com/carbon.js?serve=CKYI5KJU&placement=grapesjscom`;
this.$el.appendChild(s);
}
}
}
</script>
<style lang="stylus">
@import "~@default-theme/styles/config.styl"
.carbon-ads
min-height 102px
padding 1.5rem 1.5rem 0
margin-bottom -0.5rem
font-size 0.75rem
a
color #444
font-weight normal
display inline
.carbon-img
float left
margin-right 1rem
border 1px solid $borderColor
img
display block
.carbon-poweredby
color #999
display block
margin-top 0.5em
@media (max-width: $MQMobile)
.carbon-ads
.carbon-img img
width 100px
height 77px
</style>

17
docs/.vuepress/theme/Layout.vue

@ -0,0 +1,17 @@
<template>
<Layout>
<CarbonAds slot="sidebar-top"/>
</Layout>
</template>
<script>
var Layout = require('@default-theme/Layout.vue').default;
var CarbonAds = require('./CarbonAds.vue').default;
module.exports = {
components: {
Layout,
CarbonAds,
}
}
</script>

458
docs/Home.md

@ -0,0 +1,458 @@
# 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:
```html
<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](./Creating-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
```js
...
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
```js
...
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](./Built-in-commands)). So in this case we gonna create a new one.
```js
...
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](http://grapesjs.com/demo.html) 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
```js
...
// 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
```js
...
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](./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
```js
...
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
```js
...
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
```js
...
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](./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
```js
...
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`:
```js
...
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
```js
...
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
```js
...
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:
```js
...
storageManager: {
type: 'remote',
autosave: false,
},
...
commands: {
defaults: [{
id: 'storeData',
run: function(editor, senderBtn){
editor.store();
},
}]
}
...
```
> Check [Storage Manager API Reference]
[API Reference]: <API-Reference>
[Panels API Reference]: <API-Panels>
[Commands API Reference]: <API-Commands>
[Components API Reference]: <API-Components>
[Style Manager API Reference]: <API-Style-Manager>
[Editor API Reference]: <API-Editor>
[Storage Manager API Reference]: <API-Storage-Manager>

49
docs/README.md

@ -0,0 +1,49 @@
# Introduction
[[toc]]
## What is GrapesJS?
At first look you might probably think it's just another kind of page/HTML builder, but actually is something more. GrapesJS is a multi-purpose, Web Builder Framework, which means it allows you easily create your, drag & drop enabled, builder of "THINGS". For "THINGS" we consider anything web structure related, so HTML at first, but don't just think about web pages, we use HTML-like structure basically everywhere: Newsletters (eg. [MJML](https://mjml.io/)), Native Mobile Applications (eg. [React Native](https://github.com/facebook/react-native)), Native Desktop Applications (eg. [Vuido](https://vuido.mimec.org)), PDFs (eg. [React PDF](https://github.com/diegomura/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 in some of your applications.
GrapesJS comes along with different features and tools which enable you to craft easy to use builders, which will allow your users to create complex HTML-like templates without any knowledge of coding.
## Why GrapesJS?
Mainly, GrapesJS was designed to be used inside some CMS 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. Then, with the same concept, instead of creating just an application we decided to create a framework that could be extended and used by everyone for any kind purpose.
## Quick Start
To show up of what GrapesJS is capable of we have created some presets.
* [grapesjs-preset-webpage](https://github.com/artf/grapesjs-preset-webpage) - [Webpage Builder](http://artf.github.io/grapesjs/demo.html)
* [grapesjs-preset-newsletter](https://github.com/artf/grapesjs-preset-newsletter) - [Newsletter Builder](http://artf.github.io/grapesjs/demo-newsletter-editor.html)
* [grapesjs-mjml](https://github.com/artf/grapesjs-mjml) - [Newsletter Builder with MJML](http://artf.github.io/grapesjs/demo-mjml.html)
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](https://img.shields.io/npm/v/grapesjs.svg?colorB=e67891)](https://www.npmjs.com/package/grapesjs)
You can download GrapesJS from one of this 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`

39
docs/api.js

@ -0,0 +1,39 @@
// This script uses documentation to generate API Reference files
const path = require('path');
const exec = require('child_process').exec;
const docRoot = __dirname;
const srcRoot = path.join(docRoot, '../src/');
const binRoot = path.join(docRoot, '../node_modules/.bin/');
const cmds = [
['editor/index.js', 'editor.md'],
['asset_manager/index.js', 'assets.md'],
['block_manager/index.js', 'block_manager.md'],
['commands/index.js', 'commands.md'],
['dom_components/index.js', 'components.md'],
['panels/index.js', 'panels.md'],
['style_manager/index.js', 'style_manager.md'],
['storage_manager/index.js', 'storage_manager.md'],
['device_manager/index.js', 'device_manager.md'],
['selector_manager/index.js', 'selector_manager.md'],
['css_composer/index.js', 'css_composer.md'],
['modal_dialog/index.js', 'modal_dialog.md'],
['rich_text_editor/index.js', 'rich_text_editor.md'],
['keymaps/index.js', 'keymaps.md'],
['undo_manager/index.js', 'undo_manager.md'],
].map(entry =>
`${binRoot}documentation build ${srcRoot}/${entry[0]} -o ${docRoot}/api/${entry[1]} -f md --shallow --markdown-toc false`)
.join(' && ');
console.log('Start API Reference generation...');
exec(cmds, (error, stdout, stderr) => {
if (error) {
console.error( 'Failed to update API Reference: ', error);
return;
}
stdout.trim().split('\n').forEach(function (line) {
console.info(line);
});
console.log('API Reference generation done!');
});

7
docs/api/README.md

@ -0,0 +1,7 @@
---
pageClass: page__apis
---
# 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](/modules/Plugins.html) and make those extensions reusable and available to others.

255
docs/api/assets.md

@ -0,0 +1,255 @@
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
## AssetManager
You can customize the initial state of the module from the editor initialization, by passing the following [Configuration Object][1]
```js
const editor = grapesjs.init({
assetManager: {
// options
}
})
```
Once the editor is instantiated you can use its API. Before using these methods you should get the module from the instance
```js
const assetManager = editor.AssetManager;
```
- [add][2]
- [get][3]
- [getAll][4]
- [getAllVisible][5]
- [remove][6]
- [store][7]
- [load][8]
- [getContainer][9]
- [getAssetsEl][10]
- [addType][11]
- [getType][12]
- [getTypes][13]
## add
Add new asset/s to the collection. URLs are supposed to be unique
### Parameters
- `asset` **([string][14] \| [Object][15] \| [Array][16]&lt;[string][14]> | [Array][16]&lt;[Object][15]>)** URL strings or an objects representing the resource.
- `opts` **[Object][15]?** Options (optional, default `{}`)
### Examples
```javascript
// 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
- `src` **[string][14]** URL of the asset
### Examples
```javascript
var asset = assetManager.get('http://img.jpg');
```
Returns **[Object][15]** 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
- `src` **[string][14]** URL of the asset
### Examples
```javascript
assetManager.remove('http://img.jpg');
```
Returns **this**
## store
Store assets data to the selected storage
### Parameters
- `noStore` **[Boolean][17]** If true, won't store
### Examples
```javascript
var assets = assetManager.store();
```
Returns **[Object][15]** Data to store
## load
Load data from the passed object.
The fetched data will be added to the collection.
### Parameters
- `data` **[Object][15]** Object of data to load (optional, default `{}`)
### Examples
```javascript
var assets = assetManager.load({
assets: [...]
})
```
Returns **[Object][15]** Loaded assets
## getContainer
Return the Asset Manager Container
Returns **[HTMLElement][18]**
## getAssetsEl
Get assets element container
Returns **[HTMLElement][18]**
## render
Render assets
### Parameters
- `assets` **[array][16]** Assets to render, without the argument will render
all global assets
### Examples
```javascript
// Render all assets
assetManager.render();
// Render some of the assets
const assets = assetManager.getAll();
assetManager.render(assets.filter(
asset => asset.get('category') == 'cats'
));
```
Returns **[HTMLElement][18]**
## addType
Add new type. If you want to get more about type definition we suggest to read the [module's page][19]
### Parameters
- `id` **[string][14]** Type ID
- `definition` **[Object][15]** 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
```javascript
assetManager.addType('my-type', {
model: {},
view: {},
isType: (value) => {},
})
```
## getType
Get type
### Parameters
- `id` **[string][14]** Type ID
Returns **[Object][15]** Type definition
## getTypes
Get types
Returns **[Array][16]**
[1]: https://github.com/artf/grapesjs/blob/master/src/asset_manager/config/config.js
[2]: #add
[3]: #get
[4]: #getall
[5]: #getallvisible
[6]: #remove
[7]: #store
[8]: #load
[9]: #getcontainer
[10]: #getassetsel
[11]: #addtype
[12]: #gettype
[13]: #gettypes
[14]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[15]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[16]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
[17]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
[18]: https://developer.mozilla.org/docs/Web/HTML/Element
[19]: /modules/Assets.html

189
docs/api/block_manager.md

@ -0,0 +1,189 @@
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
## BlockManager
You can customize the initial state of the module from the editor initialization, by passing the following [Configuration Object][1]
```js
const editor = grapesjs.init({
blockManager: {
// options
}
})
```
Once the editor is instantiated you can use its API. Before using these methods you should get the module from the instance
```js
const blockManager = editor.BlockManager;
```
- [add][2]
- [get][3]
- [getAll][4]
- [getAllVisible][5]
- [remove][6]
- [getConfig][7]
- [getCategories][8]
- [getContainer][9]
- [render][10]
## getConfig
Get configuration object
Returns **[Object][11]**
## onLoad
Load default blocks if the collection is empty
## add
Add new block to the collection.
### Parameters
- `id` **[string][12]** Block id
- `opts` **[Object][11]** Options
- `opts.label` **[string][12]** Name of the block
- `opts.content` **[string][12]** HTML content
- `opts.category` **([string][12] \| [Object][11])** Group the block inside a catgegory.
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.attributes` **[Object][11]** Block attributes (optional, default `{}`)
### Examples
```javascript
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
- `id` **[string][12]** Block id
### Examples
```javascript
const block = blockManager.get('h1-block');
console.log(JSON.stringify(block));
// {label: 'Heading', content: '<h1>Put your ...', ...}
```
## getAll
Return all blocks
### Examples
```javascript
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
- `id` **[string][12]** Block id
Returns **Block** Removed block
## getCategories
Get all available categories.
It's possible to add categories only within blocks via 'add()' method
Returns **([Array][13] | Collection)**
## getContainer
Return the Blocks container element
Returns **[HTMLElement][14]**
## render
Render blocks
### Parameters
- `blocks` **[Array][13]** Blocks to render, without the argument will render
all global blocks
### Examples
```javascript
// Render all blocks (inside the global collection)
blockManager.render();
// Render new set of blocks
const blocks = blockManager.getAll();
blockManager.render(blocks.filter(
block => block.get('category') == 'sections'
));
// 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();
```
Returns **[HTMLElement][14]** Rendered element
[1]: https://github.com/artf/grapesjs/blob/master/src/block_manager/config/config.js
[2]: #add
[3]: #get
[4]: #getall
[5]: #getallvisible
[6]: #remove
[7]: #getconfig
[8]: #getcategories
[9]: #getcontainer
[10]: #render
[11]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[12]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[13]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
[14]: https://developer.mozilla.org/docs/Web/HTML/Element

93
docs/api/commands.md

@ -0,0 +1,93 @@
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
## Commands
You can customize the initial state of the module from the editor initialization, by passing the following [Configuration Object][1]
```js
const editor = grapesjs.init({
commands: {
// options
}
})
```
Once the editor is instantiated you can use its API. Before using these methods you should get the module from the instance
```js
const commands = editor.Commands;
```
- [add][2]
- [get][3]
- [has][4]
## add
Add new command to the collection
### Parameters
- `id` **[string][5]** Command's ID
- `command` **([Object][6] \| [Function][7])** 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
```javascript
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
- `id` **[string][5]** Command's ID
### Examples
```javascript
var myCommand = commands.get('myCommand');
myCommand.run();
```
Returns **[Object][6]** Object representing the command
## has
Check if command exists
### Parameters
- `id` **[string][5]** Command's ID
Returns **[Boolean][8]**
[1]: https://github.com/artf/grapesjs/blob/master/src/commands/config/config.js
[2]: #add
[3]: #get
[4]: #has
[5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[7]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function
[8]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean

176
docs/api/components.md

@ -0,0 +1,176 @@
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
## 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][1]
```js
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
```js
const domComponents = editor.DomComponents;
```
- [getWrapper][2]
- [getComponents][3]
- [addComponent][4]
- [clear][5]
- [load][6]
- [store][7]
- [render][8]
## load
Load components from the passed object, if the object is empty will try to fetch them
autonomously from the selected storage
The fetched data will be added to the collection
### Parameters
- `data` **[Object][9]** Object of data to load (optional, default `''`)
Returns **[Object][9]** Loaded data
## store
Store components on the selected storage
### Parameters
- `noStore` **[Boolean][10]** If true, won't store
Returns **[Object][9]** 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
```javascript
// 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
```javascript
// 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][9] | Component | [Array][11]&lt;[Object][9]>)** Component/s to add
- `component.tagName` **[string][12]** Tag name (optional, default `'div'`)
- `component.type` **[string][12]** Type of the component. Available: ''(default), 'text', 'image' (optional, default `''`)
- `component.removable` **[boolean][10]** If component is removable (optional, default `true`)
- `component.draggable` **[boolean][10]** If is possible to move the component around the structure (optional, default `true`)
- `component.droppable` **[boolean][10]** If is possible to drop inside other components (optional, default `true`)
- `component.badgable` **[boolean][10]** If the badge is visible when the component is selected (optional, default `true`)
- `component.stylable` **[boolean][10]** If is possible to style component (optional, default `true`)
- `component.copyable` **[boolean][10]** If is possible to copy&paste the component (optional, default `true`)
- `component.content` **[string][12]** String inside component (optional, default `''`)
- `component.style` **[Object][9]** Style object (optional, default `{}`)
- `component.attributes` **[Object][9]** Attribute object (optional, default `{}`)
### Examples
```javascript
// 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][11]&lt;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][13]**
## clear
Remove all components
Returns **this**
[1]: https://github.com/artf/grapesjs/blob/master/src/dom_components/config/config.js
[2]: #getwrapper
[3]: #getcomponents
[4]: #addcomponent
[5]: #clear
[6]: #load
[7]: #store
[8]: #render
[9]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[10]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
[11]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
[12]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[13]: https://developer.mozilla.org/docs/Web/HTML/Element

228
docs/api/css_composer.md

@ -0,0 +1,228 @@
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
## 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][1]
```js
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
```js
const cssComposer = editor.CssComposer;
```
- [load][2]
- [store][3]
- [add][4]
- [get][5]
- [getAll][6]
- [clear][7]
- [setIdRule][8]
- [getIdRule][9]
- [setClassRule][10]
- [getClassRule][11]
## 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
- `data` **[Object][12]** Object of data to load
Returns **[Object][12]** Loaded rules
## store
Store data to the selected storage
### Parameters
- `noStore` **[Boolean][13]** If true, won't store
Returns **[Object][12]** Data to store
## add
Add new rule to the collection, if not yet exists with the same selectors
### Parameters
- `selectors` **[Array][14]&lt;Selector>** Array of selectors
- `state` **[String][15]** Css rule state
- `width` **[String][15]** For which device this style is oriented
- `opts` **[Object][12]** Other options for the rule (optional, default `{}`)
### Examples
```javascript
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
- `selectors` **[Array][14]&lt;Selector>** Array of selectors
- `state` **[String][15]** Css rule state
- `width` **[String][15]** For which device this style is oriented
- `ruleProps` **[Object][12]** Other rule props
### Examples
```javascript
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**
## setIdRule
Add/update the CSS rule with id selector
### Parameters
- `name` **[string][15]** Id selector name, eg. 'my-id'
- `style` **[Object][12]** Style properties and values (optional, default `{}`)
- `opts` **[Object][12]** Custom options, like `state` and `mediaText` (optional, default `{}`)
### Examples
```javascript
const rule = cc.setIdRule('myid', { color: 'red' });
const ruleHover = cc.setIdRule('myid', { color: 'blue' }, { state: 'hover' });
// This will add current CSS:
// #myid { color: red }
// #myid:hover { color: blue }
```
Returns **CssRule** The new/updated rule
## getIdRule
Get the CSS rule by id selector
### Parameters
- `name` **[string][15]** Id selector name, eg. 'my-id'
- `opts` **[Object][12]** Custom options, like `state` and `mediaText` (optional, default `{}`)
### Examples
```javascript
const rule = cc.getIdRule('myid');
const ruleHover = cc.setIdRule('myid', { state: 'hover' });
```
Returns **CssRule**
## setClassRule
Add/update the CSS rule with class selector
### Parameters
- `name` **[string][15]** Class selector name, eg. 'my-class'
- `style` **[Object][12]** Style properties and values (optional, default `{}`)
- `opts` **[Object][12]** Custom options, like `state` and `mediaText` (optional, default `{}`)
### Examples
```javascript
const rule = cc.setClassRule('myclass', { color: 'red' });
const ruleHover = cc.setClassRule('myclass', { color: 'blue' }, { state: 'hover' });
// This will add current CSS:
// .myclass { color: red }
// .myclass:hover { color: blue }
```
Returns **CssRule** The new/updated rule
## getClassRule
Get the CSS rule by class selector
### Parameters
- `name` **[string][15]** Class selector name, eg. 'my-class'
- `opts` **[Object][12]** Custom options, like `state` and `mediaText` (optional, default `{}`)
### Examples
```javascript
const rule = cc.getClassRule('myclass');
const ruleHover = cc.getClassRule('myclass', { state: 'hover' });
```
Returns **CssRule**
[1]: https://github.com/artf/grapesjs/blob/master/src/css_composer/config/config.js
[2]: #load
[3]: #store
[4]: #add
[5]: #get
[6]: #getall
[7]: #clear
[8]: #setidrule
[9]: #getidrule
[10]: #setclassrule
[11]: #getclassrule
[12]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[13]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
[14]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
[15]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String

87
docs/api/device_manager.md

@ -0,0 +1,87 @@
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
## DeviceManager
You can customize the initial state of the module from the editor initialization, by passing the following [Configuration Object][1]
```js
const editor = grapesjs.init({
deviceManager: {
// options
}
})
```
Once the editor is instantiated you can use its API. Before using these methods you should get the module from the instance
```js
const deviceManager = editor.DeviceManager;
```
- [add][2]
- [get][3]
- [getAll][4]
## add
Add new device to the collection. URLs are supposed to be unique
### Parameters
- `name` **[string][5]** Device name
- `width` **[string][5]** Width of the device
- `opts` **[Object][6]** Custom options
### Examples
```javascript
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
- `name` **[string][5]** Name of the device
### Examples
```javascript
var device = deviceManager.get('Tablet');
console.log(JSON.stringify(device));
// {name: 'Tablet', width: '900px'}
```
## getAll
Return all devices
### Examples
```javascript
var devices = deviceManager.getAll();
console.log(JSON.stringify(devices));
// [{name: 'Desktop', width: ''}, ...]
```
Returns **Collection**
[1]: https://github.com/artf/grapesjs/blob/master/src/device_manager/config/config.js
[2]: #add
[3]: #get
[4]: #getAll
[5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object

512
docs/api/editor.md

@ -0,0 +1,512 @@
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
## 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][1]
```js
const editor = grapesjs.init({
// options
});
```
## Available Events
You can make use of available events in this way
```js
editor.on('EVENT-NAME', (some, argument) => {
// do something
})
```
### Components
- `component:add` - Triggered when a new component is added to the editor, the model is passed as an argument to the callback
- `component:remove` - Triggered when a component is removed, the model is passed as an argument to the callback
- `component:clone` - Triggered when a new component is added by a clone command, the model is passed as an argument to the callback
- `component: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
### 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: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
### 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);`)
### General
- `canvasScroll` - Triggered when the canvas is scrolle
- `undo` - Undo executed
- `redo` - Redo executed
- `load` - When the editor is loaded
## getConfig
Returns configuration object
### Parameters
- `prop` **[string][2]?** Property name
Returns **any** Returns the configuration object or
the value of the specified property
## getHtml
Returns HTML built inside canvas
### Parameters
- `opts`
Returns **[string][2]** HTML string
## getCss
Returns CSS built inside canvas
### Parameters
- `opts` **[Object][3]** Options (optional, default `{}`)
Returns **[string][2]** CSS string
## getJs
Returns JS of all components
Returns **[string][2]** JS string
## getComponents
Returns components in JSON format object
Returns **[Object][3]**
## setComponents
Set components inside editor's canvas. This method overrides actual components
### Parameters
- `components` **([Array][4]&lt;[Object][3]> | [Object][3] \| [string][2])** HTML string or components model
### Examples
```javascript
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][4]&lt;[Object][3]> | [Object][3] \| [string][2])** HTML string or components model
- `opts` **[Object][3]** Options
- `opts.avoidUpdateStyle` **[Boolean][5]** 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
```javascript
editor.addComponents('<div class="cls">New component</div>');
// or
editor.addComponents({
type: 'text',
classes:['cls'],
content: 'New component'
});
```
Returns **(Model | [Array][4]&lt;Model>)**
## getStyle
Returns style in JSON format object
Returns **[Object][3]**
## setStyle
Set style inside editor's canvas. This method overrides actual style
### Parameters
- `style` **([Array][4]&lt;[Object][3]> | [Object][3] \| [string][2])** CSS string or style model
### Examples
```javascript
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][4]**
## 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][6])** Component to select
### Examples
```javascript
// Select dropped block
editor.on('block:drag:stop', function(model) {
editor.select(model);
});
```
Returns **this**
## selectAdd
Add component to selection
### Parameters
- `el` **(Component | [HTMLElement][6] \| [Array][4])** Component to select
### Examples
```javascript
editor.selectAdd(model);
```
Returns **this**
## selectRemove
Remove component from selection
### Parameters
- `el` **(Component | [HTMLElement][6] \| [Array][4])** Component to select
### Examples
```javascript
editor.selectRemove(model);
```
Returns **this**
## selectToggle
Toggle component selection
### Parameters
- `el` **(Component | [HTMLElement][6] \| [Array][4])** Component to select
### Examples
```javascript
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
- `name` **[string][2]** Name of the device
### Examples
```javascript
editor.setDevice('Tablet');
```
Returns **this**
## getDevice
Return the actual active device
### Examples
```javascript
var device = editor.getDevice();
console.log(device);
// 'Tablet'
```
Returns **[string][2]** Device name
## runCommand
Execute command
### Parameters
- `id` **[string][2]** Command ID
- `options` **[Object][3]** Custom options (optional, default `{}`)
### Examples
```javascript
editor.runCommand('myCommand', {someValue: 1});
```
Returns **any** The return is defined by the command
## stopCommand
Stop the command if stop method was provided
### Parameters
- `id` **[string][2]** Command ID
- `options` **[Object][3]** Custom options (optional, default `{}`)
### Examples
```javascript
editor.stopCommand('myCommand', {someValue: 1});
```
Returns **any** The return is defined by the command
## store
Store data to the current storage
### Parameters
- `clb` **[Function][7]** Callback function
Returns **[Object][3]** Stored data
## load
Load data from the current storage
### Parameters
- `clb` **[Function][7]** Callback function
Returns **[Object][3]** Stored data
## getContainer
Returns container element. The one which was indicated as 'container'
on init method
Returns **[HTMLElement][6]**
## getDirtyCount
Return the count of changes made to the content and not yet stored.
This count resets at any `store()`
Returns **[number][8]**
## setCustomRte
Replace the built-in Rich Text Editor with a custom one.
### Parameters
- `obj` **[Object][3]** Custom RTE Interface
### Examples
```javascript
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
}
});
```
## on
Attach event
### Parameters
- `event` **[string][2]** Event name
- `callback` **[Function][7]** Callback function
Returns **this**
## off
Detach event
### Parameters
- `event` **[string][2]** Event name
- `callback` **[Function][7]** Callback function
Returns **this**
## trigger
Trigger event
### Parameters
- `event` **[string][2]** Event to trigger
Returns **this**
## destroy
Destroy the editor
## render
Render editor
Returns **[HTMLElement][6]**
[1]: https://github.com/artf/grapesjs/blob/master/src/editor/config/config.js
[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
[5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
[6]: https://developer.mozilla.org/docs/Web/HTML/Element
[7]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function
[8]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number

130
docs/api/keymaps.md

@ -0,0 +1,130 @@
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
## Keymaps
You can customize the initial state of the module from the editor initialization
```js
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
```js
const keymaps = editor.Keymaps;
```
- [getConfig][1]
- [add][2]
- [get][3]
- [getAll][4]
- [remove][5]
## getConfig
Get module configurations
Returns **[Object][6]** Configuration object
## add
Add new keymap
### Parameters
- `id` **[string][7]** Keymap id
- `keys` **[string][7]** Keymap keys, eg. `ctrl+a`, `⌘+z, ctrl+z`
- `handler` **([Function][8] \| [string][7])** Keymap handler, might be a function
### Examples
```javascript
// '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][6]** Added keymap
or just a command id as a string
## get
Get the keymap by id
### Parameters
- `id` **[string][7]** Keymap id
### Examples
```javascript
keymaps.get('ns:my-keymap');
// -> {keys, handler};
```
Returns **[Object][6]** Keymap object
## getAll
Get all keymaps
### Examples
```javascript
keymaps.getAll();
// -> {id1: {}, id2: {}};
```
Returns **[Object][6]**
## remove
Remove the keymap by id
### Parameters
- `id` **[string][7]** Keymap id
### Examples
```javascript
keymaps.remove('ns:my-keymap');
// -> {keys, handler};
```
Returns **[Object][6]** Removed keymap
[1]: #getconfig
[2]: #add
[3]: #get
[4]: #getAll
[5]: #remove
[6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[7]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[8]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function

111
docs/api/modal_dialog.md

@ -0,0 +1,111 @@
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
## Modal
You can customize the initial state of the module from the editor initialization, by passing the following [Configuration Object][1]
```js
const editor = grapesjs.init({
modal: {
// options
}
})
```
Once the editor is instantiated you can use its API. Before using these methods you should get the module from the instance
```js
const modal = editor.Modal;
```
- [open][2]
- [close][3]
- [isOpen][4]
- [setTitle][5]
- [getTitle][6]
- [setContent][7]
- [getContent][8]
## open
Open the modal window
Returns **this**
## close
Close the modal window
Returns **this**
## isOpen
Checks if the modal window is open
Returns **[Boolean][9]**
## setTitle
Set the title to the modal window
### Parameters
- `title` **[string][10]** Title
### Examples
```javascript
modal.setTitle('New title');
```
Returns **this**
## getTitle
Returns the title of the modal window
Returns **[string][10]**
## setContent
Set the content of the modal window
### Parameters
- `content` **([string][10] \| [HTMLElement][11])** Content
### Examples
```javascript
modal.setContent('<div>Some HTML content</div>');
```
Returns **this**
## getContent
Get the content of the modal window
Returns **[string][10]**
[1]: https://github.com/artf/grapesjs/blob/master/src/modal_dialog/config/config.js
[2]: #open
[3]: #close
[4]: #isopen
[5]: #settitle
[6]: #gettitle
[7]: #setcontent
[8]: #getcontent
[9]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
[10]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[11]: https://developer.mozilla.org/docs/Web/HTML/Element

207
docs/api/panels.md

@ -0,0 +1,207 @@
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
## Panels
You can customize the initial state of the module from the editor initialization, by passing the following [Configuration Object][1]
```js
const editor = grapesjs.init({
panels: {
// options
}
})
```
Once the editor is instantiated you can use its API. Before using these methods you should get the module from the instance
```js
const panelManager = editor.Panels;
```
- [addPanel][2]
- [addButton][3]
- [removeButton][4]
- [getButton][5]
- [getPanel][6]
- [getPanels][7]
- [getPanelsEl][8]
- [removePanel][9]
- [removeButton][10]
## getPanels
Returns the collection of panels
Returns **Collection** Collection of panel
## getPanelsEl
Returns panels element
Returns **[HTMLElement][11]**
## addPanel
Add new panel to the collection
### Parameters
- `panel` **([Object][12] | Panel)** Object with right properties or an instance of Panel
### Examples
```javascript
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][12] | Panel | [String][13])** Object with right properties or an instance of Panel or Painel id
### Examples
```javascript
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
- `id` **[string][13]** Id string
### Examples
```javascript
var myPanel = panelManager.getPanel('myNewPanel');
```
Returns **(Panel | null)**
## addButton
Add button to the panel
### Parameters
- `panelId` **[string][13]** Panel's ID
- `button` **([Object][12] | Button)** Button object or instance of Button
### Examples
```javascript
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
- `panelId` **[string][13]** Panel's ID
- `button` **([Object][12] | Button | [String][13])** Button object or instance of Button or button id
### Examples
```javascript
const removedButton = panelManager.removeButton('myNewPanel',{
id: 'myNewButton',
className: 'someClass',
command: 'someCommand',
attributes: { title: 'Some title'},
active: false,
});
// It's also possible to use the button id
const removedButton = panelManager.removeButton('myNewPanel','myNewButton');
```
Returns **(Button | null)** Removed button.
## getButton
Get button from the panel
### Parameters
- `panelId` **[string][13]** Panel's ID
- `id` **[string][13]** Button's ID
### Examples
```javascript
var button = panelManager.getButton('myPanel','myButton');
```
Returns **(Button | null)**
[1]: https://github.com/artf/grapesjs/blob/master/src/panels/config/config.js
[2]: #addpanel
[3]: #addbutton
[4]: #removebutton
[5]: #getbutton
[6]: #getpanel
[7]: #getpanels
[8]: #getpanelsel
[9]: #removepanel
[10]: #removeButton
[11]: https://developer.mozilla.org/docs/Web/HTML/Element
[12]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[13]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String

139
docs/api/rich_text_editor.md

@ -0,0 +1,139 @@
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
## RichTextEditor
This module allows to customize the built-in toolbar of the Rich Text Editor and use commands from the [HTML Editing APIs][1].
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][2]
```js
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
```js
const rte = editor.RichTextEditor;
```
- [add][3]
- [get][4]
- [getAll][5]
- [remove][6]
- [getToolbarEl][7]
## add
Add a new action to the built-in RTE toolbar
### Parameters
- `name` **[string][8]** Action name
- `action` **[Object][9]** Action options (optional, default `{}`)
### Examples
```javascript
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
- `name` **[string][8]** Action name
### Examples
```javascript
const action = rte.get('bold');
// {name: 'bold', ...}
```
Returns **[Object][9]**
## getAll
Get all actions
Returns **[Array][10]**
## remove
Remove the action from the toolbar
### Parameters
- `name` **[string][8]**
### Examples
```javascript
const action = rte.remove('bold');
// {name: 'bold', ...}
```
Returns **[Object][9]** Removed action
## getToolbarEl
Get the toolbar element
Returns **[HTMLElement][11]**
[1]: https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand
[2]: https://github.com/artf/grapesjs/blob/master/src/rich_text_editor/config/config.js
[3]: #add
[4]: #get
[5]: #getall
[6]: #remove
[7]: #gettoolbarel
[8]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[9]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[10]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
[11]: https://developer.mozilla.org/docs/Web/HTML/Element

139
docs/api/selector_manager.md

@ -0,0 +1,139 @@
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
## SelectorManager
Selectors in GrapesJS are used in CSS Composer inside Rules and in Components as classes. To get better this concept let's take
a look at this code:
```css
span > #send-btn.btn{
...
}
```
```html
<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][1]
```js
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
```js
const selectorManager = editor.SelectorManager;
```
- [getConfig][2]
- [add][3]
- [addClass][4]
- [get][5]
- [getAll][6]
## getConfig
Get configuration object
Returns **[Object][7]**
## add
Add a new selector to collection if it's not already exists. Class type is a default one
### Parameters
- `name` **[String][8]** Selector name
- `opts` **[Object][7]** Selector options (optional, default `{}`)
- `opts.label` **[String][8]** Label for the selector, if it's not provided the label will be the same as the name (optional, default `''`)
- `opts.type` **[String][8]** Type of the selector. At the moment, only 'class' (1) is available (optional, default `1`)
### Examples
```javascript
var selector = selectorManager.add('selectorName');
// Same as
var selector = selectorManager.add('selectorName', {
type: 1,
label: 'selectorName'
});
```
Returns **Model**
## addClass
Add class selectors
### Parameters
- `classes` **([Array][9] \| [string][8])** Array or string of classes
### Examples
```javascript
sm.addClass('class1');
sm.addClass('class1 class2');
sm.addClass(['class1', 'class2']);
// -> [SelectorObject, ...]
```
Returns **[Array][9]** Array of added selectors
## get
Get the selector by its name
### Parameters
- `name` **[String][8]** Selector name
- `type` (optional, default `Selector.TYPE_CLASS`)
- `tyoe` **[String][8]** Selector type
### Examples
```javascript
var selector = selectorManager.get('selectorName');
```
Returns **(Model | null)**
## getAll
Get all selectors
Returns **Collection**
[1]: https://github.com/artf/grapesjs/blob/master/src/selector_manager/config/config.js
[2]: #getconfig
[3]: #add
[4]: #addclass
[5]: #get
[6]: #getAll
[7]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[8]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[9]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array

222
docs/api/storage_manager.md

@ -0,0 +1,222 @@
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
## StorageManager
You can customize the initial state of the module from the editor initialization, by passing the following [Configuration Object][1]
```js
const editor = grapesjs.init({
storageManager: {
// options
}
})
```
Once the editor is instantiated you can use its API. Before using these methods you should get the module from the instance
```js
const storageManager = editor.StorageManager;
```
- [getConfig][2]
- [isAutosave][3]
- [setAutosave][4]
- [getStepsBeforeSave][5]
- [setStepsBeforeSave][6]
- [setStepsBeforeSave][6]
- [getStorages][7]
- [getCurrent][8]
- [getCurrentStorage][9]
- [setCurrent][10]
- [add][11]
- [get][12]
- [store][13]
- [load][14]
## getConfig
Get configuration object
Returns **[Object][15]**
## isAutosave
Checks if autosave is enabled
Returns **[Boolean][16]**
## setAutosave
Set autosave value
### Parameters
- `v` **[Boolean][16]**
Returns **this**
## getStepsBeforeSave
Returns number of steps required before trigger autosave
Returns **[number][17]**
## setStepsBeforeSave
Set steps required before trigger autosave
### Parameters
- `v` **[number][17]**
Returns **this**
## add
Add new storage
### Parameters
- `id` **[string][18]** Storage ID
- `storage` **[Object][15]** Storage wrapper
- `storage.load` **[Function][19]** Load method
- `storage.store` **[Function][19]** Store method
### Examples
```javascript
storageManager.add('local2', {
load: function(keys, clb, clbErr) {
var res = {};
for (var i = 0, len = keys.length; i < len; i++){
var v = localStorage.getItem(keys[i]);
if(v) res[keys[i]] = v;
}
clb(res); // might be called inside some async method
// In case of errors...
// clbErr('Went something wrong');
},
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
- `id` **[string][18]** Storage ID
Returns **([Object][15] | null)**
## getStorages
Returns all storages
Returns **[Array][20]**
## getCurrent
Returns current storage type
Returns **[string][18]**
## setCurrent
Set current storage type
### Parameters
- `id` **[string][18]** Storage ID
Returns **this**
## store
Store key-value resources in the current storage
### Parameters
- `data` **[Object][15]** Data in key-value format, eg. {item1: value1, item2: value2}
- `clb` **[Function][19]** Callback function
### Examples
```javascript
storageManager.store({item1: value1, item2: value2});
```
Returns **([Object][15] | null)**
## load
Load resource from the current storage by keys
### Parameters
- `keys` **([string][18] \| [Array][20]&lt;[string][18]>)** Keys to load
- `clb` **[Function][19]** Callback function
### Examples
```javascript
storageManager.load(['item1', 'item2'], res => {
// res -> {item1: value1, item2: value2}
});
storageManager.load('item1', res => {
// res -> {item1: value1}
});
```
## getCurrentStorage
Get current storage
Returns **Storage**
[1]: https://github.com/artf/grapesjs/blob/master/src/storage_manager/config/config.js
[2]: #getconfig
[3]: #isautosave
[4]: #setautosave
[5]: #getstepsbeforesave
[6]: #setstepsbeforesave
[7]: #getstorages
[8]: #getcurrent
[9]: #getcurrentstorage
[10]: #setcurrent
[11]: #add
[12]: #get
[13]: #store
[14]: #load
[15]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[16]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
[17]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
[18]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[19]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function
[20]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array

318
docs/api/style_manager.md

@ -0,0 +1,318 @@
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
## 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][1]
```js
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
```js
const styleManager = editor.StyleManager;
```
- [getConfig][2]
- [addSector][3]
- [getSector][4]
- [removeSector][5]
- [getSectors][6]
- [addProperty][7]
- [getProperty][8]
- [removeProperty][9]
- [getProperties][10]
- [getModelToStyle][11]
- [getModelToStyle][11]
- [addType][12]
- [getType][13]
- [getTypes][14]
- [createType][15]
## getConfig
Get configuration object
Returns **[Object][16]**
## addSector
Add new sector to the collection. If the sector with the same id already exists,
that one will be returned
### Parameters
- `id` **[string][17]** Sector id
- `sector` **[Object][16]** Object representing sector
- `sector.name` **[string][17]** Sector's label (optional, default `''`)
- `sector.open` **[Boolean][18]** Indicates if the sector should be opened (optional, default `true`)
- `sector.properties` **[Array][19]&lt;[Object][16]>** Array of properties (optional, default `[]`)
### Examples
```javascript
var sector = styleManager.addSector('mySector',{
name: 'My sector',
open: true,
properties: [{ name: 'My property'}]
});
```
Returns **Sector** Added Sector
## getSector
Get sector by id
### Parameters
- `id` **[string][17]** Sector id
### Examples
```javascript
var sector = styleManager.getSector('mySector');
```
Returns **(Sector | null)**
## removeSector
Remove a sector by id
### Parameters
- `id` **[string][17]** Sector id
### Examples
```javascript
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
- `sectorId` **[string][17]** Sector id
- `property` **[Object][16]** Property object
- `property.name` **[string][17]** Name of the property (optional, default `''`)
- `property.property` **[string][17]** CSS property, eg. `min-height` (optional, default `''`)
- `property.type` **[string][17]** Type of the property: integer | radio | select | color | file | composite | stack (optional, default `''`)
- `property.units` **[Array][19]&lt;[string][17]>** Unit of measure available, eg. ['px','%','em']. Only for integer type (optional, default `[]`)
- `property.unit` **[string][17]** Default selected unit from `units`. Only for integer type (optional, default `''`)
- `property.min` **[number][20]** Min possible value. Only for integer type (optional, default `null`)
- `property.max` **[number][20]** Max possible value. Only for integer type (optional, default `null`)
- `property.defaults` **[string][17]** Default value (optional, default `''`)
- `property.info` **[string][17]** Some description (optional, default `''`)
- `property.icon` **[string][17]** Class name. If exists no text will be displayed (optional, default `''`)
- `property.preview` **[Boolean][18]** Show layers preview. Only for stack type (optional, default `false`)
- `property.functionName` **[string][17]** Indicates if value need to be wrapped in some function, for istance `transform: rotate(90deg)` (optional, default `''`)
- `property.properties` **[Array][19]&lt;[Object][16]>** Nested properties for composite and stack type (optional, default `[]`)
- `property.layers` **[Array][19]&lt;[Object][16]>** Layers for stack properties (optional, default `[]`)
- `property.list` **[Array][19]&lt;[Object][16]>** List of possible options for radio and select types (optional, default `[]`)
### Examples
```javascript
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
- `sectorId` **[string][17]** Sector id
- `name` **[string][17]** CSS property name, eg. 'min-height'
### Examples
```javascript
var property = styleManager.getProperty('mySector','min-height');
```
Returns **(Property | null)**
## removeProperty
Remove a property from the sector
### Parameters
- `sectorId` **[string][17]** Sector id
- `name` **[string][17]** CSS property name, eg. 'min-height'
### Examples
```javascript
const property = styleManager.removeProperty('mySector', 'min-height');
```
Returns **Property** Removed property
## getProperties
Get properties of the sector
### Parameters
- `sectorId` **[string][17]** Sector id
### Examples
```javascript
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
- `model` **Model**
Returns **Model**
## addType
Add new property type
### Parameters
- `id` **[string][17]** Type ID
- `definition` **[Object][16]** 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
```javascript
styleManager.addType('my-type', {
model: {},
view: {},
isType: (value) => {
if (value && value.type == 'my-type') {
return value;
}
},
})
```
## getType
Get type
### Parameters
- `id` **[string][17]** Type ID
Returns **[Object][16]** Type definition
## getTypes
Get all types
Returns **[Array][19]**
## createType
Create new property from type
### Parameters
- `id` **[string][17]** Type ID
- `options` **[Object][16]** Options (optional, default `{}`)
- `options.model` **[Object][16]** Custom model object (optional, default `{}`)
- `options.view` **[Object][16]** Custom view object (optional, default `{}`)
### Examples
```javascript
const propView = styleManager.createType('integer', {
model: {units: ['px', 'rem']}
});
propView.render();
propView.model.on('change:value', ...);
someContainer.appendChild(propView.el);
```
Returns **PropertyView**
[1]: https://github.com/artf/grapesjs/blob/master/src/style_manager/config/config.js
[2]: #getconfig
[3]: #addsector
[4]: #getsector
[5]: #removesector
[6]: #getsectors
[7]: #addproperty
[8]: #getproperty
[9]: #removeproperty
[10]: #getproperties
[11]: #getmodeltostyle
[12]: #addtype
[13]: #gettype
[14]: #gettypes
[15]: #createtype
[16]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[17]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[18]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
[19]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
[20]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number

236
docs/api/undo_manager.md

@ -0,0 +1,236 @@
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
## 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
```js
const um = editor.UndoManager;
```
- [getConfig][1]
- [add][2]
- [remove][3]
- [removeAll][4]
- [start][5]
- [stop][6]
- [undo][7]
- [undoAll][8]
- [redo][9]
- [redoAll][10]
- [hasUndo][11]
- [hasRedo][12]
- [getStack][13]
- [clear][14]
## getConfig
Get module configurations
### Examples
```javascript
const config = um.getConfig();
// { ... }
```
Returns **[Object][15]** 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
```javascript
um.add(someModelOrCollection);
```
Returns **this**
## remove
Remove and stop tracking the entity (Model/Collection)
### Parameters
- `entity` **(Model | Collection)** Entity to remove
### Examples
```javascript
um.remove(someModelOrCollection);
```
Returns **this**
## removeAll
Remove all entities
### Examples
```javascript
um.removeAll();
```
Returns **this**
## start
Start/resume tracking changes
### Examples
```javascript
um.start();
```
Returns **this**
## stop
Stop tracking changes
### Examples
```javascript
um.stop();
```
Returns **this**
## undo
Undo last change
### Examples
```javascript
um.undo();
```
Returns **this**
## undoAll
Undo all changes
### Examples
```javascript
um.undoAll();
```
Returns **this**
## redo
Redo last change
### Examples
```javascript
um.redo();
```
Returns **this**
## redoAll
Redo all changes
### Examples
```javascript
um.redoAll();
```
Returns **this**
## hasUndo
Checks if exists an available undo
### Examples
```javascript
um.hasUndo();
```
Returns **[Boolean][16]**
## hasRedo
Checks if exists an available redo
### Examples
```javascript
um.hasRedo();
```
Returns **[Boolean][16]**
## getStack
Get stack of changes
### Examples
```javascript
const stack = um.getStack();
stack.each(item => ...);
```
Returns **Collection**
## clear
Clear the stack
### Examples
```javascript
um.clear();
```
Returns **this**
[1]: #getconfig
[2]: #add
[3]: #remove
[4]: #removeall
[5]: #start
[6]: #stop
[7]: #undo
[8]: #undoall
[9]: #redo
[10]: #redoall
[11]: #hasundo
[12]: #hasredo
[13]: #getstack
[14]: #clear
[15]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[16]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean

28
docs/deploy.sh

@ -0,0 +1,28 @@
#!/usr/bin/env sh
# abort on errors
set -e
# build
npm run docs:build
# navigate into the build output directory
cd docs/.vuepress/dist
# I need to deploy all the documentation inside docs folder
mkdir docs-new
# move all the files from the current directory in docs
mv `\ls -1 ./ | grep -v docs-new` ./docs-new
# fetch the current site, remove the old docs dir and make current the new one
git clone -b gh-pages https://github.com/artf/grapesjs.git tmp && mv tmp/* tmp/.* . && rm -rf tmp
rm -fR docs
mv ./docs-new ./docs
# stage all and commit
git add -A
git commit -m 'deploy docs'
git push https://github.com/artf/grapesjs.git gh-pages
# surge --domain grapesjs.surge.sh
cd -

7
docs/faq.md

@ -0,0 +1,7 @@
---
title: Faq
---
# FAQ
Coming soon

725
docs/getting-started.md

@ -0,0 +1,725 @@
---
title: Getting Started
pageClass: page__getting-started
meta:
- name: keywords
content: grapesjs getting started
---
# Getting Started
This guide is a step-by-step introduction for everyone who wants to start creating its own builder with GrapesJS. This is not a complete guide of all functionalities but just a concise overview of most common modules.
Let's see how to create a completely customized page builder from scratch. The last demo of this page represents the [final result](#final-result)
## Import the library
Before start using GrapesJS you have to import it inside your project, so let's import its latest version
```html
<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 node environment
```js
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 and for this purpose we gonna start from 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, by providing just few helpers but letting the user define the whole interface, this guarantees maximum flexibility.
The main part of the GrapesJS editor is the canvas, this is where you gonna create the whole structure of your templates and you definitely can't miss it. Let's try to initiate the editor with just the canvas and no panels.
<<< @/docs/.vuepress/components/demos/DemoCanvasOnly.html
<<< @/docs/.vuepress/components/demos/DemoCanvasOnly.js
<<< @/docs/.vuepress/components/demos/DemoCanvasOnly.css
<Demo>
<DemoCanvasOnly/>
</Demo>
With just the canvas you're already able to move, copy and delete components from the structure (when you select components in the canvas, the toolbar is shown). For now we just see the example template taken from the container. Let's see now how can we 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 from creating another container and append inside it few basic blocks which we can later use to build more complex structures.
```html{4}
<div id="gjs">
...
</div>
<div id="blocks"></div>
```
```js
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,
}
]
},
});
```
```css
.gjs-block {
width: auto;
height: auto;
min-height: auto;
}
```
<Demo>
<DemoBasicBlocks/>
</Demo>
As you see we add our blocks via the initial configuration, which is ok, but obviously there might be the case you would like to add them dynamically, in this case you have to use the [Block Manager API](api/block_manager.html)
```js
editor.BlockManager.add('my-block-id', {
label: '...',
category: '...',
// ...
})
```
::: tip
If you want to get more about blocks we suggest to read its dedicated page: [Block Manager Module](modules/Blocks.html)
:::
## Define Components
Technically, once you drop your HTML block inside the canvas each element of the content is transformed in GrapesJS Component, which is an object containing informations 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, so, for example, if you add a new attribute to the model, not only it will be available in the export code (will see later how to get it) but also the element you see in the canvas is updated with new attributes.
While this is a common behavior what it's cool about Components that you can create a totally decoupled view and show to the user whatever you desire (so not necessary reflecting the model). For example, by dragging a placeholder text you can fetch and show instead a dynamic content. If want to get more about Custom Components and how to create and extend them, we recommend to check out [Component Manager Module](modules/Components.html).
GrapesJS comes along with few [built-in Components](modules/Components.html#built-in-components) which enable different core features once rendered in canvas. Just to mention few of them, by double clicking on the image component you will see show up the default [Asset Manager](modules/Assets.html), which you can customize or integrate you own, by double clicking on the text component you're able to edit it via the built-in Rich Text Editor, which is also customizable and [replaceable](guides/Replace-Rich-Text-Editor.html).
As we have seen before you can create Blocks directly as Components
```js
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 the [Components API](api/components.html) and see how to interact with components dynamically
:::
An example on how to select some inner component and replace its children with new contents
```js
// 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](api/panels.html)) which trigger commands (the core one or custom).
```html{1,2,3}
<div class="panel__top">
<div class="panel__basic-actions"></div>
</div>
<div id="gjs">
...
</div>
<div id="blocks"></div>
```
```css
.panel__top {
padding: 0;
width: 100%;
display: flex;
position: initial;
justify-content: center;
justify-content: space-between;
}
.panel__basic-actions {
position: initial;
}
```
```js
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();
},
}
],
});
```
<Demo>
<DemoCustomPanels/>
</Demo>
So, 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](api/commands.html) when possible, they allow you to track actions globally and execute also callbacks before and after their execution (you can even interrupt them).
```js
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 the [Panels API](api/panels.html) to see all the available methods
:::
## Layers
Another utility tool you might find useful when working with web elements is a layer manger. It's just 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
```html{4,5,6,7,8,9,10,11}
<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>
```
<<< @/docs/.vuepress/components/demos/DemoLayers.css
```js
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',
},
}]
}
});
```
<Demo>
<DemoLayers/>
</Demo>
## Style Manager
Once you have defined the structure of the template probably 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 from adding one more panel inside the `panel__right` and another one in `panel__top` which will contain a Layer/Style Manager switcher
```html{3,8}
<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>
...
```
```css
.panel__switcher {
position: initial;
}
```
```js
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';
},
});
```
<Demo>
<DemoStyle/>
</Demo>
Inside Style Manager definition we use `buildProps` which helps us create common properties from [available built-in objects](modules/Style-manager.html#built-in-properties) 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](https://github.com/artf/grapesjs/blob/gh-pages/demo.html#L1000)
::: tip
Check the [Style Manager API](api/panels.html) to see how to update sectors and properties dynamically
:::
<!--
To get more about style manager extension check out this guide.
Each component can also indicate what to style and what not.
-- Example component with limit styles
-->
## 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. A common use of traits is the ability 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 just gonna show you how to render available traits, for more details on how to extend them we suggest to read the [Trait Manager Module page](modules/Traits.html).
Let's create a new container for traits, tell the editor where to render it and update the sidebar switcher
```html{5}
...
<div class="panel__right">
<div class="layers-container"></div>
<div class="styles-container"></div>
<div class="traits-container"></div>
</div>
...
```
```js
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';
},
});
```
<Demo>
<DemoTraits/>
</Demo>
Now if you switch to the Trait panel and select some of the inner component you should see its default traits.
## Responsive templates
GrapesJS implements also a module which allows you to work with responsive templates easily. Let's see how to define different devices and some button for device switching
```html{3}
<div class="panel__top">
<div class="panel__basic-actions"></div>
<div class="panel__devices"></div>
<div class="panel__switcher"></div>
</div>
...
```
```css
.panel__devices {
position: initial;
}
```
```js
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')
});
```
<Demo>
<DemoDevices/>
</Demo>
As you can see from the commands definition we just use the `editor.setDevice` method to change the size of the viewport. In case you need to trigger some action on device change you can setup a listener like this:
```js
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:
```js
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 the [Device Manager API](api/panels.html) to get all available methods
:::
## Store & load data
Once you finished with defining you 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](https://github.com/artf/grapesjs-indexeddb) is a good example).
Let's see how the default options looks like
```js
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
}
});
```
Worth noting the default `id` parameter which adds a prefix for all keys to store. If you check the `localStorage` inside the devtool panel you'll see something like `{ 'gjs-components': '....' ...}` in this way it prevents the risk of collisions.
Let's check also the configuration required to setup the remote storage
```js
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 option unchanged, increased changes necessary for autosave triggering and passed remote endpoints.
If you prefer you could also disable the autosaving and do it by yourself using some custom command
```js
// ...
storageManager: {
type: 'remote',
autosave: false,
// ...
},
// ...
commands: {
defaults: [
// ...
{
id: 'store-data',
run(editor) {
editor.store();
},
}
]
}
// ...
```
To get a better overview of the Storage Manager, how correctly you should store/load the template and how to define new storages you have to read the [Storage Manager Module](modules/Storage.html) 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 or if you include GrapesJS styles via SCSS you can make use of its [internal variables](https://github.com/artf/grapesjs/blob/dev/src/styles/scss/_gjs_variables.scss) and declare yours before the import
```scss
// 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 just 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 gonna replace all button labels with SVG icons
```css
/* 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
<Demo id="final-result">
<DemoTheme/>
</Demo>

125
docs/guides/Replace-Rich-Text-Editor.md

@ -0,0 +1,125 @@
---
title: Replace the built-in Rich Text Editor
---
# 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.
[[toc]]
## 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.
```js
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.
```js
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
```js
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:
```js
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](https://github.com/artf/grapesjs-plugin-ckeditor).

584
docs/modules/Assets.md

@ -0,0 +1,584 @@
---
title: Asset Manager
---
# Asset Manager
<p align="center"><img src="http://grapesjs.com/img/sc-grapesjs-assets-1.jpg" alt="GrapesJS - Asset Manager" align="center"/></p>
In this section, you will see how to setup and take the full advantage of built-in Asset Manager in GrapesJS, which is intentionally lightweight and implements just an `image` in its core, but as you'll see next it's easy to extend and create your own asset types.
[[toc]]
## Configuration
To change default configurations you have to pass `assetManager` property with the main configuration object
```js
const editor = grapesjs.init({
...
assetManager: {
assets: [...],
...
}
});
```
You can update most of them later by using `getConfig` inside of the module
```js
const amConfig = editor.AssetManager.getConfig();
```
Below the list of currently available options
```js
// Default assets
// eg. [
// 'https://...image1.png',
// 'https://...image2.png',
// {type: 'image', src: 'https://...image3.png', someOtherCustomProp: 1},
// ..
// ]
assets: [],
// Content to add where there is no assets to show
// eg. 'No <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: {},
// If true, tries to add automatically uploaded assets.
// To make it work the server should respond with a JSON containing assets
// in a data key, eg:
// {
// data: [
// 'https://.../image.png',
// ...
// {src: 'https://.../image2.png'},
// ...
// ]
// }
autoAdd: 1,
// Text on upload input
uploadText: 'Drop files here or click to upload',
// Label for the add button
addBtnText: 'Add image',
// Custom uploadFile function
// @example
// uploadFile: (e) => {
// var files = e.dataTransfer ? e.dataTransfer.files : e.target.files;
// // ...send somewhere
// }
uploadFile: '',
// Handle the image url submit from the built-in 'Add image' form
// @example
// handleAdd: (textFromInput) => {
// // some check...
// editor.AssetManager.add(textFromInput);
// }
handleAdd: '',
// Enable an upload dropzone on the entire editor (not document) when dragging
// files over it
dropzone: 1,
// Open the asset manager once files are been dropped via the dropzone
openAssetsOnDrop: 1,
// Any dropzone content to append inside dropzone element
dropzoneContent: '',
// Default title for the asset manager modal
modalTitle: 'Select Image',
```
Not always docs are in inline with its code, therefore we'd suggest to keep an eye at the current state of configurations by checking the dedicated source file [Asset Manager Config](https://github.com/artf/grapesjs/blob/dev/src/asset_manager/config/config.js)
## Initialization
The Asset Manager is ready to work by default, so just pass few urls to see them loaded
```js
const editor = grapesjs.init({
...
assetManager: {
assets: [
'http://placehold.it/350x250/78c5d6/fff/image1.jpg',
// Pass an object with your properties
{
type: 'image',
src: 'http://placehold.it/350x250/459ba8/fff/image2.jpg',
height: 350,
width: 250
},
{
// As the 'image' is the base type of assets, omitting it will
// be set as `image` by default
src: 'http://placehold.it/350x250/79c267/fff/image3.jpg',
height: 350,
width: 250
},
],
}
});
```
If you want a complete list of available properties check the source [AssetImage Model](https://github.com/artf/grapesjs/blob/dev/src/asset_manager/model/AssetImage.js)
The built-in Asset Manager modal is implemented and is showing up when requested. By default, you can make it appear by dragging Image Components in canvas, double clicking on images and all other stuff related to images (eg. CSS styling)
<img :src="$withBase('/assets-builtin-modal.png')">
Showing up of the modal is registered with a command, so you can make it appear with this
```js
// This command shows only assets with `image` type
editor.runCommand('open-assets');
```
Worth nothing that by doing this you can't do much with assets (if you double click on them nothing happens) and this is because you've not indicated any target. Try just to select an image in your canvas and run this in console (you should first make the editor globally available `window.editor = editor;` in your script)
```js
editor.runCommand('open-assets', {
target: editor.getSelected()
});
```
Now you should be able to change the image of the component.
## Customization
If you want to customize the Asset Manager after the initialization you have to use its [APIs](API-Asset-Manager)
```js
// Get the Asset Manager module first
const am = editor.AssetManager;
```
First of all, it's worth nothing that Asset Manager keeps 2 collections of assets:
* **global** - which is just the one with all available assets, you can get it with `am.getAll()`
* **visible** - this is the collection which is currently rendered by the Asset Manager, you get it with `am.getAllVisible()`
This allows you to decide which assets to show and when. Let's say we'd like to have a category switcher, first of all you gonna add to the **global** collection all your assets (which you may already defined at init by `config.assetManager.assets = [...]`)
```js
am.add([
{
// You can pass any custom property you want
category: 'c1',
src: 'http://placehold.it/350x250/78c5d6/fff/image1.jpg',
}, {
category: 'c1',
src: 'http://placehold.it/350x250/459ba8/fff/image2.jpg',
}, {
category: 'c2',
src: 'http://placehold.it/350x250/79c267/fff/image3.jpg',
}
// ...
]);
```
Now if you call the `render()`, without any argument, you will see all the assets rendered
```js
// without any argument
am.render();
am.getAll().length // <- 3
am.getAllVisible().length // <- 3
```
Ok, now let's show only assets form the first category
```js
const assets = am.getAll();
am.render(assets.filter(
asset => asset.get('category') == 'c1'
));
am.getAll().length // Still have 3 assets
am.getAllVisible().length // but only 2 are shown
```
Obviously, you can mix more arrays of assets
```js
am.render([...assets1, ...assets2, ...assets3]);
```
If you want to customize the asset manager container you can get its `HTMLElement`
```js
am.getContainer().insertAdjacentHTML('afterbegin', '<div><button type="button">Click</button></div>');
```
For more APIs methods check out the [API Reference](API-Asset-Manager)
### Define new Asset type
Generally speaking, an asset is not only an image, it could be a `video`, `svg-icon`, or any other kind of `document`. Each type of the asset is applied in our templates/pages differently. If you need to change the image of the Component all you need is another `url` in `src` attribute, but in case of `svg-icon`, for instance, its not the same, you might want to replace the element with a new `<svg>` content. Besides this you also have to deal with the presentation/preview of the asset inside the panel/modal, like for example showing a thumbnail for big images or the possibility to preview videos.
Defining a new asset it means we have to push on top of the 'Stack of Types' a new layer. This stack is iterated by the editor at any addition of the asset and tries to associate the correct type.
```js
am.add('https://.../image.png');
// string, url, ends with '.png' -> it's an 'image' type
am.add('<svg ...');
// string and starts with '<svg...' -> 'svg' type
am.add({type: 'video', src: '...'});
// an object, has 'video' type key -> 'video' type
```
Obviously, it's up to you tell the editor how to recognize your type and for this purpose you have to use `isType()` method.
Let's see now an example of how we'd start to defining a type like `svg-icon`
```js
am.addType('svg-icon', {
// `value` is for example the argument passed in `am.add(VALUE);`
isType(value) {
// The condition is intentionally simple
if (value.substring(0, 5) == '<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 correctly the appropriate type.
```js
// 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
```js
am.render(am.getAll().filter(
asset => asset.get('type') == 'svg-icon'
));
```
You should see something like this
<img :src="$withBase('/assets-empty-view.png')">
The SVG asset is not correctly rendered and this is because we haven't yet configured its view
```js
am.addType('svg-icon', {
view: {
// `getPreview()` and `getInfo()` are just few helpers, you can
// override the entire template with `template()`
// Check the base `template()` here:
// https://github.com/artf/grapesjs/blob/dev/src/asset_manager/view/AssetView.js
getPreview() {
return `<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
<img :src="$withBase('/assets-svg-view.png')">
Now we have to deal with how to assign our `svgContent` to the selected element
```js
am.addType('svg-icon', {
view: {
// In our case the target is the selected component
updateTarget(target) {
const svg = this.model.get('svgContent');
// Just to make things bit interesting, if it's an image type
// I put the svg as a data uri, content otherwise
if (target.get('type') == 'image') {
// Tip: you can also use `data:image/svg+xml;utf8,<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 which should group the business logic of your asset but usually it's optional so you can skip this part.
```js
// 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 just choose what type to extend and how.
```js
// 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)
```js
// Extend the original `image` and add a confirm dialog before removing it
am.addType('image', {
// As you adding on top of an already defined type you can avoid indicating
// `am.getType('image').view.extend({...` the editor will do it by default
// but you can eventually extend some other type
view: {
// If you want to see more methods to extend check out
// https://github.com/artf/grapesjs/blob/dev/src/asset_manager/view/AssetImageView.js
onRemove(e) {
e.stopPropagation();
const model = this.model;
if (confirm('Are you sure?')) {
model.collection.remove(model);
}
}
},
})
```
## Uploading assets
Asset Manager includes an easy to use, drag and drop, uploader and integrates few UI helpers. The default uploader is already visible when you open the Asset Manager.
<img :src="$withBase('/assets-uploader.png')">
You can click on the uploader to start select your files or just drag them directly from your computer to trigger the uploader. Obviously, before make it work you have to setup your server in order to receive your assets and specify the upload endpoint in configurations
```js
let editor = grapesjs.init({
...
assetManager: {
...
// Upload endpoint, set `false` to disable upload, default `false`
upload: 'https://endpoint/upload/assets',
// The name used in POST to pass uploaded files, default: `'files'`
uploadName: 'files',
...
},
...
});
```
### Listeners
If you want to execute some action before/after the uploading process (eg. loading animation) or even on response, you can make use of these listeners
```js
// The upload is started
editor.on('asset:upload:start', () => {
...
startAnimation();
});
// The upload is ended (completed or not)
editor.on('asset:upload:end', () => {
...
endAnimation();
});
// Error handling
editor.on('asset:upload:error', (err) => {
...
notifyError(err);
});
// Do something on response
editor.on('asset:upload:response', (response) => {
...
});
```
### Response
When the uploading is over, by default (via config parameter `autoAdd: 1`), the editor expects to receive a JSON of uploaded assets in a `data` key as a response and tries to add them to the main collection. The JSON might look like this:
```js
{
data: [
'https://.../image.png',
// ...
{
src: 'https://.../image2.png',
type: 'image',
height: 100,
width: 200,
},
// ...
]
}
```
### Setup Dropzone
There is also another helper which improve the uploading of assets, a full-width editor dropzone.
<img :src="$withBase('/assets-full-dropzone.gif')">
All you have to do is to activate it and possibly set a custom content (you might also want to hide the default uploader)
```js
const editor = grapesjs.init({
...
assetManager: {
...,
dropzone: 1,
dropzoneContent: '<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

74
docs/modules/Blocks.md

@ -0,0 +1,74 @@
---
title: Block Manager
---
# Block Manager
<p align="center"><img src="http://grapesjs.com/img/sc-grapesjs-blocks-prp.jpg" alt="GrapesJS - Block Manager" height="400" align="center"/></p>
The Block is a group of [Components] and can be easily reused inside templates.
To better understand the difference between components and blocks, the component is more atomic so, for example, a single image, a text box or a map fits perfectly in this concept. The block is what the end user will drag inside the canvas, so it could contain a single image (single Component) or the entire section like, for example, the footer with a lot of components inside (texts, images, inputs, etc).
Check [Components] page to see the list of built-in components and how to create your own.
Let's see how to add a new block to the editor using the [Blocks API]
```js
var editor = grapesjs.init({...});
var blockManager = editor.BlockManager;
// 'my-first-block' is the ID of the block
blockManager.add('my-first-block', {
label: 'Simple block',
content: '<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
```js
blockManager.get('my-first-block').set({
label: 'Updated simple block',
attributes: {
title: 'My title'
}
})
```
As you see a simple HTML string is enough to create a block, the editor will do the rest.
If you want you could also pass an object representing the [Component].
```js
blockManager.add('my-map-block', {
label: 'Simple map block',
content: {
type: 'map', // Built-in 'map' component
style: {
height: '350px'
},
removable: false, // Once inserted it can't be removed
}
})
```
From the v0.3.70 it's also possible to pass the HTML string with Component's properties as attributes.
```js
blockManager.add('the-row-block', {
label: '2 Columns',
content: '<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
[Component]: <Component>
[Components]: <Components>
[Blocks API]: <API-Block-Manager>

222
docs/modules/Components-js.md

@ -0,0 +1,222 @@
---
title: Components & JS
---
# Components & JS
In this guide you'll see how to attach component related scripts and deal with external javascript libraries (for stuff like counters, galleries, slideshows, etc.)
[[toc]]
## Basic scripts
Let's see how to create a component with scripts using Blocks.
```js
editor.BlockManager.add('test-block', {
label: 'Test block',
attributes: {class: 'fa fa-text'},
content: {
script: "alert('Hi'); console.log('the element', this)",
// Add some style just to make the component visible
style: {
width: '100px',
height: '100px',
'background-color': 'red',
}
}
});
```
Now if you drag the new block inside the canvas you'll see an alert popup and the message in console, as you might expected.
One thing worth noting is that `this` context is binded to the component element, so, for example, if you want to change some property you'd do `this.innerHTML = 'inner content'`.
One thing you should take in account is how the script is binded to component once rendered in the canvas or in your final template. If you check now the generated HTML coded by the editor (via Export button or `editor.getHtml()`), you might see something like this:
```html
<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:
```html
<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), therefore are NOT part of the current `document` and all your external libraries (eg. JQuery) are not there, but you'll see further how to manage scripted components with dependencies.
One thing you might be concerned about is a string used for the `script`, definitely not the best way to deal with a code, for this reason GrapesJS is able also to handle functions for you, so the previous example might look like this:
```js
editor.BlockManager.add('test-block', {
...
content: {
script: function () {
alert('Hi');
console.log('the element', this);
},
...
}
});
```
Much easier now, but be aware of a string conversion, you can't use variables outside of the function scope, let's see this scenario
```js
var myVar = 'John';
editor.BlockManager.add('test-block', {
...
script: function () {
alert('Hi ' + myVar);
console.log('the element', this);
},
...
});
```
Unfortunately, this won't work as you'll get undefined `myVar` error. The final HTML, with script functions converted to string, will look like this:
```html
<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 actually a solution to make your scripts behave dynamically, you can interpolate properties of the component model.
```js
editor.BlockManager.add('test-block', {
...
content: {
myModelPropName: 'John',
script: function () {
alert('Hi {[ myModelPropName ]}');
console.log('the element', this);
},
...
}
});
```
The final HTML will be:
```html
<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 tags used for the interpolation
```js
var editor = grapesjs.init({
...
// Default values
tagVarStart: '{[ ',
tagVarEnd: ' ]}',
...
});
```
You can use this technique with [property Traits](https://github.com/artf/grapesjs/wiki/Traits#add-traits-to-components) to create highly customizable components.
## Dependencies
As we mentioned above, scripts are executed independently inside the iframe of the canvas, where you won't find any dependency, so exactly as the final HTML generated by the editor.
If you want to make use of external libraries you have basically 2 types of approaches, component related and template related.
### Component related
If you're building, for example, a slider component based on some third-party library you probably would like to include the external file only when the component is actually dragged inside the canvas, in this case, component related approaches is the perfect one as it's loading external libraries dynamically.
All you have to do is to require the dependency when is needed and then call your script.
```js
...
script: function () {
var el = this;
var initMySLider = function() {
CoolSliderJS.init(el);
}
if (typeof CoolSliderJS == 'undefined') {
var script = document.createElement('script');
script.onload = initMySLider;
script.src = 'https://.../coolslider.min.js';
document.body.appendChild(script);
}
},
...
```
### Template related
Some dependency might be highly used along all your components (eg. JQuery) so instead requiring it inside each script you might want to inject it directly inside the canvas:
```js
var editor = grapesjs.init({
...
canvas: {
scripts: ['https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js']
}
});
...
script: function () {
// Do stuff using jquery
$('...');
},
...
```
## Examples
Examples of components using scripts inside
* [grapesjs-navbar](https://github.com/artf/grapesjs-navbar)
* [grapesjs-component-countdown](https://github.com/artf/grapesjs-component-countdown)

270
docs/modules/Components.md

@ -0,0 +1,270 @@
---
title: Component Manager
---
# Component Manager
The Component is the base element for the template composition and, usually, elements like images, text boxes, maps, etc. fit perfectly in this concept. The concept of the component was made to allow the developer to bind different behaviors to different elements. Like for example, opening the Asset Manager on double click of the image.
[[toc]]
## Built-in components
* Default (Basic)
* Text
* Image
* Video
* Link
* Map
* Table
* Row (for the table)
* Cell (for the table)
## How Components work?
When we pass an HTML string to the editor like this:
```html
<div>
<img src="https://path/image" />
<span title="foo">bar</span>
</div>
```
The editor will create and store, for each DOM element, its object representation and all next changes to the template will be made on top of this structure, which will then reflect on canvas. So, each object, usually called *Model* (or state/store), will be the source of truth for the template, but what exactly does it mean? For instance, in more practical way, once the template is rendered on the canvas, if you try to remove one of the elements using the browser inspector and then ask the editor to print the HTML (using `editor.getHtml()`) you'll see, from the code, that the element will still be there, this because the editor relies on Models and not on the DOM inside the canvas. This approach allows us to be extremely flexible on how to generate the final code (from the *Model*) and how to render it inside the canvas (from the *View*).
# Manage Components
## Component recognition
But now, how does the editor recognize which Component to bind to the `img` element and what to do with the `span` one?
Each Component inherits, from the base one, a particular static method
```js
/**
* @param {HTMLElement} el
* @return {Object}
*/
isComponent: function(el) {
...
}
```
This method gives us the possibility to recognize and bind component types to each HTMLElement (div, img, iframe, etc.). Each HTML element introduced inside the canvas will be processed by `isComponent` of all available types and if it matches, the object represented the type should be returned. So, for example, with the image component this method looks like:
```js
// Image component
isComponent: function(el) {
if(el.tagName == 'IMG')
return {type: 'image'};
}
```
Let's try with something that might look a little bit tricky. What about a Google's Map?!? Google's maps are generally embedded as `iframe`s, but the template can be composed by a lot of different `iframe`s, how can I tell the editor that a particular iframe is actually a Google's Map. Well, this part is up to you to understand which is the right pattern to choose, you have the `HTMLElement` so you can make all checks you want and in this particular case this pattern is used:
```js
// Map component
isComponent: function(el) {
if(el.tagName == 'IFRAME' && /maps\.google\.com/.test(el.src)) {
return {type: 'map', src: el.src};
}
},
```
So, as you see, in addition to `tagName` check, we also used the `src` property, but, as you'll see, you can actually override it with your own logic by extending the built-in component.
## Define new Component
Let's see now an example, with another HTML element, which is not handled by default Component types. What about `input` elements?
With the default GrapesJS configuration `input`s are treated just like any other element, you can move it around, style it, etc., but usually we'd like to handle this type of element more specifically. In this case, we have to create a new Component type.
Let's define just few specs for our new *Input* type:
* Can be dropped only inside `form` elements
* Can't drop other elements inside it
* Can change the type of the input (text, password, email, etc.)
* Can make it required for the form
To define a new Component type you need to choose from which built-in Component inherit its properties, in our case we just gonna choose the default one. Let's see a complete example of the new type definition
```js
// Get DomComponents module
var comps = editor.DomComponents;
// Get the model and the view from the default Component type
var defaultType = comps.getType('default');
var defaultModel = defaultType.model;
var defaultView = defaultType.view;
var inputTypes = [
{value: 'text', name: 'Text'},
{value: 'email', name: 'Email'},
{value: 'password', name: 'Password'},
{value: 'number', name: 'Number'},
];
// The `input` will be the Component type ID
comps.addType('input', {
// Define the Model
model: defaultModel.extend({
// Extend default properties
defaults: Object.assign({}, defaultModel.prototype.defaults, {
// Can be dropped only inside `form` elements
draggable: 'form, form *',
// Can't drop other elements inside it
droppable: false,
// Traits (Settings)
traits: ['name', 'placeholder', {
// Change the type of the input (text, password, email, etc.)
type: 'select',
label: 'Type',
name: 'type',
options: inputTypes,
},{
// Can make it required for the form
type: 'checkbox',
label: 'Required',
name: 'required',
}],
}),
},
// The second argument of .extend are static methods and we'll put inside our
// isComponent() method. As you're putting a new Component type on top of the stack,
// not declaring isComponent() might probably break stuff, especially if you extend
// the default one.
{
isComponent: function(el) {
if(el.tagName == 'INPUT'){
return {type: 'input'};
}
},
}),
// Define the View
view: defaultType.view,
});
```
The code above is pretty much self-explanatory and as you see a lot of work is basically done on top of the Model properties.
The *View* is just extending the default one, so to cover also this part let's add some random behavior.
```js
comps.addType('input', {
model: {...},
view: defaultType.view.extend({
// Bind events
events: {
// If you want to bind the event to children elements
// 'click .someChildrenClass': 'methodName',
click: 'handleClick',
dblclick: function(){
alert('Hi!');
}
},
// It doesn't make too much sense this method inside the component
// but it's ok as an example
randomHex: function() {
return '#' + Math.floor(Math.random()*16777216).toString(16);
},
handleClick: function(e) {
this.model.set('style', {color: this.randomHex()}); // <- Affects the final HTML code
this.el.style.backgroundColor = this.randomHex(); // <- Doesn't affect the final HTML code
// Tip: updating the model will reflect the changes to the view, so, in this case,
// if you put the model change after the DOM one this will override the backgroundColor
// change made before
},
// The render() should return 'this'
render: function () {
// Extend the original render method
defaultType.view.prototype.render.apply(this, arguments);
this.el.placeholder = 'Text here'; // <- Doesn't affect the final HTML code
return this;
},
}),
});
```
From the example above you can notice few interesting things: how to bind events, how to update directly the DOM and how to update the model. The difference between updating the DOM and the model is that the HTML code (the one you get with `editor.getHtml()`) is generated from the *Model* so updating directly the DOM will not affect it, it's just the change for the canvas.
## Update Component type
Here an example of how easily you can update/override the component
```js
var originalMap = comps.getType('map');
comps.addType('map', {
model: originalMap.model.extend({
// Override how the component is rendered to HTML
toHTML: function() {
return '<div>My Custom Map</div>';
},
}, {
isComponent: function(el) {
// ... new logic for isComponent
},
}),
view: originalMap.view
});
```
## Components & JS
If you want to know how to create Components with javascript attached (eg. counters, galleries, slideshows, etc.) check the dedicated page
[Components & JS](Components-&-JS)
## Hints
```html
<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`
```html
<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](https://github.com/artf/grapesjs/wiki/Creating-plugins))

151
docs/modules/Plugins.md

@ -0,0 +1,151 @@
---
title: Plugins
---
# Plugins
Creating plugins in GrapesJS is pretty straightforward and here you'll get how to achieve it.
[[toc]]
## Basic plugin
The most simple plugins are just functions that are run when the editor is being built.
```js
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.
```js
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:
```js
export default grapesjs.plugins.add('my-plugin-name', (editor, options) => {
/*
* Here you should rely on GrapesJS APIs, so check 'API Reference' for more info
* For example, you could do something like this to add some new command:
*
* editor.Commands.add(...);
*/
})
```
The name `my-plugin-name` is an ID of your plugin and you'll use it to tell your editor to grab it.
Here is a complete generic example:
```html
<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.
```js
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
```js
export default grapesjs.plugins.add('my-plugin-name', (editor, options) => {
console.log(options);
//{ customField: 'customValue' }
})
```
This also works with plugins that aren't named.
```js
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.
```js
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](https://github.com/artf/grapesjs-plugin-boilerplate) which you can clone and start developing a named plugin immediately. For more informations check the repository
## Popular Plugins
- https://github.com/artf/grapesjs-preset-webpage
- https://github.com/artf/grapesjs-preset-newsletter

298
docs/modules/Storage.md

@ -0,0 +1,298 @@
---
title: Storage Manager
---
# 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
:::
[[toc]]
## Basic configuration
The storage manager is a built-in module implemented inside GrapesJS which allows the persistence of your data. By default, GrapesJS saves the data locally by using the built-in `LocalStorage` which just leverages [localStorage API].
You can initialize the editor with different storage configurations via `storageManager` option:
```js
const editor = grapesjs.init({
...
// Default configurations
storageManager: {
id: 'gjs-', // Prefix identifier that will be used on parameters
type: 'local', // Type of the storage
autosave: true, // Store data automatically
autoload: true, // Autoload stored data on init
stepsBeforeSave: 1, // If autosave enabled, indicates how many changes are necessary before store method is triggered
},
});
```
The `id` option is used to prevent collisions (quite common with localStorage) in case of multiple editors on the same page, therefore you will see parameters passed like `{ 'gjs-components': '...', 'gjs-style': '...', }`
If you need to disable the storage manager you can pass any empty `type`:
```js
...
storageManager: { type: null },
```
For all other available options check directly the [configuration source file](https://github.com/artf/grapesjs/blob/dev/src/storage_manager/config/config.js).
## Setup remote storage
Switching up the remote storage is very simple, it's just a matter of specifying your endpoints for storing and loading, which generally might be also the same (if you rely on HTTP methods).
```js
const editor = grapesjs.init({
...
storageManager: {
type: 'remote',
stepsBeforeSave: 3,
urlStore: 'http://endpoint/store-template/some-id-123',
urlLoad: 'http://endpoint/load-template/some-id-123',
// For custom parameters/headers on requests
params: { _some_token: '....' },
headers: { Authorization: 'Basic ...' },
}
});
```
As you can see we've left some default option unchanged, increased changes necessary for autosave triggering and passed remote endpoints.
## Store and load templates
Even without a fully working endpoint, you can see what is sent from the editor by triggering the store and looking in the network panel of the inspector. GrapesJS sends mainly 4 types of parameters and it prefixes them with the `gjs-` key (you can disable it via `storageManager.id`). From the parameters, you will get the final result in 'gjs-html' and 'gjs-css' and this is what actually your end-users will gonna see on the final template/page. The other two, 'gjs-components' and 'gjs-style', are a JSON representation of your template and therefore those should be used for the template editing. **So be careful**, GrapesJS is able to start from any HTML/CSS but use this approach only for importing already existent HTML templates, once the user starts editing, rely always on JSON objects because the HTML doesn't contain information about your components. You can achieve it in a pretty straightforward way and if you load your page by server-side you don't even need to load asynchronously your data (so you can turn off the `autoload`).
```js
// Lets say, for instance, you start with your already defined HTML template and you'd like to
// import it on fly for the user
const LandingPage = {
html: `<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
```js
editor.load(res => console.log('Load callback'));
```
Similarly, you have the same control over the storing. By default, the `autosave` is enabled and is triggered by how many changes are made to the template (change it via `stepsBeforeSave` option). As before, you can disable this behavior and trigger it manually when you need it
```js
...
const editor = grapesjs.init({
...
storageManager: {
...
autosave: false,
},
});
// Call load somewhere
editor.store(res => console.log('Store callback'));
```
If you need to check changes which yet need to be stored you can use `editor.getDirtyCount()`. At any, successful, store of the editor, it resets the count.
## Setup the server
Server configuration might differ for any use case so generally, it's something up to you on how to make it work, but usually, the flow is pretty straightforward. Create two endpoints, one for storing (eg. `mydomain.com/store-page/123`) and the other one for loading (eg. `mydomain.com/load-page/123`), you can also create just one and distinguish them via HTTP methods (eg. `mydomain.com/page/123`, via GET you load the template, with POST you store it).
When you **store**, the editor doesn't expect any particular result but only a valid response from the server (status code 200).
When you **load** the template, return a JSON object with the data you have (don't forget to include the `id` prefix if it's used)
```js
{
// `gjs-` is the id prefix
'gjs-components': [{ tagName: 'div', ... }, {...}, ...],
'gjs-style': [{...}, {...}, ...],
}
```
Be sure to have a correct `Content-Type` response header, eg. in PHP you would do something like this:
```php
header('Content-Type: application/json');
echo json_encode([
'gjs-components': [...],
'gjs-style': [...],
]);
```
## Storage API
The Storage module has also its own [set of API](https://github.com/artf/grapesjs/wiki/API-Storage-Manager) that allows you to extend and add new functionalities.
### Define new storage
One of the most useful methods of API is the possibility to add new storages. You might think, we have the `local` and `remote` storages, what else do we need, right? Well, let's take as an example the `local` one. As you already know, it relies on [localStorage API] which is really cool and easy to use but one of his specs might be a big limit, by default it has a limited amount of MB to use per site (something around 5MB-10MB, depends on the browser implementation). As an alternative, we can make use of [IndexedDB] which is also quite [well supported](https://caniuse.com/#search=indexedDB) and allows more space usage (each browser implements its own rules, for a better understanding on how browser storage limits work, check [here](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Browser_storage_limits_and_eviction_criteria)).
[IndexedDB configuration](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Using_IndexedDB) might be too much verbose for this guide so we decided to create the [grapesjs-indexeddb] plugin, so you can check its source and see how it's implemented. For this guide we gonna see something more simpler but with the same flow, it'll be just a simple javascript object which stores key-value data, not persistent at all but the concept is the same.
```js
const editor = grapesjs.init({
...
storageManager: { type: 'simple-storage' },
});
// Here our `simple-storage` implementation
const SimpleStorage = {};
editor.StorageManager.add('simple-storage', {
/**
* Load the data
* @param {Array} keys Array containing values to load, eg, ['gjs-components', 'gjs-style', ...]
* @param {Function} clb Callback function to call when the load is ended
* @param {Function} clbErr Callback function to call in case of errors
*/
load(keys, clb, clbErr) {
const result = {};
keys.forEach(key => {
const value = SimpleStorage[key];
if (value) {
result[key] = value;
}
});
// Might be called inside some async method
clb(result);
},
/**
* Store the data
* @param {Object} data Data object to store
* @param {Function} clb Callback function to call when the load is ended
* @param {Function} clbErr Callback function to call in case of errors
*/
store(data, clb, clbErr) {
for (let key in data) {
SimpleStorage[key] = data[key];
}
// Might be called inside some async method
clb();
}
});
```
### Extend storage
Among other needs, you might need to use existing storages to create more complex uses. For example, let's say we would like to mix the local and remote storages inside another one. This is how it would look like:
```js
const sm = editor.StorageManager;
sm.add('local-remote', {
store(data, clb, clbErr) {
const remote = sm.get('remote');
const local = sm.get('local');
// ...
remote.store(data, clb, err => {
// eg. some error on remote side, store it locally
local.store(data, clb, clbError);
});
},
load(keys, clb, clbErr) {
// ...
},
});
```
If you need to completely replace the storage, just use the same id in `add` method
```js
editor.StorageManager.add('local', {
// New logic for the local storage
load() {
// ...
},
store() {
// ...
},
});
```
### Examples
Here you can find some of the plugins extending the Storage Manager
* [grapesjs-indexeddb] - Storage wrapper for IndexedDB
* [grapesjs-firestore] - Storage wrapper for [Cloud Firestore](https://firebase.google.com/docs/firestore)
## Events
Another way to extend storage capabilities is to make use of GrapesJS's event hooks, you can check [here](https://github.com/artf/grapesjs/wiki/API-Editor#storages) the list of all available events for the Storage module. Let's see some of the cases where you might want to use them:
* Loading animation on storage requests
```js
editor.on('storage:start', startLoading);
editor.on('storage:end', endLoading);
```
* Error handling
```js
editor.on('storage:error', (err) => {
alert(`Error: ${err}`);
});
```
* Extend parameters to store
```js
editor.on('storage:start:store', (objectToStore) => {
if (needToAddExtraParam) {
objectToStore.customHtml = `<div>...${editor.getHtml()}...</div>`;
}
});
```
* Do stuff post load
```js
editor.on('storage:end:load', (resultObject) => {
if (resultObject.hasSomeKey) {
// do stuff
}
});
```
[grapesjs-indexeddb]: <https://github.com/artf/grapesjs-indexeddb>
[grapesjs-firestore]: <https://github.com/artf/grapesjs-firestore>
[localStorage API]: <https://developer.mozilla.org/it/docs/Web/API/Window/localStorage>
[IndexedDB]: <https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API>

31
docs/modules/Style-manager.md

@ -0,0 +1,31 @@
---
title: Style Manager
---
# Style Manager
Coming soon
[[toc]]
## 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`, `heigth`, `min-width`, `min-heigth`, `max-width`, `max-heigth`, `font-size`, `letter-spacing`, `line-height`, `text-shadow`, `border-radius`, `border-top-left-radius`, `border-top-right-radius`, `border-bottom-left-radius`, `border-bottom-right-radius`, `perspective`, `transform`, `transform-rotate-x`, `transform-rotate-y`, `transform-rotate-z`, `transform-scale-x`, `transform-scale-y`, `transform-scale-z`, `color`, `background-color`, `background`, `background-image`, `cursor`
Example usage:
```js
...
styleManager : {
sectors: [{
name: 'Dimension',
buildProps: ['width', 'min-height']
},{
name: 'Extra',
buildProps: ['background-color', 'box-shadow']
}]
}
...
```

149
docs/modules/Traits.md

@ -0,0 +1,149 @@
---
title: Trait Manager
---
# Trait Manager
In GrapesJS, Traits could define different parameters and behaviors of a single component. The user generally will see traits as *Settings* of each component. A common use of traits is to customize element attributes (eg. `placeholder` for inputs) and in this case the editor comes already with some built-in, easy configurable, types.
[[toc]]
## Built-in trait types
* text
* number
* checkbox
* select
* color
## Add Traits to Components
You can add traits to the component by extending them or while creating a new one. Let's see in this example how to make inputs more customizable by the editor. All components, by default, contain 2 traits: id and title (at the moment of writing). So, if you select an input and open the Settings panel you will see just this:
<img :src="$withBase('/default-traits.png')">
In this case we gonna create a new Component ([check here](Components) for more details about the creation of new components) with a new set of traits
```js
var editor = grapesjs.init({...});
var domComps = editor.DomComponents;
var dType = domComps.getType('default');
var dModel = dType.model;
var dView = dType.view;
domComps.addType('input', {
model: dModel.extend({
defaults: Object.assign({}, dModel.prototype.defaults, {
traits: [
// strings are automatically converted to text types
'name',
'placeholder',
{
type: 'select',
label: 'Type',
name: 'type',
options: [
{value: 'text', name: 'Text'},
{value: 'email', name: 'Email'},
{value: 'password', name: 'Password'},
{value: 'number', name: 'Number'},
]
}, {
type: 'checkbox',
label: 'Required',
name: 'required',
}],
}),
}, {
isComponent: function(el) {
if(el.tagName == 'INPUT'){
return {type: 'input'};
}
},
}),
view: dView,
});
```
Now the result will be
<img :src="$withBase('/input-custom-traits.png')">
By default, traits modify attributes of the model (which than reflected in canvas) but you can also have traits which change the property
```js
...
traits: [{
type: 'text',
label: 'Test',
name: 'model-prop-name',
changeProp: 1,
}],
...
```
In this way you're able to listen this changes and react with your own logic
```js
editor.DomComponents.addType('input', {
model: dModel.extend({
init() {
this.listenTo(this, 'change:model-prop-name', this.doStuff);
},
doStuff() {}
}),
...
});
```
## Define new Trait type
If built-in types are not enough (eg. something with more complex UI) you can define a new one.
Let's see this simple `textarea` element which updates contents of the component.
```js
// Each new type extends the default Trait
editor.TraitManager.addType('content', {
events:{
'keyup': 'onChange', // trigger parent onChange method on keyup
},
/**
* Returns the input element
* @return {HTMLElement}
*/
getInputEl: function() {
if (!this.inputEl) {
var input = document.createElement('textarea');
input.value = this.target.get('content');
this.inputEl = input;
}
return this.inputEl;
},
/**
* Triggered when the value of the model is changed
*/
onValueChange: function () {
this.target.set('content', this.model.get('value'));
}
});
// And then use it in your component
...
traits: [{
type: 'content',
}],
...
```

803
index.html

@ -3,39 +3,87 @@
<head>
<meta charset="utf-8">
<title>GrapesJS</title>
<link rel="stylesheet" href="styles/css/main.css">
<link rel="stylesheet" href="node_modules/codemirror/lib/codemirror.css">
<link rel="stylesheet" href="node_modules/codemirror/theme/hopscotch.css">
<link rel="stylesheet" href="dist/css/grapes.min.css">
<script src="dist/grapes.min.js"></script>
<script src="https://unpkg.com/grapesjs-blocks-basic"></script>
<script src="private/grapesjs-blocks-basic.min.js"></script>
<script src="https://unpkg.com/grapesjs-plugin-forms"></script>
<script src="https://unpkg.com/grapesjs-component-countdown"></script>
<script src="private/grapesjs-lory-slider.min.js"></script>
</head>
<style>
body, html{ height: 100%; margin: 0;}
body,
html {
height: 100%;
margin: 0;
}
.gjs-row {
display: flex;
justify-content: flex-start;
align-items: stretch;
flex-wrap: nowrap;
padding: 10px;
}
.gjs-column {
min-height: 75px;
flex-grow: 1;
flex-basis: 100%;
}
/*
.gjs-pn-panel {
position: relative;
}*/
</style>
<body>
<div class="gjs-row" style="display: none;">
<div id="layers" class="gjs-column" style="flex-basis: 800px;">
Layers header
<div id="layers-container"></div>
Layers footer
</div>
<div class="gjs-column">
<div id="panel-tools">
</div>
<div id="gjs2" style="overflow:hidden; margin: 0 auto;">
<div style="color:red; padding: 50px 100px" data-gjs-name="Text1">
Text
</div>
<div style="color:red; padding: 50px 100px" data-gjs-name="Text2">
Text2
</div>
</div>
<div id="blocks"></div>
</div>
<div id="style-manager" class="gjs-column" style="flex-basis: 800px;">
Here Style Manager
<br>
<div id="selectors-container"></div>
<div id="traits-container"></div>
<div id="style-manager-container"></div>
</div>
</div>
<div id="gjs" style="height:0px; overflow:hidden">
<div id="gjs" style="height:0px; overflow:hidden; displayZ: none">
<div style="color:red; padding: 50px 100px" data-gjs-stylable='["width"]' data-gjs-traits='[{"type":"colours","name":"llll"}]'>
Text
</div>
<div>
<div class="glyphicon glyphicon-asterisk"></div> Try to edit me
</div>
<div data-gjs-type="void-component">
<div>Text 1</div>
<div>Text 2</div>
</div>
<header class="header-banner">
<div class="container-width">
<!--
<table>
<thead>
<tr> <th>Header1</th> <th>Header2</th> <th>Header3</th> </tr>
</thead>
<tbody>
<tr> <td>Row11</td> <td>Row12</td> <td>Row13</td> </tr>
<tr> <td>Row21</td> <td>Row22</td> <td>Row23</td> </tr>
<tr> <td>Row31</td> <td>Row32</td> <td>Row33</td> </tr>
</tbody>
</table>
<br/>
<br/>
<table>
<tr> <td>Row11</td> <td>Row12</td> <td>Row13</td> </tr>
<tr> <td>Row21</td> <td>Row22</td> <td>Row23</td> </tr>
<tr> <td>Row31</td> <td>Row32</td> <td><div>Div Content Row33</div></td> </tr>
</table>
<iframe class="iframe" src="http://player.vimeo.com/video/2?&controls=0&loop=1&color=ff0000"></iframe>
<iframe class="iframe" src="https://maps.google.com/maps?&q=London, UK&z=11&t=q&output=embed"></iframe>
-->
<div class="logo-container">
<div class="logo">GrapesJS</div>
</div>
@ -45,7 +93,7 @@
<div class="menu-item">WEB</div>
</nav>
<div class="clearfix"></div>
<div class="lead-title">Build your templates without coding</div>
<div class="lead-title" data-gjs-selectable="false">Build your templates without coding</div>
<div class="sub-lead-title">All text blocks could be edited easily with double clicking on it. You can create new text blocks with the command from the left panel</div>
<div class="lead-btn">Hover me</div>
</div>
@ -270,7 +318,7 @@
font-family: Helvetica, serif;
font-weight: 100;
background-image:url("http://grapesjs.com/img/bg-gr-v.png"), url("http://grapesjs.com/img/work-desk.jpg");
background-attachment:scroll, scroll;
background-attachment: fixed, scroll;
background-position:left top, center center;
background-repeat:repeat-y, no-repeat;
background-size: contain, cover;
@ -795,6 +843,26 @@
width: 500px;
}
.row {
display: table;
padding: 10px;
width: 100%;
}
.cell {
width: 8%;
display: table-cell;
height: 75px;
}
.cell30 {
width: 30%;
}
.cell70 {
width: 70%;
}
@media (max-width: 768px){
.foot-form-cont{
width:400px;
@ -802,6 +870,11 @@
.foot-form-title{
width: auto;
}
.cell, .cell30, .cell70 {
width: 100%;
display: block;
}
}
@media (max-width: 480px){
@ -812,6 +885,678 @@
</style>
</div>
<script data-main="src/demo" src="node_modules/requirejs/require.js"></script>
<script type="text/javascript">
//grapesjs.plugins.add('testplug', (editor, config) => {});
var blkStyle = '.blk-row::after{ content: ""; clear: both; display: block;} .blk-row{padding: 10px;}';
var sectors = [{
name: 'General',
open: false,
buildProps: ['float', 'display', 'position', 'top', 'right', 'left', 'bottom']
},{
name: 'Dimension',
open: false,
buildProps: [ 'width', 'flex-width', 'height', 'max-width', 'min-height', 'margin', 'padding'],
properties: [{
id: 'flex-width',
type: 'integer',
name: 'Width',
units: ['px', '%'],
property: 'flex-basis',
toRequire: 1,
}]
},{
name: 'Typography',
open: false,
buildProps: ['font-family', 'font-size', 'font-weight', 'letter-spacing', 'color', 'line-height', 'text-align', 'text-shadow'],
properties: [{
property: 'text-align',
list : [
{value: 'left', className: 'fa fa-align-left'},
{value: 'center', className: 'fa fa-align-center' },
{value: 'right', className: 'fa fa-align-right'},
{value: 'justify', className: 'fa fa-align-justify'}
],
}]
},{
name: 'Decorations',
open: false,
buildProps: ['border-radius-c', 'background-color', 'border-radius', 'border', 'box-shadow', 'background'],
},{
name: 'Extra',
open: false,
buildProps: ['opacity', 'transition', 'perspective', 'transform'],
properties: [{
type: 'slider',
property: 'opacity',
defaults: 1,
step: 0.01,
max: 1,
min:0,
}]
},{
name: 'Flex',
open: false,
properties: [{
name : 'Flex Container',
property : 'display',
type : 'select',
defaults : 'block',
list : [{
value : 'block',
name : 'Disable',
},{
value : 'flex',
name : 'Enable',
}],
},{
name: 'Flex Parent',
property: 'label-parent-flex',
},{
name : 'Direction',
property : 'flex-direction',
type : 'radio',
defaults : 'row',
list : [{
value : 'row',
name : 'Row',
className : 'icons-flex icon-dir-row',
title : 'Row',
},{
value : 'row-reverse',
name : 'Row reverse',
className : 'icons-flex icon-dir-row-rev',
title : 'Row reverse',
},{
value : 'column',
name : 'Column',
title : 'Column',
className : 'icons-flex icon-dir-col',
},{
value : 'column-reverse',
name : 'Column reverse',
title : 'Column reverse',
className : 'icons-flex icon-dir-col-rev',
}],
},{
name : 'Wrap',
property : 'flex-wrap',
type : 'radio',
defaults : 'nowrap',
list : [{
value : 'nowrap',
title : 'Single line',
},{
value : 'wrap',
title : 'Multiple lines',
},{
value : 'wrap-reverse',
title : 'Multiple lines reverse',
}],
},{
name : 'Justify',
property : 'justify-content',
type : 'radio',
defaults : 'flex-start',
list : [{
value : 'flex-start',
className : 'icons-flex icon-just-start',
title : 'Start',
},{
value : 'flex-end',
title : 'End',
className : 'icons-flex icon-just-end',
},{
value : 'space-between',
title : 'Space between',
className : 'icons-flex icon-just-sp-bet',
},{
value : 'space-around',
title : 'Space around',
className : 'icons-flex icon-just-sp-ar',
},{
value : 'center',
title : 'Center',
className : 'icons-flex icon-just-sp-cent',
}],
},{
name : 'Align',
property : 'align-items',
type : 'radio',
defaults : 'center',
list : [{
value : 'flex-start',
title : 'Start',
className : 'icons-flex icon-al-start',
},{
value : 'flex-end',
title : 'End',
className : 'icons-flex icon-al-end',
},{
value : 'stretch',
title : 'Stretch',
className : 'icons-flex icon-al-str',
},{
value : 'center',
title : 'Center',
className : 'icons-flex icon-al-center',
}],
},{
name: 'Flex Children',
property: 'label-parent-flex',
},{
name: 'Order',
property: 'order',
type: 'integer',
defaults : 0,
min: 0
},{
name : 'Flex',
property : 'flex',
type : 'composite',
properties : [{
name: 'Grow',
property: 'flex-grow',
type: 'integer',
defaults : 0,
min: 0
},{
name: 'Shrink',
property: 'flex-shrink',
type: 'integer',
defaults : 0,
min: 0
},{
name: 'Basis',
property: 'flex-basis',
type: 'integer',
units: ['px','%',''],
unit: '',
defaults : 'auto',
}],
},{
name : 'Align',
property : 'align-self',
type : 'radio',
defaults : 'auto',
list : [{
value : 'auto',
name : 'Auto',
},{
value : 'flex-start',
title : 'Start',
className : 'icons-flex icon-al-start',
},{
value : 'flex-end',
title : 'End',
className : 'icons-flex icon-al-end',
},{
value : 'stretch',
title : 'Stretch',
className : 'icons-flex icon-al-str',
},{
value : 'center',
title : 'Center',
className : 'icons-flex icon-al-center',
}],
}]
}
];
/*
var editor2 = grapesjs.init({
container : '#gjs2',
height: '450px',
fromElement: true,
storageManager: { type: 0 },
plugins: ['testplug', 'gjs-blocks-basic', 'gjs-plugin-forms', 'grapesjs-lory-slider'],
width: '500px',
layerManager: {
appendTo: '#layers-container'
},
blockManager: {
appendTo: '#blocks'//blocks
},
styleManager: {
appendTo: '#style-manager-container',
sectors: sectors,
},
selectorManager: {
appendTo: '#selectors-container',
},
traitManager: {
appendTo: '#traits-container',
},
panels: {
defaults: [{
id: 'layers',
el: '#layers',
resizable: {
tc: 0,
cr: 1,
bc: 0,
keyWidth: 'flex-basis',
},
},{
id: 'styles',
el: '#style-manager',
resizable: {
tc: 0,
cr: 0,
cl: 1,
bc: 0,
keyWidth: 'flex-basis',
},
}]
}
});
*/
var editor = grapesjs.init(
{
container : '#gjs',
height: '100%',
fromElement: true,
plugins: ['testplug', 'gjs-blocks-basic', /*'gjs-plugin-forms'*/, 'grapesjs-lory-slider', 'gjs-component-countdown'],
autorender: 0,
allowScripts: 1,
showOffsets: 1,
noticeOnUnload: 0,
avoidInlineStyle: 1,
avoidDefaults: 1,
// forceClass: 0,
storageManager: { autoload: 0 },
// storageManager: { type: 'firebase-firestore' },
layerManager: {
showWrapper: 0,
},
assetManager: {
embedAsBase64: 1,
upload: 'https://test.page',
params: {
_token: 'pCYrSwjuiV0t5NVtZpQDY41Gn5lNUwo3it1FIkAj',
},
assets: [
{ type: 'image', src : 'http://placehold.it/350x250/78c5d6/fff/image1.jpg', height:350, width:250},
{ type: 'image', src : 'http://placehold.it/350x250/459ba8/fff/image2.jpg', height:350, width:250},
{ type: 'image', src : 'http://placehold.it/350x250/79c267/fff/image3.jpg', height:350, width:250},
{ type: 'image', src : 'http://placehold.it/350x250/c5d647/fff/image4.jpg', height:350, width:250},
{ type: 'image', src : 'http://placehold.it/350x250/f28c33/fff/image5.jpg', height:350, width:250},
{ type: 'image', src : 'http://placehold.it/350x250/e868a2/fff/image6.jpg', height:350, width:250},
{ type: 'image', src : 'http://placehold.it/350x250/cc4360/fff/image7.jpg', height:350, width:250},
{ type: 'image', src : './img/work desk.jpg', date: '2015-02-01',height:1080, width:1728},
{ type: 'image', src : './img/phone-app.png', date: '2015-02-01',height:650, width:320},
{ type: 'image', src : './img/bg-gr-v.png', date: '2015-02-01',height:1, width:1728},
]
},
blockManager: {
blocks: [/*{
id: 'b1',
label: '1 Block',
category: 'Basic',
attributes: {class:'gjs-fonts gjs-f-b1'},
content: `<div class="row" data-gjs-droppable=".cell" data-gjs-custom-name="Row">
<div class="cell" data-gjs-draggable=".row" data-gjs-custom-name="Cell"></div>
</div>`
},{
id: 'b2',
label: '2 Blocks',
category: 'Basic',
attributes: {class:'gjs-fonts gjs-f-b2'},
content: `<div class="row" data-gjs-droppable=".cell" data-gjs-custom-name="Row">
<div class="cell" data-gjs-draggable=".row" data-gjs-custom-name="Cell"></div>
<div class="cell" data-gjs-draggable=".row" data-gjs-custom-name="Cell"></div>
</div>`
},{
id: 'b3',
label: '3 Blocks',
category: 'Basic',
attributes: {class:'gjs-fonts gjs-f-b3'},
content: `<div class="row" data-gjs-droppable=".cell" data-gjs-custom-name="Row">
<div class="cell" data-gjs-draggable=".row" data-gjs-custom-name="Cell"></div>
<div class="cell" data-gjs-draggable=".row" data-gjs-custom-name="Cell"></div>
<div class="cell" data-gjs-draggable=".row" data-gjs-custom-name="Cell"></div>
</div>`
},{
id: 'b4',
label: '3/7 Block',
category: 'Basic',
attributes: {class:'gjs-fonts gjs-f-b37'},
content: `<div class="row" data-gjs-droppable=".cell" data-gjs-custom-name="Row">
<div class="cell cell30" data-gjs-draggable=".row" data-gjs-custom-name="Cell"></div>
<div class="cell cell70" data-gjs-draggable=".row" data-gjs-custom-name="Cell"></div>
</div>`,
},{
id: 'hero',
label: 'Hero section',
category: 'Section',
content: '<header class="header-banner"> <div class="container-width">'+
'<div class="logo-container"><div class="logo">GrapesJS</div></div>'+
'<nav class="navbar">'+
'<div class="menu-item">BUILDER</div><div class="menu-item">TEMPLATE</div><div class="menu-item">WEB</div>'+
'</nav><div class="clearfix"></div>'+
'<div class="lead-title">Build your templates without coding</div>'+
'<div class="lead-btn">Try it now</div></div></header>',
attributes: {class:'gjs-fonts gjs-f-hero'}
},{
id: 'h1p',
label: 'Text section',
category: 'Typography',
content: `<div>
<h1 class="heading">Insert title here</h1>
<p class="paragraph">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua</p>
</div>`,
attributes: {class:'gjs-fonts gjs-f-h1p'}
},*/{
id: '3ba',
label: 'Badges',
category: 'Section',
content: '<div class="badges">'+
'<div class="badge">'+
'<div class="badge-header"></div>'+
'<img class="badge-avatar" src="img/team1.jpg">'+
'<div class="badge-body">'+
'<div class="badge-name">Adam Smith</div><div class="badge-role">CEO</div><div class="badge-desc">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore ipsum dolor sit</div>'+
'</div>'+
'<div class="badge-foot"><span class="badge-link">f</span><span class="badge-link">t</span><span class="badge-link">ln</span></div>'+
'</div>'+
'<div class="badge">'+
'<div class="badge-header"></div>'+
'<img class="badge-avatar" src="img/team2.jpg">'+
'<div class="badge-body">'+
'<div class="badge-name">John Black</div><div class="badge-role">Software Engineer</div><div class="badge-desc">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore ipsum dolor sit</div>'+
'</div>'+
'<div class="badge-foot"><span class="badge-link">f</span><span class="badge-link">t</span><span class="badge-link">ln</span></div>'+
'</div>'+
'<div class="badge">'+
'<div class="badge-header"></div>'+
'<img class="badge-avatar" src="img/team3.jpg">'+
'<div class="badge-body">'+
'<div class="badge-name">Jessica White</div><div class="badge-role">Web Designer</div><div class="badge-desc">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore ipsum dolor sit</div>'+
'</div>'+
'<div class="badge-foot"><span class="badge-link">f</span><span class="badge-link">t</span><span class="badge-link">ln</span>'+
'</div>'+
'</div></div>',
attributes: {class:'gjs-fonts gjs-f-3ba'}
},{
id: 'text',
label: 'Text',
attributes: {class:'gjs-fonts gjs-f-text'},
category: 'Basic',
content: {
type:'text',
content:'Insert your text here',
style: {padding: '10px' },
activeOnRender: 1
},
},{
id: 'image',
label: 'Image',
category: 'Basic',
attributes: {class:'gjs-fonts gjs-f-image'},
content: {
style: {color: 'black'},
type:'image',
activeOnRender: 1
},
},{
id: 'quo',
label: 'Quote',
category: 'Typography',
content: '<blockquote class="quote">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore ipsum dolor sit</blockquote>',
attributes: {class:'fa fa-quote-right'}
},{
id: 'link',
label: 'Link',
category: 'Basic',
attributes: {class:'fa fa-link'},
content: {
type:'link',
content:'Link',
style:{color: '#d983a6'}
},
},{
id: 'map',
label: 'Map',
category: 'Extra',
attributes: {class:'fa fa-map-o'},
content: {
type: 'map',
style: {height: '350px'}
},
},{
id: 'video',
label: 'Video',
category: 'Basic',
attributes: {class:'fa fa-youtube-play'},
content: {
type: 'video',
src: 'img/video2.webm',
style: {
height: '350px',
width: '615px',
}
},
}],
},
/*
ztyleManager : {
clearProperties: 1,
sectors: [{
name: 'General',
open: false,
buildProps: ['gigi', 'width'],
properties: [{
id: 'gigi',
name: 'My prop',
property: 'custom-prop',
}]
}]
},*/
styleManager : {
clearProperties: 1,
sectors: sectors,
},
});
window.editor = editor;
var pnm = editor.Panels;
pnm.addButton('options', [{
id: 'undo',
className: 'fa fa-undo icon-undo',
command: function(e) { return e.runCommand('core:undo') },
},{
id: 'redo',
className: 'fa fa-repeat icon-redo',
command: function(e) { return e.runCommand('core:redo') },
},{
id: 'clear-all',
className: 'fa fa-trash icon-blank',
command: function(e) { return e.runCommand('core:canvas-clear') },
}]);
var bm = editor.BlockManager;
bm.add('link-block', {
label: 'Link Block',
attributes: {class:'fa fa-link'},
category: 'Basic',
content: {
type:'link',
editable: false,
draggable: '#wrapper',
style:{
display: 'inline-block',
padding: '5px',
'min-height': '50px',
'min-width': '50px'
}
},
});
editor.on('selector:add', function(selector) {
if (['.row-flex', '.cell-flex', '.cell-gut'].indexOf(selector.getFullName()) >= 0 ) {
selector.set({private: 1});
}
});
/*
bm.add('b1-2', {
label: 'Flex Block',
category: 'Basic',
attributes: { class:'gjs-fonts gjs-f-b1' },
content: `
<div class="row-flex" data-columns="1" style="text-align: center" data-gjs-droppable="[data-column]" data-gjs-highlightable="false" data-gjs-resizable='{"tl":0,"tc":0,"tr":0,"cl":0,"cr":0,"bl":0,"br":0}' data-gjs-custom-name="Row">
<div class="cell-flex" data-column="1" style="letter-spacing: normal; flex-basis: 100%" data-gjs-draggable="[data-columns]" data-gjs-resizable='{"tl":0,"tc":0,"tr":0,"cl":0,"bl":0,"br":0,"bc": 0, "keyWidth": "flex-basis", "currentUnit": 1, "minDim": 1, "maxDim": 100, "step": 0.2}' data-gjs-unstylable='["width"]' data-gjs-stylable-require='["flex-basis"]'></div>
<div class="cell-flex" data-column="1" style="letter-spacing: normal; flex-basis: 100%" data-gjs-draggable="[data-columns]" data-gjs-resizable='{"tl":0,"tc":0,"tr":0,"cl":0,"bl":0,"br":0,"bc": 0, "keyWidth": "flex-basis", "currentUnit": 1, "minDim": 1, "maxDim": 100, "step": 0.2}' data-gjs-unstylable='["width"]' data-gjs-stylable-require='["flex-basis"]'></div>
</div>
<style>
.row-flex {
display: flex;
justify-content: flex-start;
align-items: stretch;
flex-wrap: nowrap;
padding: 10px;
}
.cell-flex {
min-height: 75px;
flex-grow: 1;
flex-basis: 100%;
}
</style>
`
});
*/
bm.add('TEST', {
label: 'TEST JS LOAD',
category: 'Basic',
attributes: { class:'gjs-fonts gjs-f-b1' },
content: {
script: function() {
console.log(this);
this.addEventListener('click', function(e) {
console.log('clicked');
})
},
style: {padding: '25px'},
components: '<div style="padding: 10px"></div>'
}
});
var domc = editor.DomComponents;
var defaultType = domc.getType('default');
var defaultModel = defaultType.model;
var defaultView = defaultType.view;
var textType = domc.getType('text');
/*
domc.addType('void-component', {
model: defaultModel.extend({
defaults: { ...defaultModel.prototype.defaults,
style: { 'pointer-events': 'none' }
},
toHTML() {
return '';
}
}, {
isComponent: function(el) {},
}),
view: defaultView,
});
*/
/*
domc.addType('raw-text', {
model: textType.model.extend({
},{
isComponent: function(el) {
if (el.hasAttribute && el.hasAttribute('data-raw-text')) {
return {
type: 'raw-text',
content: el.innerHTML,
components: []
};
}
}
}
),
view: textType.view.extend({
disableEditing() {
const model = this.model;
const rte = this.rte;
if (rte && model.get('editable')) {
rte.disable(this, this.activeRte);
model.set('content', this.getChildrenContainer().innerHTML)
.trigger('change:content', model);
}
this.rteEnabled = 0;
this.toggleEvents();
},
})
});
*/
/*
domc.addType('default', {
model: defaultModel.extend({
defaults: Object.assign({}, defaultModel.prototype.defaults, {
'custom-name': 'testiiing',
traits: [{
name: 'title',
label: 'Título',
placeholder: 'Insira um texto aqui'
}]
}),
}),
});
*/
// Store and load events
editor.on('storage:load', function(e) {
console.log('LOAD ', e);
});
editor.on('storage:store', function(e) {
console.log('STORE ', e);
});
// Canvas DND
/*
editor.on('canvas:dragenter', (dt, content) => console.log('DRAG-ENTER', content));
editor.on('canvas:dragover', dt => console.log('DRAG-OVER')); // As orignal dragover
editor.on('canvas:drop', (dt, model) => console.log('DRAG-DROP', model)); // When something is dropped on canvas
editor.on('canvas:dragend', dt => console.log('DRAG-END')); // Fired when a drag operation is being ended
editor.on('canvas:dragdata', (dt, result) => {
console.group('DRAG-DATA');
console.log('Files', dt.files);
console.log('Types', dt.types);
console.log('Content', result.content);
console.groupEnd();
});
*/
//editor.on('component:remove', m => console.log('Removed', m, m.getEl()));
//editor.on('component:add', m => console.log('Added', m, m.getEl()));
editor.on('traverse:html', function (node, resultNode) {
});
editor.on('load', () => {
});
editor.render();
</script>
</body>
</html>

19938
package-lock.json

File diff suppressed because it is too large

132
package.json

@ -1,74 +1,112 @@
{
"name": "grapesjs",
"description": "Open source Web Template Editor",
"version": "0.5.4",
"description": "Free and Open Source Web Builder Framework",
"version": "0.14.25",
"author": "Artur Arseniev",
"license": "BSD-3-Clause",
"homepage": "http://grapesjs.com",
"main": "dist/grapes.min.js",
"main": "dist/grapes.js",
"repository": {
"type": "git",
"url": "https://github.com/artf/grapesjs.git"
},
"dependencies": {
"almond": "^0.3.3",
"backbone": "^1.3.3",
"backbone-undo": "^0.2.5",
"bower": "^1.7.2",
"codemirror": "^5.21.0",
"cash-dom": "^1.3.7",
"codemirror": "^5.39.0",
"codemirror-formatting": "^1.0.0",
"jquery": "^3.1.1",
"font-awesome": "^4.7.0",
"keymaster": "^1.6.2",
"promise-polyfill": "^8.0.0",
"spectrum-colorpicker": "^1.8.0",
"underscore": "^1.8.3"
"underscore": "^1.9.1"
},
"devDependencies": {
"bower": "^1.7.2",
"chai": "^3.5.0",
"documentation": "^4.0.0-beta2",
"grunt": "^0.4.5",
"grunt-bowercopy": "^1.2.4",
"grunt-cli": "^0.1.13",
"grunt-contrib-clean": "^0.7.0",
"grunt-contrib-concat": "^0.5.1",
"grunt-contrib-connect": "^0.11.2",
"grunt-contrib-copy": "^0.8.2",
"grunt-contrib-cssmin": "^0.14.0",
"grunt-contrib-jshint": "^0.12.0",
"grunt-contrib-requirejs": "^0.4.4",
"grunt-contrib-uglify": "^0.11.0",
"grunt-contrib-watch": "^0.6.1",
"grunt-mocha": "^0.4.15",
"grunt-sass": "^1.1.0",
"istanbul": "^0.4.2",
"mocha": "^3.1.2",
"node-sass": "^3.4.2",
"requirejs": "^2.3.2",
"requirejs-text": "^2.0.12",
"sinon": "^1.17.6",
"svg2ttf": "^4.0.1",
"ttf2eot": "^2.0.0",
"ttf2woff": "^2.0.1",
"ttf2woff2": "^2.0.3"
"babel-core": "^6.26.3",
"babel-loader": "^7.1.5",
"babel-plugin-transform-object-rest-spread": "^6.26.0",
"babel-preset-env": "^1.7.0",
"documentation": "^8.0.0",
"eslint": "^4.19.1",
"html-webpack-plugin": "^3.2.0",
"husky": "^0.14.3",
"jest": "^23.3.0",
"lint-staged": "^7.2.0",
"node-sass": "^4.9.1",
"prettier": "^1.13.7",
"sinon": "^6.1.0",
"string-replace-loader": "^2.1.1",
"vuepress": "^0.10.2",
"webpack": "^4.15.1",
"webpack-cli": "^3.0.8",
"webpack-dev-server": "^3.1.4",
"whatwg-fetch": "^2.0.4"
},
"keywords": [
"wte",
"grapes",
"grapesjs",
"web template editor",
"web site builder",
"web template builder",
"html website builder",
"site builder",
"newsletter builder",
"wysiwyg",
"web",
"template",
"editor"
"editor",
"newsletter",
"site",
"builder"
],
"babel": {
"presets": [
[
"env",
{
"targets": [
"> 1%",
"ie 11",
"safari 8"
],
"useBuiltIns": true
}
]
],
"plugins": [
"transform-object-rest-spread"
]
},
"lint-staged": {
"{src,test}/**/*.js": [
"prettier --single-quote --write",
"git add"
]
},
"jest": {
"testURL": "http://localhost/",
"modulePaths": [
"./src"
],
"setupFiles": [
"<rootDir>/test/setup.js"
],
"moduleNameMapper": {
"^jquery$": "cash-dom"
}
},
"scripts": {
"build": "./node_modules/.bin/grunt build",
"build:fonts": "./node_modules/.bin/grunt build:fonts",
"test": "./node_modules/.bin/grunt test",
"start": "./node_modules/.bin/grunt dev"
"docs": "vuepress dev docs",
"docs:api": "node docs/api.js",
"docs:build-vp": "vuepress build docs",
"docs:build": "npm run docs:api && npm run docs:build-vp",
"docs:deploy": "docs/deploy.sh",
"lint": "eslint src",
"check": "npm run lint && npm run test",
"precommit": "lint-staged",
"build": "npm run check && npm run v:patch && npm run build-dev && webpack --env=prod",
"build-n": "npm run check && npm run build:css && webpack --env=prod",
"build-dev": "webpack --env=dev && npm run build:css",
"build:css": "node-sass src/styles/scss/main.scss dist/css/grapes.min.css --output-style compressed",
"v:patch": "npm version --no-git-tag-version patch",
"start": "npm run build:css -- -w & webpack-dev-server --open --progress --colors",
"format": "prettier --single-quote --write './{src,test}/**/*.js'",
"test": "jest",
"test:dev": "jest --watch"
}
}

94
src/asset_manager/config/config.js

@ -1,18 +1,84 @@
define(function () {
return {
// Default assets
assets: [],
module.exports = {
// Default assets
// eg. [
// 'https://...image1.png',
// 'https://...image2.png',
// {type: 'image', src: 'https://...image3.png', someOtherCustomProp: 1},
// ..
// ]
assets: [],
// Style prefix
stylePrefix: 'am-',
// Content to add where there is no assets to show
// eg. 'No <b>assets</b> here, drag to upload'
noAssets: '',
// Url where uploads will be send, set false to disable upload
upload: 'http://localhost/assets/upload',
// Style prefix
stylePrefix: 'am-',
// Text on upload input
uploadText: 'Drop files here or click to upload',
// Upload endpoint, set `false` to disable upload
// upload: 'https://endpoint/upload/assets',
// upload: false,
upload: 0,
// Label for the add button
addBtnText: 'Add image',
};
});
// The name used in POST to pass uploaded files
uploadName: 'files',
// Custom headers to pass with the upload request
headers: {},
// Custom parameters to pass with the upload request, eg. csrf token
params: {},
// If true, tries to add automatically uploaded assets.
// To make it work the server should respond with a JSON containing assets
// in a data key, eg:
// {
// data: [
// 'https://.../image.png',
// ...
// {src: 'https://.../image2.png'},
// ...
// ]
// }
autoAdd: 1,
// Text on upload input
uploadText: 'Drop files here or click to upload',
// Label for the add button
addBtnText: 'Add image',
// Custom uploadFile function
// @example
// uploadFile: (e) => {
// var files = e.dataTransfer ? e.dataTransfer.files : e.target.files;
// // ...send somewhere
// }
uploadFile: '',
// Handle the image url submit from the built-in 'Add image' form
// @example
// handleAdd: (textFromInput) => {
// // some check...
// editor.AssetManager.add(textFromInput);
// }
handleAdd: '',
// Enable an upload dropzone on the entire editor (not document) when dragging
// files over it
// If active the dropzone disable/hide the upload dropzone in asset modal,
// otherwise you will get double drops (#507)
dropzone: 0,
// 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',
//Default placeholder for input
inputPlaceholder: 'http://path/to/the/image.jpg'
};

351
src/asset_manager/index.js

@ -0,0 +1,351 @@
/**
* You can customize the initial state of the module from the editor initialization, by passing the following [Configuration Object](https://github.com/artf/grapesjs/blob/master/src/asset_manager/config/config.js)
* ```js
* 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
*
* ```js
* const assetManager = editor.AssetManager;
* ```
*
* * [add](#add)
* * [get](#get)
* * [getAll](#getall)
* * [getAllVisible](#getallvisible)
* * [remove](#remove)
* * [store](#store)
* * [load](#load)
* * [getContainer](#getcontainer)
* * [getAssetsEl](#getassetsel)
* * [addType](#addtype)
* * [getType](#gettype)
* * [getTypes](#gettypes)
*
* @module AssetManager
*/
module.exports = () => {
let c = {};
const defaults = require('./config/config');
const Assets = require('./model/Assets');
const AssetsView = require('./view/AssetsView');
const FileUpload = require('./view/FileUploader');
let assets, am, fu;
return {
/**
* Name of the module
* @type {String}
* @private
*/
name: 'AssetManager',
/**
* Mandatory for the storage manager
* @type {String}
* @private
*/
storageKey: 'assets',
getConfig() {
return c;
},
/**
* Initialize module
* @param {Object} config Configurations
* @private
*/
init(config) {
c = config || {};
for (let name in defaults) {
if (!(name in c)) c[name] = defaults[name];
}
const ppfx = c.pStylePrefix;
const em = c.em;
if (ppfx) {
c.stylePrefix = ppfx + c.stylePrefix;
}
// Global assets collection
assets = new Assets([]);
const obj = {
// Collection visible in asset manager
collection: new Assets([]),
globalCollection: assets,
config: c
};
fu = new FileUpload(obj);
obj.fu = fu;
am = new AssetsView(obj);
// Setup the sync between the global and public collections
assets.listenTo(assets, 'add', model => {
this.getAllVisible().add(model);
em && em.trigger('asset:add', model);
});
assets.listenTo(assets, 'remove', model => {
this.getAllVisible().remove(model);
em && em.trigger('asset:remove', model);
});
return this;
},
/**
* Add new asset/s to the collection. URLs are supposed to be unique
* @param {string|Object|Array<string>|Array<Object>} asset URL strings or an objects representing the resource.
* @param {Object} [opts] Options
* @return {Model}
* @example
* // 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',
* }]);
*/
add(asset, opts = {}) {
// Put the model at the beginning
if (typeof opts.at == 'undefined') {
opts.at = 0;
}
return assets.add(asset, opts);
},
/**
* Returns the asset by URL
* @param {string} src URL of the asset
* @return {Object} Object representing the asset
* @example
* var asset = assetManager.get('http://img.jpg');
*/
get(src) {
return assets.where({ src })[0];
},
/**
* Return the global collection, containing all the assets
* @return {Collection}
*/
getAll() {
return assets;
},
/**
* Return the visible collection, which containes assets actually rendered
* @return {Collection}
*/
getAllVisible() {
return am.collection;
},
/**
* Remove the asset by its URL
* @param {string} src URL of the asset
* @return {this}
* @example
* assetManager.remove('http://img.jpg');
*/
remove(src) {
var asset = this.get(src);
this.getAll().remove(asset);
return this;
},
/**
* Store assets data to the selected storage
* @param {Boolean} noStore If true, won't store
* @return {Object} Data to store
* @example
* var assets = assetManager.store();
*/
store(noStore) {
var obj = {};
var assets = JSON.stringify(this.getAll().toJSON());
obj[this.storageKey] = assets;
if (!noStore && c.stm) c.stm.store(obj);
return obj;
},
/**
* Load data from the passed object.
* The fetched data will be added to the collection.
* @param {Object} data Object of data to load
* @return {Object} Loaded assets
* @example
* var assets = assetManager.load({
* assets: [...]
* })
*
*/
load(data = {}) {
const name = this.storageKey;
let assets = data[name] || [];
if (typeof assets == 'string') {
try {
assets = JSON.parse(data[name]);
} catch (err) {}
}
if (assets && assets.length) {
this.getAll().reset(assets);
}
return assets;
},
/**
* Return the Asset Manager Container
* @return {HTMLElement}
*/
getContainer() {
return am.el;
},
/**
* Get assets element container
* @return {HTMLElement}
*/
getAssetsEl() {
return am.el.querySelector('[data-el=assets]');
},
/**
* Render assets
* @param {array} assets Assets to render, without the argument will render
* all global assets
* @return {HTMLElement}
* @example
* // Render all assets
* assetManager.render();
*
* // Render some of the assets
* const assets = assetManager.getAll();
* assetManager.render(assets.filter(
* asset => asset.get('category') == 'cats'
* ));
*/
render(assets) {
const toRender = assets || this.getAll().models;
if (!am.rendered) {
am.render();
}
am.collection.reset(toRender);
return this.getContainer();
},
/**
* Add new type. If you want to get more about type definition we suggest to read the [module's page](/modules/Assets.html)
* @param {string} id Type ID
* @param {Object} definition Definition of the type. Each definition contains
* `model` (business logic), `view` (presentation logic)
* and `isType` function which recognize the type of the
* passed entity
* @example
* assetManager.addType('my-type', {
* model: {},
* view: {},
* isType: (value) => {},
* })
*/
addType(id, definition) {
this.getAll().addType(id, definition);
},
/**
* Get type
* @param {string} id Type ID
* @return {Object} Type definition
*/
getType(id) {
return this.getAll().getType(id);
},
/**
* Get types
* @return {Array}
*/
getTypes() {
return this.getAll().getTypes();
},
//-------
AssetsView() {
return am;
},
FileUploader() {
return fu;
},
onLoad() {
this.getAll().reset(c.assets);
},
postRender(editorView) {
c.dropzone && fu.initDropzone(editorView);
},
/**
* Set new target
* @param {Object} m Model
* @private
* */
setTarget(m) {
am.collection.target = m;
},
/**
* Set callback after asset was selected
* @param {Object} f Callback function
* @private
* */
onSelect(f) {
am.collection.onSelect = f;
},
/**
* Set callback to fire when the asset is clicked
* @param {function} func
* @private
*/
onClick(func) {
c.onClick = func;
},
/**
* Set callback to fire when the asset is double clicked
* @param {function} func
* @private
*/
onDblClick(func) {
c.onDblClick = func;
}
};
};

241
src/asset_manager/main.js

@ -1,241 +0,0 @@
/**
* * [add](#add)
* * [get](#get)
* * [getAll](#getall)
* * [remove](#remove)
* * [store](#store)
* * [load](#load)
* * [onClick](#onClick)
* * [onDblClick](#onDblClick)
*
* Before using this methods you should get first the module from the editor instance, in this way:
*
* ```js
* var assetManager = editor.AssetManager;
* ```
*
* @module AssetManager
* @param {Object} config Configurations
* @param {Array<Object>} [config.assets=[]] Default assets
* @param {String} [config.uploadText='Drop files here or click to upload'] Upload text
* @param {String} [config.addBtnText='Add image'] Text for the add button
* @param {String} [config.upload=''] Where to send upload data. Expects as return a JSON with asset/s object
* as: {data: [{src:'...'}, {src:'...'}]}
* @return {this}
* @example
* ...
* {
* assets: [
* {src:'path/to/image.png'},
* ...
* ],
* upload: 'http://dropbox/path', // Set to false to disable it
* uploadText: 'Drop files here or click to upload',
* }
*/
define(function(require) {
return function() {
var c = {},
Assets = require('./model/Assets'),
AssetsView = require('./view/AssetsView'),
FileUpload = require('./view/FileUploader'),
assets, am, fu;
return {
/**
* Name of the module
* @type {String}
* @private
*/
name: 'AssetManager',
/**
* Mandatory for the storage manager
* @type {String}
* @private
*/
storageKey: 'assets',
/**
* Initialize module
* @param {Object} config Configurations
* @private
*/
init: function(config){
c = config || {};
var defaults = require('./config/config');
for (var name in defaults) {
if (!(name in c))
c[name] = defaults[name];
}
var ppfx = c.pStylePrefix;
if(ppfx)
c.stylePrefix = ppfx + c.stylePrefix;
assets = new Assets(c.assets);
var obj = {
collection: assets,
config: c,
};
am = new AssetsView(obj);
fu = new FileUpload(obj);
return this;
},
/**
* Add new asset/s to the collection. URLs are supposed to be unique
* @param {string|Object|Array<string>|Array<Object>} asset URL strings or an objects representing the resource.
* @return {Model}
* @example
* // 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',
* }]);
*/
add: function(asset){
return assets.add(asset);
},
/**
* Returns the asset by URL
* @param {string} src URL of the asset
* @return {Object} Object representing the asset
* @example
* var asset = assetManager.get('http://img.jpg');
*/
get: function(src){
return assets.where({src: src})[0];
},
/**
* Return all assets
* @return {Collection}
*/
getAll: function(){
return assets;
},
/**
* Remove the asset by its URL
* @param {string} src URL of the asset
* @return {this}
* @example
* assetManager.remove('http://img.jpg');
*/
remove: function(src){
var asset = this.get(src);
this.getAll().remove(asset);
return this;
},
/**
* Store assets data to the selected storage
* @param {Boolean} noStore If true, won't store
* @return {Object} Data to store
* @example
* var assets = assetManager.store();
*/
store: function(noStore){
var obj = {};
var assets = JSON.stringify(this.getAll().toJSON());
obj[this.storageKey] = assets;
if(!noStore && c.stm)
c.stm.store(obj);
return obj;
},
/**
* 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
* @param {Object} data Object of data to load
* @return {Object} Loaded assets
* @example
* var assets = assetManager.load();
* // The format below will be used by the editor model
* // to load automatically all the stuff
* var assets = assetManager.load({
* assets: [...]
* });
*
*/
load: function(data){
var d = data || '';
var name = this.storageKey;
if(!d && c.stm)
d = c.stm.load(name);
var assets = [];
try{
assets = JSON.parse(d[name]);
}catch(err){}
this.getAll().add(assets);
return assets;
},
/**
* Render assets
* @param {Boolean} f Force to render, otherwise cached version will be returned
* @return {HTMLElement}
* @private
*/
render: function(f){
if(!this.rendered || f)
this.rendered = am.render().$el.add(fu.render().$el);
return this.rendered;
},
//-------
/**
* Set new target
* @param {Object} m Model
* @private
* */
setTarget: function(m){
am.collection.target = m;
},
/**
* Set callback after asset was selected
* @param {Object} f Callback function
* @private
* */
onSelect: function(f){
am.collection.onSelect = f;
},
/**
* Set callback to fire when the asset is clicked
* @param {function} func
*/
onClick: function(func) {
c.onClick = func;
},
/**
* Set callback to fire when the asset is double clicked
* @param {function} func
*/
onDblClick: function(func) {
c.onDblClick = func;
},
};
};
});

53
src/asset_manager/model/Asset.js

@ -1,31 +1,30 @@
define(['backbone'],
function (Backbone) {
return Backbone.Model.extend({
module.exports = require('backbone').Model.extend({
idAttribute: 'src',
idAttribute: 'src',
defaults: {
type: '',
src: ''
},
defaults: {
type: '',
src: '',
},
/**
* Get filename of the asset
* @return {string}
* @private
* */
getFilename() {
return this.get('src')
.split('/')
.pop();
},
/**
* Get filename of the asset
* @return {string}
* @private
* */
getFilename: function(){
return this.get('src').split('/').pop();
},
/**
* Get extension of the asset
* @return {string}
* @private
* */
getExtension: function(){
return this.getFilename().split('.').pop();
},
});
/**
* Get extension of the asset
* @return {string}
* @private
* */
getExtension() {
return this.getFilename()
.split('.')
.pop();
}
});

20
src/asset_manager/model/AssetImage.js

@ -1,13 +1,11 @@
define(['backbone', './Asset'],
function (Backbone, Asset) {
return Asset.extend({
const Asset = require('./Asset');
defaults: _.extend({}, Asset.prototype.defaults, {
type: 'image',
unitDim: 'px',
height: 0,
width: 0,
}),
});
module.exports = Asset.extend({
defaults: {
...Asset.prototype.defaults,
type: 'image',
unitDim: 'px',
height: 0,
width: 0
},
});

85
src/asset_manager/model/Assets.js

@ -1,67 +1,22 @@
define(['backbone', './Asset', './AssetImage'],
function (Backbone, Asset, AssetImage) {
return Backbone.Collection.extend({
model: AssetImage,
initialize: function(models, opt){
this.model = function(attrs, options) {
var model;
switch(attrs.type){
default:
model = new AssetImage(attrs, options);
import TypeableCollection from 'domain_abstract/model/TypeableCollection';
module.exports = require('backbone')
.Collection.extend(TypeableCollection)
.extend({
types: [
{
id: 'image',
model: require('./AssetImage'),
view: require('./../view/AssetImageView'),
isType(value) {
if (typeof value == 'string') {
return {
type: 'image',
src: value
};
}
return model;
};
},
/**
* Add new image asset to the collection
* @param {string} url URL of the image
* @param {Object} opts Options
* @return {this}
* @private
*/
addImg: function(url, opts){
this.add({
type: 'image',
src: url,
}, opts);
return this;
},
/**
* Prevent inserting assets with the same 'src'
* Seems like idAttribute is not working with dynamic model assignament
* @private
*/
add: function(models, opt) {
var mods = [];
models = models instanceof Array ? models : [models];
for (var i = 0, len = models.length; i < len; i++) {
var model = models[i];
if(typeof model === 'string')
model = {src: model, type: 'image'};
if(!model || !model.src)
continue;
var found = this.where({src: model.src});
if(!found.length)
mods.push(model);
return value;
}
if(mods.length == 1)
mods = mods[0];
return Backbone.Collection.prototype.add.apply(this, [mods, opt]);
},
});
});
}
]
});

10
src/asset_manager/template/assetImage.html

@ -1,10 +0,0 @@
<div id="<%= pfx %>preview-cont">
<div id="<%= pfx %>preview" style="background-image: url(<%= src %>);"></div>
<div id="<%= pfx %>preview-bg" class="<%= ppfx %>checker-bg"></div>
</div>
<div id="<%= pfx %>meta">
<div id="<%= pfx %>name"><%= name %></div>
<div id="<%= pfx %>dimensions"><%= dim %></div>
</div>
<div id="<%= pfx %>close">&Cross;</div>
<div style="clear:both"></div>

17
src/asset_manager/template/assets.html

@ -1,17 +0,0 @@
<div class="<%= pfx %>assets-cont">
<div class="<%= pfx %>assets-header">
<form class="<%= pfx %>add-asset">
<div class="<%= ppfx %>field <%= pfx %>add-field">
<input placeholder="http://path/to/the/image.jpg"/>
</div>
<button class="<%= ppfx %>btn-prim"><%= btnText %></button>
<div style="clear:both"></div>
</form>
<div class="<%= pfx %>dips" style="display:none">
<button class="fa fa-th <%= ppfx %>btnt"></button>
<button class="fa fa-th-list <%= ppfx %>btnt"></button>
</div>
</div>
<div class="<%= pfx %>assets"></div>
<div style="clear:both"></div>
</div>

5
src/asset_manager/template/fileUploader.html

@ -1,5 +0,0 @@
<form>
<div id="<%= pfx %>title"><%= title %></div>
<input type="file" id="<%= uploadId %>" name="file" accept="image/*" <%= disabled ? 'disabled' : '' %> multiple/>
<div style="clear:both;"></div>
</form>

158
src/asset_manager/view/AssetImageView.js

@ -1,97 +1,83 @@
define(['./AssetView','text!./../template/assetImage.html'],
function (AssetView, assetTemplate) {
return AssetView.extend({
import { isFunction } from 'underscore';
events:{
'click': 'handleClick',
'dblclick': 'handleDblClick',
},
module.exports = require('./AssetView').extend({
events: {
'click [data-toggle=asset-remove]': 'onRemove',
click: 'onClick',
dblclick: 'onDblClick'
},
template: _.template(assetTemplate),
getPreview() {
const pfx = this.pfx;
const src = this.model.get('src');
return `
<div class="${pfx}preview" style="background-image: url('${src}');"></div>
<div class="${pfx}preview-bg ${this.ppfx}checker-bg"></div>
`;
},
initialize: function(o) {
AssetView.prototype.initialize.apply(this, arguments);
this.className += ' ' + this.pfx + 'asset-image';
this.events['click #' + this.pfx + 'close'] = 'removeItem';
this.delegateEvents();
},
getInfo() {
const pfx = this.pfx;
const model = this.model;
let name = model.get('name');
let width = model.get('width');
let height = model.get('height');
let unit = model.get('unitDim');
let dim = width && height ? `${width}x${height}${unit}` : '';
name = name || model.getFilename();
return `
<div class="${pfx}name">${name}</div>
<div class="${pfx}dimensions">${dim}</div>
`;
},
/**
* Trigger when the asset is clicked
* @private
* */
handleClick: function() {
var onClick = this.config.onClick;
var model = this.model;
model.collection.trigger('deselectAll');
this.$el.addClass(this.pfx + 'highlight');
init(o) {
const pfx = this.pfx;
this.className += ` ${pfx}asset-image`;
},
if (typeof onClick === 'function') {
onClick(model);
} else {
this.updateTarget(model.get('src'));
}
},
/**
* Triggered when the asset is clicked
* @private
* */
onClick() {
var onClick = this.config.onClick;
var model = this.model;
this.collection.trigger('deselectAll');
this.$el.addClass(this.pfx + 'highlight');
/**
* Trigger when the asset is double clicked
* @private
* */
handleDblClick: function() {
var onDblClick = this.config.onDblClick;
var model = this.model;
if (isFunction(onClick)) {
onClick(model);
} else {
this.updateTarget(this.collection.target);
}
},
if (typeof onDblClick === 'function') {
onDblClick(model);
} else {
this.updateTarget(model.get('src'));
}
/**
* Triggered when the asset is double clicked
* @private
* */
onDblClick() {
const { em, model } = this;
const onDblClick = this.config.onDblClick;
var onSelect = model.collection.onSelect;
if(typeof onSelect == 'function'){
onSelect(this.model);
}
},
if (isFunction(onDblClick)) {
onDblClick(model);
} else {
this.updateTarget(this.collection.target);
em && em.get('Modal').close();
}
/**
* Update target if exists
* @param {String} v Value
* @private
* */
updateTarget: function(v){
var target = this.model.collection.target;
if(target && target.set) {
var attr = _.clone( target.get('attributes') );
target.set('attributes', attr );
target.set('src', v );
}
},
var onSelect = this.collection.onSelect;
isFunction(onSelect) && onSelect(model);
},
/**
* Remove asset from collection
* @private
* */
removeItem: function(e){
e.stopPropagation();
this.model.collection.remove(this.model);
},
render : function(){
var name = this.model.get('name'),
dim = this.model.get('width') && this.model.get('height') ?
this.model.get('width')+' x '+this.model.get('height') : '';
name = name ? name : this.model.get('src').split("/").pop();
name = name && name.length > 30 ? name.substring(0, 30)+'...' : name;
dim = dim ? dim + (this.model.get('unitDim') ? this.model.get('unitDim') : ' px' ) : '';
this.$el.html( this.template({
name: name,
src: this.model.get('src'),
dim: dim,
pfx: this.pfx,
ppfx: this.ppfx
}));
this.$el.attr('class', this.className);
return this;
},
});
/**
* Remove asset from collection
* @private
* */
onRemove(e) {
e.stopImmediatePropagation();
this.model.collection.remove(this.model);
}
});

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

Loading…
Cancel
Save