├── .travis.yml ├── docs ├── faq.md ├── .vuepress │ ├── theme │ │ ├── index.js │ │ └── layouts │ │ │ ├── Layout.vue │ │ │ └── CarbonAds.vue │ ├── public │ │ ├── logo.png │ │ ├── blocks3.jpg │ │ ├── new-btn.png │ │ ├── btn-clicked.png │ │ ├── default-gjs.jpg │ │ ├── default-sm.jpg │ │ ├── demo-view.png │ │ ├── empty-gjs.png │ │ ├── enabled-sm.jpg │ │ ├── logo-icon.png │ │ ├── new-panel.png │ │ ├── style-comp.jpg │ │ ├── canvas-panels.jpg │ │ ├── cssom-result.jpg │ │ ├── assets-svg-view.png │ │ ├── assets-uploader.png │ │ ├── cssom-devtools.png │ │ ├── default-traits.png │ │ ├── margin-strings.jpg │ │ ├── sm-empty-state.jpg │ │ ├── assets-empty-view.png │ │ ├── default-link-comp.jpg │ │ ├── assets-builtin-modal.png │ │ ├── assets-full-dropzone.gif │ │ ├── block-custom-render.jpg │ │ ├── block-custom-render2.jpg │ │ ├── docs-init-link-trait.jpg │ │ ├── docs-link-trait-raw.jpg │ │ └── input-custom-traits.png │ ├── components │ │ ├── demos │ │ │ ├── DemoCanvasOnly.html │ │ │ ├── DemoCanvasOnly.css │ │ │ ├── DemoLayers.css │ │ │ └── DemoCanvasOnly.js │ │ ├── Demo.vue │ │ ├── DemoCanvasOnly.vue │ │ ├── DemoBasicBlocks.vue │ │ ├── DemoViewer.vue │ │ ├── DemoLayers.vue │ │ ├── DemoCustomPanels.vue │ │ ├── DemoStyle.vue │ │ ├── DemoTraits.vue │ │ └── DemoDevices.vue │ ├── styles │ │ ├── palette.styl │ │ └── index.styl │ └── enhanceApp.js ├── api │ ├── README.md │ └── device_manager.md ├── deploy.sh ├── modules │ └── Style-manager.md └── api.js ├── src ├── device_manager │ ├── config │ │ └── config.js │ └── model │ │ ├── Devices.js │ │ └── Device.js ├── plugin_manager │ ├── config │ │ └── config.js │ └── index.js ├── keymaps │ └── messages.js ├── styles │ ├── fonts │ │ ├── main-fonts.eot │ │ ├── main-fonts.ttf │ │ └── main-fonts.woff │ └── scss │ │ ├── _gjs_status.scss │ │ ├── _gjs_devices.scss │ │ ├── _gjs_traits.scss │ │ ├── _gjs_rte.scss │ │ ├── _gjs_modal.scss │ │ ├── _gjs_panels.scss │ │ └── _gjs_blocks.scss ├── code_manager │ ├── config │ │ └── config.js │ ├── model │ │ ├── HtmlGenerator.js │ │ ├── JsonGenerator.js │ │ └── JsGenerator.js │ └── view │ │ └── EditorView.js ├── dom_components │ ├── view │ │ ├── ComponentTableBodyView.js │ │ ├── ComponentTableCellView.js │ │ ├── ComponentTableFootView.js │ │ ├── ComponentTableHeadView.js │ │ ├── ComponentTableRowView.js │ │ ├── ComponentTableView.js │ │ ├── ComponentLabelView.js │ │ ├── ComponentCommentView.js │ │ ├── ComponentSvgView.js │ │ ├── ComponentTextNodeView.js │ │ ├── ToolbarView.js │ │ ├── ComponentLinkView.js │ │ ├── ComponentMapView.js │ │ ├── ComponentScriptView.js │ │ └── ToolbarButtonView.js │ ├── model │ │ ├── Toolbar.js │ │ ├── ToolbarButton.js │ │ ├── ComponentWrapper.js │ │ ├── ComponentTableRow.js │ │ ├── ComponentText.js │ │ ├── ComponentLabel.js │ │ ├── ComponentTableFoot.js │ │ ├── ComponentTableHead.js │ │ ├── ComponentComment.js │ │ ├── ComponentSvgIn.js │ │ ├── ComponentTableCell.js │ │ ├── ComponentScript.js │ │ ├── ComponentSvg.js │ │ ├── ComponentTable.js │ │ ├── ComponentTextNode.js │ │ ├── ComponentLink.js │ │ └── ComponentTableBody.js │ └── config │ │ └── config.js ├── commands │ ├── view │ │ ├── CanvasClear.js │ │ ├── CopyComponent.js │ │ ├── ComponentEnter.js │ │ ├── ComponentNext.js │ │ ├── ComponentPrev.js │ │ ├── SwitchVisibility.js │ │ ├── ComponentExit.js │ │ ├── OpenBlocks.js │ │ ├── ComponentDelete.js │ │ ├── OpenLayers.js │ │ ├── ComponentStyleClear.js │ │ ├── PasteComponent.js │ │ ├── Resize.js │ │ ├── OpenAssets.js │ │ ├── ExportTemplate.js │ │ ├── DeleteComponent.js │ │ ├── OpenTraitManager.js │ │ └── CanvasMove.js │ ├── model │ │ ├── Command.js │ │ └── Commands.js │ └── config │ │ └── config.js ├── modal_dialog │ ├── config │ │ └── config.js │ └── model │ │ └── Modal.js ├── panels │ ├── model │ │ ├── Panels.js │ │ ├── Panel.js │ │ ├── Button.js │ │ └── Buttons.js │ └── view │ │ ├── ButtonsView.js │ │ └── PanelsView.js ├── block_manager │ ├── model │ │ ├── Blocks.js │ │ ├── Categories.js │ │ ├── Category.js │ │ └── Block.js │ └── config │ │ └── config.js ├── style_manager │ ├── model │ │ ├── Sectors.js │ │ ├── PropertySelect.js │ │ ├── PropertySlider.js │ │ ├── PropertyRadio.js │ │ ├── PropertyInteger.js │ │ └── Layer.js │ ├── view │ │ ├── PropertyColorView.js │ │ ├── PropertyIntegerView.js │ │ ├── PropertySelectView.js │ │ ├── PropertySliderView.js │ │ └── PropertiesView.js │ └── config │ │ └── config.js ├── parser │ ├── config │ │ └── config.js │ ├── model │ │ └── ParserCss.js │ └── index.js ├── css_composer │ ├── config │ │ └── config.js │ ├── view │ │ ├── CssGroupRuleView.js │ │ └── CssRuleView.js │ └── model │ │ └── CssRules.js ├── asset_manager │ ├── model │ │ ├── AssetImage.js │ │ ├── Asset.js │ │ └── Assets.js │ └── view │ │ ├── AssetView.js │ │ └── AssetImageView.js ├── rich_text_editor │ └── config │ │ └── config.js ├── trait_manager │ ├── config │ │ └── config.js │ ├── view │ │ ├── TraitColorView.js │ │ ├── TraitNumberView.js │ │ ├── TraitButtonView.js │ │ ├── TraitsView.js │ │ ├── TraitCheckboxView.js │ │ └── TraitSelectView.js │ ├── model │ │ ├── TraitFactory.js │ │ └── Traits.js │ └── index.js ├── i18n │ └── config.js ├── utils │ ├── index.js │ ├── polyfills.js │ └── fetch.js ├── canvas │ ├── view │ │ └── FramesView.js │ ├── model │ │ ├── Frames.js │ │ └── Canvas.js │ └── config │ │ └── config.js ├── selector_manager │ └── model │ │ ├── Selectors.js │ │ └── Selector.js ├── editor │ └── view │ │ └── EditorView.js ├── navigator │ └── config │ │ └── config.js ├── storage_manager │ ├── model │ │ └── LocalStorage.js │ └── config │ │ └── config.js └── domain_abstract │ └── ui │ └── Input.js ├── .github ├── FUNDING.yml ├── no-response.yml ├── lock.yml └── ISSUE_TEMPLATE.md ├── dist └── fonts │ ├── main-fonts.eot │ ├── main-fonts.ttf │ └── main-fonts.woff ├── .editorconfig ├── test ├── test_utils.js ├── specs │ ├── asset_manager │ │ ├── model │ │ │ ├── Assets.js │ │ │ ├── AssetImage.js │ │ │ └── Asset.js │ │ └── view │ │ │ ├── AssetView.js │ │ │ ├── AssetImageView.js │ │ │ └── FileUploader.js │ ├── trait_manager │ │ ├── model │ │ │ └── TraitsModel.js │ │ └── view │ │ │ └── TraitsView.js │ ├── commands │ │ ├── model │ │ │ └── CommandModels.js │ │ └── view │ │ │ └── SwitchVisibility.js │ ├── style_manager │ │ └── view │ │ │ ├── SectorsView.js │ │ │ └── LayerView.js │ ├── plugin_manager │ │ └── index.js │ ├── code_manager │ │ └── index.js │ ├── panels │ │ ├── view │ │ │ ├── ButtonsView.js │ │ │ ├── PanelsView.js │ │ │ └── PanelView.js │ │ └── e2e │ │ │ └── PanelsE2e.js │ ├── dom_components │ │ ├── view │ │ │ ├── ComponentImageView.js │ │ │ ├── ComponentTextView.js │ │ │ └── ComponentsView.js │ │ └── model │ │ │ └── ComponentImage.js │ ├── modal │ │ ├── view │ │ │ └── ModalView.js │ │ └── index.js │ ├── selector_manager │ │ └── model │ │ │ └── SelectorModels.js │ ├── device_manager │ │ ├── index.js │ │ └── view │ │ │ └── DevicesView.js │ └── block_manager │ │ ├── index.js │ │ └── view │ │ └── BlocksView.js └── setup.js ├── .gitignore ├── .eslintrc ├── .npmignore ├── scripts └── build-locale.js ├── LICENSE └── webpack.config.js /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "8" 4 | - "10" 5 | -------------------------------------------------------------------------------- /docs/faq.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Faq 3 | --- 4 | 5 | # FAQ 6 | 7 | Coming soon 8 | -------------------------------------------------------------------------------- /src/device_manager/config/config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | devices: [] 3 | }; 4 | -------------------------------------------------------------------------------- /src/plugin_manager/config/config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: [] 3 | }; 4 | -------------------------------------------------------------------------------- /docs/.vuepress/theme/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extend: '@vuepress/theme-default' 3 | } -------------------------------------------------------------------------------- /src/keymaps/messages.js: -------------------------------------------------------------------------------- 1 | export default { 2 | en: { 3 | hello: 'Hello' 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # Shows a funding button via Open Collective 2 | 3 | open_collective: grapesjs 4 | -------------------------------------------------------------------------------- /dist/fonts/main-fonts.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear/grapesjs/dev/dist/fonts/main-fonts.eot -------------------------------------------------------------------------------- /dist/fonts/main-fonts.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear/grapesjs/dev/dist/fonts/main-fonts.ttf -------------------------------------------------------------------------------- /dist/fonts/main-fonts.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear/grapesjs/dev/dist/fonts/main-fonts.woff -------------------------------------------------------------------------------- /docs/.vuepress/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear/grapesjs/dev/docs/.vuepress/public/logo.png -------------------------------------------------------------------------------- /src/styles/fonts/main-fonts.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear/grapesjs/dev/src/styles/fonts/main-fonts.eot -------------------------------------------------------------------------------- /src/styles/fonts/main-fonts.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear/grapesjs/dev/src/styles/fonts/main-fonts.ttf -------------------------------------------------------------------------------- /src/styles/fonts/main-fonts.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear/grapesjs/dev/src/styles/fonts/main-fonts.woff -------------------------------------------------------------------------------- /docs/.vuepress/public/blocks3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear/grapesjs/dev/docs/.vuepress/public/blocks3.jpg -------------------------------------------------------------------------------- /docs/.vuepress/public/new-btn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear/grapesjs/dev/docs/.vuepress/public/new-btn.png -------------------------------------------------------------------------------- /docs/.vuepress/components/demos/DemoCanvasOnly.html: -------------------------------------------------------------------------------- 1 |
2 |

Hello World Component!

3 |
4 | -------------------------------------------------------------------------------- /docs/.vuepress/public/btn-clicked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear/grapesjs/dev/docs/.vuepress/public/btn-clicked.png -------------------------------------------------------------------------------- /docs/.vuepress/public/default-gjs.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear/grapesjs/dev/docs/.vuepress/public/default-gjs.jpg -------------------------------------------------------------------------------- /docs/.vuepress/public/default-sm.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear/grapesjs/dev/docs/.vuepress/public/default-sm.jpg -------------------------------------------------------------------------------- /docs/.vuepress/public/demo-view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear/grapesjs/dev/docs/.vuepress/public/demo-view.png -------------------------------------------------------------------------------- /docs/.vuepress/public/empty-gjs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear/grapesjs/dev/docs/.vuepress/public/empty-gjs.png -------------------------------------------------------------------------------- /docs/.vuepress/public/enabled-sm.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear/grapesjs/dev/docs/.vuepress/public/enabled-sm.jpg -------------------------------------------------------------------------------- /docs/.vuepress/public/logo-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear/grapesjs/dev/docs/.vuepress/public/logo-icon.png -------------------------------------------------------------------------------- /docs/.vuepress/public/new-panel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear/grapesjs/dev/docs/.vuepress/public/new-panel.png -------------------------------------------------------------------------------- /docs/.vuepress/public/style-comp.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear/grapesjs/dev/docs/.vuepress/public/style-comp.jpg -------------------------------------------------------------------------------- /docs/.vuepress/public/canvas-panels.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear/grapesjs/dev/docs/.vuepress/public/canvas-panels.jpg -------------------------------------------------------------------------------- /docs/.vuepress/public/cssom-result.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear/grapesjs/dev/docs/.vuepress/public/cssom-result.jpg -------------------------------------------------------------------------------- /docs/.vuepress/public/assets-svg-view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear/grapesjs/dev/docs/.vuepress/public/assets-svg-view.png -------------------------------------------------------------------------------- /docs/.vuepress/public/assets-uploader.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear/grapesjs/dev/docs/.vuepress/public/assets-uploader.png -------------------------------------------------------------------------------- /docs/.vuepress/public/cssom-devtools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear/grapesjs/dev/docs/.vuepress/public/cssom-devtools.png -------------------------------------------------------------------------------- /docs/.vuepress/public/default-traits.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear/grapesjs/dev/docs/.vuepress/public/default-traits.png -------------------------------------------------------------------------------- /docs/.vuepress/public/margin-strings.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear/grapesjs/dev/docs/.vuepress/public/margin-strings.jpg -------------------------------------------------------------------------------- /docs/.vuepress/public/sm-empty-state.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear/grapesjs/dev/docs/.vuepress/public/sm-empty-state.jpg -------------------------------------------------------------------------------- /docs/.vuepress/public/assets-empty-view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear/grapesjs/dev/docs/.vuepress/public/assets-empty-view.png -------------------------------------------------------------------------------- /docs/.vuepress/public/default-link-comp.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear/grapesjs/dev/docs/.vuepress/public/default-link-comp.jpg -------------------------------------------------------------------------------- /src/code_manager/config/config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | // Style prefix 3 | stylePrefix: 'cm-', 4 | 5 | inlineCss: false 6 | }; 7 | -------------------------------------------------------------------------------- /docs/.vuepress/public/assets-builtin-modal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear/grapesjs/dev/docs/.vuepress/public/assets-builtin-modal.png -------------------------------------------------------------------------------- /docs/.vuepress/public/assets-full-dropzone.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear/grapesjs/dev/docs/.vuepress/public/assets-full-dropzone.gif -------------------------------------------------------------------------------- /docs/.vuepress/public/block-custom-render.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear/grapesjs/dev/docs/.vuepress/public/block-custom-render.jpg -------------------------------------------------------------------------------- /docs/.vuepress/public/block-custom-render2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear/grapesjs/dev/docs/.vuepress/public/block-custom-render2.jpg -------------------------------------------------------------------------------- /docs/.vuepress/public/docs-init-link-trait.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear/grapesjs/dev/docs/.vuepress/public/docs-init-link-trait.jpg -------------------------------------------------------------------------------- /docs/.vuepress/public/docs-link-trait-raw.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear/grapesjs/dev/docs/.vuepress/public/docs-link-trait-raw.jpg -------------------------------------------------------------------------------- /docs/.vuepress/public/input-custom-traits.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clear/grapesjs/dev/docs/.vuepress/public/input-custom-traits.png -------------------------------------------------------------------------------- /src/dom_components/view/ComponentTableBodyView.js: -------------------------------------------------------------------------------- 1 | import ComponentView from './ComponentView'; 2 | 3 | export default ComponentView.extend({}); 4 | -------------------------------------------------------------------------------- /src/dom_components/view/ComponentTableCellView.js: -------------------------------------------------------------------------------- 1 | import ComponentView from './ComponentView'; 2 | 3 | export default ComponentView.extend({}); 4 | -------------------------------------------------------------------------------- /src/dom_components/view/ComponentTableFootView.js: -------------------------------------------------------------------------------- 1 | import ComponentView from './ComponentView'; 2 | 3 | export default ComponentView.extend({}); 4 | -------------------------------------------------------------------------------- /src/dom_components/view/ComponentTableHeadView.js: -------------------------------------------------------------------------------- 1 | import ComponentView from './ComponentView'; 2 | 3 | export default ComponentView.extend({}); 4 | -------------------------------------------------------------------------------- /src/dom_components/view/ComponentTableRowView.js: -------------------------------------------------------------------------------- 1 | import ComponentView from './ComponentView'; 2 | 3 | export default ComponentView.extend({}); 4 | -------------------------------------------------------------------------------- /src/commands/view/CanvasClear.js: -------------------------------------------------------------------------------- 1 | export default { 2 | run(ed) { 3 | ed.DomComponents.clear(); 4 | ed.CssComposer.clear(); 5 | } 6 | }; 7 | -------------------------------------------------------------------------------- /docs/.vuepress/styles/palette.styl: -------------------------------------------------------------------------------- 1 | $accentColor = #e2627f 2 | $accentColor = #e67891 3 | $navBarColor = white 4 | $scrollBarSize = 8px 5 | $pageWidth = 900px -------------------------------------------------------------------------------- /src/commands/model/Command.js: -------------------------------------------------------------------------------- 1 | import Backbone from 'backbone'; 2 | 3 | export default Backbone.Model.extend({ 4 | defaults: { 5 | id: '' 6 | } 7 | }); 8 | -------------------------------------------------------------------------------- /src/dom_components/view/ComponentTableView.js: -------------------------------------------------------------------------------- 1 | import ComponentView from './ComponentView'; 2 | 3 | export default ComponentView.extend({ 4 | events: {} 5 | }); 6 | -------------------------------------------------------------------------------- /src/modal_dialog/config/config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | stylePrefix: 'mdl-', 3 | 4 | title: '', 5 | 6 | content: '', 7 | 8 | backdrop: true 9 | }; 10 | -------------------------------------------------------------------------------- /src/panels/model/Panels.js: -------------------------------------------------------------------------------- 1 | import Backbone from 'backbone'; 2 | import Panel from './Panel'; 3 | 4 | export default Backbone.Collection.extend({ 5 | model: Panel 6 | }); 7 | -------------------------------------------------------------------------------- /src/block_manager/model/Blocks.js: -------------------------------------------------------------------------------- 1 | import Backbone from 'backbone'; 2 | import Block from './Block'; 3 | 4 | export default Backbone.Collection.extend({ 5 | model: Block 6 | }); 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | 7 | [*.js] 8 | charset = utf-8 9 | indent_style = space 10 | indent_size = 2 11 | -------------------------------------------------------------------------------- /src/commands/model/Commands.js: -------------------------------------------------------------------------------- 1 | import Backbone from 'backbone'; 2 | import Command from './Command'; 3 | 4 | export default Backbone.Collection.extend({ 5 | model: Command 6 | }); 7 | -------------------------------------------------------------------------------- /src/style_manager/model/Sectors.js: -------------------------------------------------------------------------------- 1 | import Backbone from 'backbone'; 2 | import Sector from './Sector'; 3 | 4 | export default Backbone.Collection.extend({ 5 | model: Sector 6 | }); 7 | -------------------------------------------------------------------------------- /src/block_manager/model/Categories.js: -------------------------------------------------------------------------------- 1 | import Backbone from 'backbone'; 2 | import Category from './Category'; 3 | 4 | export default Backbone.Collection.extend({ 5 | model: Category 6 | }); 7 | -------------------------------------------------------------------------------- /src/dom_components/model/Toolbar.js: -------------------------------------------------------------------------------- 1 | import Backbone from 'backbone'; 2 | import ToolbarButton from './ToolbarButton'; 3 | 4 | export default Backbone.Collection.extend({ model: ToolbarButton }); 5 | -------------------------------------------------------------------------------- /src/dom_components/model/ToolbarButton.js: -------------------------------------------------------------------------------- 1 | import Backbone from 'backbone'; 2 | 3 | export default Backbone.Model.extend({ 4 | defaults: { 5 | command: '', 6 | attributes: {} 7 | } 8 | }); 9 | -------------------------------------------------------------------------------- /src/modal_dialog/model/Modal.js: -------------------------------------------------------------------------------- 1 | import Backbone from 'backbone'; 2 | 3 | export default Backbone.Model.extend({ 4 | defaults: { 5 | title: '', 6 | content: '', 7 | open: false 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /src/style_manager/model/PropertySelect.js: -------------------------------------------------------------------------------- 1 | import Property from './PropertyRadio'; 2 | 3 | export default Property.extend({ 4 | defaults: () => ({ 5 | ...Property.prototype.defaults(), 6 | full: 0 7 | }) 8 | }); 9 | -------------------------------------------------------------------------------- /src/style_manager/model/PropertySlider.js: -------------------------------------------------------------------------------- 1 | import Property from './PropertyInteger'; 2 | 3 | export default Property.extend({ 4 | defaults: { 5 | ...Property.prototype.defaults, 6 | showInput: 1 7 | } 8 | }); 9 | -------------------------------------------------------------------------------- /src/dom_components/view/ComponentLabelView.js: -------------------------------------------------------------------------------- 1 | import ComponentLinkView from './ComponentLinkView'; 2 | 3 | export default ComponentLinkView.extend({ 4 | tagName: 'span' // Avoid Firefox bug with label editing #2332 5 | }); 6 | -------------------------------------------------------------------------------- /src/parser/config/config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | textTags: ['br', 'b', 'i', 'u', 'a', 'ul', 'ol'], 3 | 4 | // Custom CSS parser 5 | parserCss: null, 6 | 7 | // Custom HTML parser 8 | parserHtml: null 9 | }; 10 | -------------------------------------------------------------------------------- /src/block_manager/model/Category.js: -------------------------------------------------------------------------------- 1 | import Backbone from 'backbone'; 2 | 3 | export default Backbone.Model.extend({ 4 | defaults: { 5 | id: '', 6 | label: '', 7 | open: true, 8 | attributes: {} 9 | } 10 | }); 11 | -------------------------------------------------------------------------------- /src/css_composer/config/config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | // Style prefix 3 | stylePrefix: 'css-', 4 | 5 | // Custom CSS string to render on top 6 | staticRules: '', 7 | 8 | // Default CSS style 9 | rules: [] 10 | }; 11 | -------------------------------------------------------------------------------- /src/dom_components/view/ComponentCommentView.js: -------------------------------------------------------------------------------- 1 | import ComponentView from './ComponentTextNodeView'; 2 | 3 | export default ComponentView.extend({ 4 | _createElement() { 5 | return document.createComment(this.model.get('content')); 6 | } 7 | }); 8 | -------------------------------------------------------------------------------- /src/commands/view/CopyComponent.js: -------------------------------------------------------------------------------- 1 | export default { 2 | run(ed) { 3 | const em = ed.getModel(); 4 | const models = [...ed.getSelectedAll()]; 5 | 6 | if (models.length) { 7 | em.set('clipboard', models); 8 | } 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /docs/.vuepress/components/demos/DemoCanvasOnly.css: -------------------------------------------------------------------------------- 1 | /* Let's highlight canvas boundaries */ 2 | #gjs { 3 | border: 3px solid #444; 4 | } 5 | 6 | /* Reset some default styling */ 7 | .gjs-cv-canvas { 8 | top: 0; 9 | width: 100%; 10 | height: 100%; 11 | } 12 | -------------------------------------------------------------------------------- /src/asset_manager/model/AssetImage.js: -------------------------------------------------------------------------------- 1 | import Asset from './Asset'; 2 | 3 | export default Asset.extend({ 4 | defaults: { 5 | ...Asset.prototype.defaults, 6 | type: 'image', 7 | unitDim: 'px', 8 | height: 0, 9 | width: 0 10 | } 11 | }); 12 | -------------------------------------------------------------------------------- /src/dom_components/view/ComponentSvgView.js: -------------------------------------------------------------------------------- 1 | import ComponentView from './ComponentView'; 2 | 3 | export default ComponentView.extend({ 4 | _createElement: function(tagName) { 5 | return document.createElementNS('http://www.w3.org/2000/svg', tagName); 6 | } 7 | }); 8 | -------------------------------------------------------------------------------- /src/dom_components/model/ComponentWrapper.js: -------------------------------------------------------------------------------- 1 | // We need this one just to identify better the wrapper type 2 | import Component from './Component'; 3 | 4 | export default Component.extend( 5 | {}, 6 | { 7 | isComponent() { 8 | return false; 9 | } 10 | } 11 | ); 12 | -------------------------------------------------------------------------------- /docs/.vuepress/components/Demo.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 15 | -------------------------------------------------------------------------------- /src/block_manager/config/config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | // Specify the element to use as a container, string (query) or HTMLElement 3 | // With the empty value, nothing will be rendered 4 | appendTo: '', 5 | 6 | // Append blocks to canvas on click 7 | appendOnClick: 0, 8 | 9 | blocks: [] 10 | }; 11 | -------------------------------------------------------------------------------- /src/rich_text_editor/config/config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | stylePrefix: 'rte-', 3 | 4 | // If true, moves the toolbar below the element when the top canvas 5 | // edge is reached 6 | adjustToolbar: 1, 7 | 8 | // Default RTE actions 9 | actions: ['bold', 'italic', 'underline', 'strikethrough', 'link'] 10 | }; 11 | -------------------------------------------------------------------------------- /test/test_utils.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | storageMock() { 3 | var db = {}; 4 | return { 5 | id: 'testStorage', 6 | store(data) { 7 | db = data; 8 | }, 9 | load(keys) { 10 | return db; 11 | }, 12 | getDb() { 13 | return db; 14 | } 15 | }; 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /docs/api/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | pageClass: page__apis 3 | --- 4 | 5 | # API Reference 6 | 7 | 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. 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .settings/ 3 | .sass-cache/ 4 | .project 5 | .idea 6 | npm-debug.log* 7 | yarn-error.log 8 | yarn.lock 9 | style/.sass-cache/ 10 | stats.json 11 | 12 | img/ 13 | images/ 14 | private/ 15 | vendor/ 16 | coverage/ 17 | /locale/ 18 | node_modules/ 19 | bower_components/ 20 | grapesjs-*.tgz 21 | _index.html 22 | docs/.vuepress/dist 23 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "node": true 5 | }, 6 | "parserOptions": { 7 | "ecmaVersion": 2018, 8 | "sourceType": "module" 9 | }, 10 | "rules": { 11 | "strict": 0, 12 | "quotes": [0, "single"], 13 | "eol-last": [0], 14 | "no-mixed-requires": [0], 15 | "no-underscore-dangle": [0] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /docs/.vuepress/components/demos/DemoLayers.css: -------------------------------------------------------------------------------- 1 | .editor-row { 2 | display: flex; 3 | justify-content: flex-start; 4 | align-items: stretch; 5 | flex-wrap: nowrap; 6 | height: 300px; 7 | } 8 | 9 | .editor-canvas { 10 | flex-grow: 1; 11 | } 12 | 13 | .panel__right { 14 | flex-basis: 230px; 15 | position: relative; 16 | overflow-y: auto; 17 | } 18 | -------------------------------------------------------------------------------- /src/commands/config/config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | stylePrefix: 'com-', 3 | 4 | // Default array of commands 5 | defaults: [], 6 | 7 | // If true, stateful commands (with `run` and `stop` methods) can't be runned multiple times. 8 | // So, if the command is already active, running it again will not execute the `run` method 9 | strict: 1 10 | }; 11 | -------------------------------------------------------------------------------- /src/trait_manager/config/config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | stylePrefix: 'trt-', 3 | 4 | // Specify the element to use as a container, string (query) or HTMLElement 5 | // With the empty value, nothing will be rendered 6 | appendTo: '', 7 | 8 | // Default options for the target input 9 | optionsTarget: [{ value: false }, { value: '_blank' }] 10 | }; 11 | -------------------------------------------------------------------------------- /docs/.vuepress/components/DemoCanvasOnly.vue: -------------------------------------------------------------------------------- 1 | 3 | 4 | 13 | 14 | 16 | -------------------------------------------------------------------------------- /src/dom_components/view/ComponentTextNodeView.js: -------------------------------------------------------------------------------- 1 | import Backbone from 'backbone'; 2 | 3 | export default Backbone.View.extend({ 4 | initialize() { 5 | const { $el, model } = this; 6 | $el.data('model', model); 7 | model.view = this; 8 | }, 9 | _createElement() { 10 | return document.createTextNode(this.model.get('content')); 11 | } 12 | }); 13 | -------------------------------------------------------------------------------- /src/styles/scss/_gjs_status.scss: -------------------------------------------------------------------------------- 1 | $gjs-is-prefix: '.#{$app-prefix}is__'; 2 | 3 | @function gjs-is($name) { 4 | @return '#{$gjs-is-prefix}#{$name}'; 5 | } 6 | 7 | #{$gjs-is-prefix} { 8 | &grab, 9 | &grab * { 10 | cursor: grab !important; 11 | } 12 | 13 | &grabbing, 14 | &grabbing * { 15 | @include user-select(none); 16 | cursor: grabbing !important; 17 | } 18 | } -------------------------------------------------------------------------------- /src/dom_components/model/ComponentTableRow.js: -------------------------------------------------------------------------------- 1 | import Component from './Component'; 2 | 3 | export default Component.extend( 4 | { 5 | defaults: { 6 | ...Component.prototype.defaults, 7 | tagName: 'tr', 8 | draggable: ['thead', 'tbody', 'tfoot'], 9 | droppable: ['th', 'td'] 10 | } 11 | }, 12 | { 13 | isComponent: el => el.tagName == 'TR' && true 14 | } 15 | ); 16 | -------------------------------------------------------------------------------- /src/commands/view/ComponentEnter.js: -------------------------------------------------------------------------------- 1 | export default { 2 | run(ed) { 3 | if (!ed.Canvas.hasFocus()) return; 4 | const toSelect = []; 5 | 6 | ed.getSelectedAll().forEach(component => { 7 | const coll = component.components(); 8 | const next = coll && coll.at(0); 9 | next && toSelect.push(next); 10 | }); 11 | 12 | toSelect.length && ed.select(toSelect); 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /src/dom_components/view/ToolbarView.js: -------------------------------------------------------------------------------- 1 | import DomainViews from 'domain_abstract/view/DomainViews'; 2 | import ToolbarButtonView from './ToolbarButtonView'; 3 | 4 | export default DomainViews.extend({ 5 | itemView: ToolbarButtonView, 6 | 7 | initialize(opts = {}) { 8 | this.config = { editor: opts.editor || '', em: opts.em }; 9 | this.listenTo(this.collection, 'reset', this.render); 10 | } 11 | }); 12 | -------------------------------------------------------------------------------- /src/device_manager/model/Devices.js: -------------------------------------------------------------------------------- 1 | import Backbone from 'backbone'; 2 | import Device from './Device'; 3 | 4 | export default Backbone.Collection.extend({ 5 | model: Device, 6 | 7 | comparator: (left, right) => { 8 | const max = Number.MAX_VALUE; 9 | return (right.get('priority') || max) - (left.get('priority') || max); 10 | }, 11 | 12 | getSorted() { 13 | return this.sort(); 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /src/dom_components/model/ComponentText.js: -------------------------------------------------------------------------------- 1 | import Component from './Component'; 2 | 3 | export default Component.extend({ 4 | defaults: { 5 | ...Component.prototype.defaults, 6 | type: 'text', 7 | droppable: false, 8 | editable: true 9 | }, 10 | 11 | toHTML() { 12 | this.trigger('sync:content', { silent: 1 }); 13 | return Component.prototype.toHTML.apply(this, arguments); 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /docs/.vuepress/enhanceApp.js: -------------------------------------------------------------------------------- 1 | // We can use this hook to install additional Vue plugins, register global components, or add additional router hooks 2 | module.exports = ({ 3 | Vue, // the version of Vue being used in the VuePress app 4 | options, // the options for the root Vue instance 5 | router, // the router instance for the app 6 | siteData // site metadata 7 | }) => { 8 | // ...apply enhancements to the app 9 | } 10 | -------------------------------------------------------------------------------- /src/i18n/config.js: -------------------------------------------------------------------------------- 1 | import en from './locale/en'; 2 | 3 | export default { 4 | // Locale value 5 | locale: 'en', 6 | 7 | // Fallback locale 8 | localeFallback: 'en', 9 | 10 | // Detect locale by checking browser language 11 | detectLocale: 1, 12 | 13 | // Show warnings when some of the i18n resources are missing 14 | debug: 0, 15 | 16 | // Messages to translate 17 | messages: { 18 | en 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /src/styles/scss/_gjs_devices.scss: -------------------------------------------------------------------------------- 1 | .#{$app-prefix}devices-c { 2 | display: flex; 3 | align-items: center; 4 | padding: 3px; 5 | 6 | .#{$app-prefix}device-label { 7 | flex-grow: 2; 8 | text-align: left; 9 | margin-right: 10px; 10 | } 11 | 12 | .#{$app-prefix}select { 13 | flex-grow: 20; 14 | } 15 | 16 | .#{$app-prefix}add-trasp { 17 | flex-grow: 1; 18 | margin-left: 5px; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/css_composer/view/CssGroupRuleView.js: -------------------------------------------------------------------------------- 1 | import CssRuleView from './CssRuleView'; 2 | 3 | export default CssRuleView.extend({ 4 | _createElement: function(tagName) { 5 | return document.createTextNode(''); 6 | }, 7 | 8 | render() { 9 | const model = this.model; 10 | const important = model.get('important'); 11 | this.el.textContent = model.getDeclaration({ important }); 12 | return this; 13 | } 14 | }); 15 | -------------------------------------------------------------------------------- /src/dom_components/model/ComponentLabel.js: -------------------------------------------------------------------------------- 1 | import Component from './ComponentText'; 2 | 3 | export default Component.extend( 4 | { 5 | defaults: { 6 | ...Component.prototype.defaults, 7 | tagName: 'label', 8 | traits: ['id', 'title', 'for'] 9 | } 10 | }, 11 | { 12 | isComponent(el) { 13 | if (el.tagName == 'LABEL') { 14 | return { type: 'label' }; 15 | } 16 | } 17 | } 18 | ); 19 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .settings/ 3 | .sass-cache/ 4 | .project 5 | .idea 6 | npm-debug.log* 7 | yarn-error.log 8 | yarn.lock 9 | style/.sass-cache/ 10 | stats.json 11 | 12 | img/ 13 | images/ 14 | private/ 15 | vendor/ 16 | coverage/ 17 | node_modules/ 18 | bower_components/ 19 | grapesjs-*.tgz 20 | _index.html 21 | index.html 22 | docs 23 | .github 24 | test 25 | .editorconfig 26 | .eslintrc 27 | .travis.yml 28 | *.md 29 | webpack.config.js 30 | -------------------------------------------------------------------------------- /src/commands/view/ComponentNext.js: -------------------------------------------------------------------------------- 1 | export default { 2 | run(ed) { 3 | if (!ed.Canvas.hasFocus()) return; 4 | const toSelect = []; 5 | 6 | ed.getSelectedAll().forEach(component => { 7 | const coll = component.collection; 8 | const at = coll.indexOf(component); 9 | const next = coll.at(at + 1); 10 | toSelect.push(next || component); 11 | }); 12 | 13 | toSelect.length && ed.select(toSelect); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /src/dom_components/view/ComponentLinkView.js: -------------------------------------------------------------------------------- 1 | import ComponentView from './ComponentTextView'; 2 | 3 | export default ComponentView.extend({ 4 | render(...args) { 5 | ComponentView.prototype.render.apply(this, args); 6 | 7 | // I need capturing instead of bubbling as bubbled clicks from other 8 | // children will execute the link event 9 | this.el.addEventListener('click', this.prevDef, true); 10 | 11 | return this; 12 | } 13 | }); 14 | -------------------------------------------------------------------------------- /src/panels/model/Panel.js: -------------------------------------------------------------------------------- 1 | import Backbone from 'backbone'; 2 | import Buttons from './Buttons'; 3 | 4 | export default Backbone.Model.extend({ 5 | defaults: { 6 | id: '', 7 | content: '', 8 | visible: true, 9 | buttons: [], 10 | attributes: {} 11 | }, 12 | 13 | initialize(options) { 14 | this.btn = this.get('buttons') || []; 15 | this.buttons = new Buttons(this.btn); 16 | this.set('buttons', this.buttons); 17 | } 18 | }); 19 | -------------------------------------------------------------------------------- /test/specs/asset_manager/model/Assets.js: -------------------------------------------------------------------------------- 1 | import Assets from 'asset_manager/model/Assets'; 2 | 3 | describe('Assets', () => { 4 | var obj; 5 | 6 | beforeEach(() => { 7 | obj = new Assets(); 8 | }); 9 | 10 | afterEach(() => { 11 | obj = null; 12 | }); 13 | 14 | test('Object exists', () => { 15 | expect(obj).toBeTruthy(); 16 | }); 17 | 18 | test('Collection is empty', () => { 19 | expect(obj.length).toEqual(0); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /src/commands/view/ComponentPrev.js: -------------------------------------------------------------------------------- 1 | export default { 2 | run(ed) { 3 | if (!ed.Canvas.hasFocus()) return; 4 | const toSelect = []; 5 | 6 | ed.getSelectedAll().forEach(component => { 7 | const coll = component.collection; 8 | const at = coll.indexOf(component); 9 | const next = coll.at(at - 1); 10 | toSelect.push(next && at - 1 >= 0 ? next : component); 11 | }); 12 | 13 | toSelect.length && ed.select(toSelect); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /src/commands/view/SwitchVisibility.js: -------------------------------------------------------------------------------- 1 | export default { 2 | run(ed) { 3 | this.toggleVis(ed); 4 | }, 5 | 6 | stop(ed) { 7 | this.toggleVis(ed, 0); 8 | }, 9 | 10 | toggleVis(ed, active = 1) { 11 | if (!ed.Commands.isActive('preview')) { 12 | const method = active ? 'add' : 'remove'; 13 | 14 | ed.Canvas.getFrames().forEach(frame => { 15 | frame.view.getBody().classList[method](`${this.ppfx}dashed`); 16 | }); 17 | } 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | import Dragger from './Dragger'; 2 | import Sorter from './Sorter'; 3 | import Resizer from './Resizer'; 4 | 5 | export default () => { 6 | return { 7 | /** 8 | * Name of the module 9 | * @type {String} 10 | * @private 11 | */ 12 | name: 'Utils', 13 | 14 | /** 15 | * Initialize module 16 | */ 17 | init() { 18 | return this; 19 | }, 20 | 21 | Sorter, 22 | Resizer, 23 | Dragger 24 | }; 25 | }; 26 | -------------------------------------------------------------------------------- /src/canvas/view/FramesView.js: -------------------------------------------------------------------------------- 1 | import DomainViews from 'domain_abstract/view/DomainViews'; 2 | import FrameWrapView from './FrameWrapView'; 3 | 4 | export default DomainViews.extend({ 5 | itemView: FrameWrapView, 6 | autoAdd: 1, 7 | 8 | init() { 9 | this.listenTo(this.collection, 'reset', this.render); 10 | }, 11 | 12 | onRender() { 13 | const { config, $el } = this; 14 | const { em } = config; 15 | em && $el.attr({ class: `${em.getConfig('stylePrefix')}frames` }); 16 | } 17 | }); 18 | -------------------------------------------------------------------------------- /src/dom_components/model/ComponentTableFoot.js: -------------------------------------------------------------------------------- 1 | import ComponentTableBody from './ComponentTableBody'; 2 | 3 | export default ComponentTableBody.extend( 4 | { 5 | defaults: { 6 | ...ComponentTableBody.prototype.defaults, 7 | type: 'tfoot', 8 | tagName: 'tfoot' 9 | } 10 | }, 11 | { 12 | isComponent(el) { 13 | let result = ''; 14 | 15 | if (el.tagName == 'TFOOT') { 16 | result = { type: 'tfoot' }; 17 | } 18 | 19 | return result; 20 | } 21 | } 22 | ); 23 | -------------------------------------------------------------------------------- /src/dom_components/model/ComponentTableHead.js: -------------------------------------------------------------------------------- 1 | import ComponentTableBody from './ComponentTableBody'; 2 | 3 | export default ComponentTableBody.extend( 4 | { 5 | defaults: { 6 | ...ComponentTableBody.prototype.defaults, 7 | type: 'thead', 8 | tagName: 'thead' 9 | } 10 | }, 11 | { 12 | isComponent(el) { 13 | let result = ''; 14 | 15 | if (el.tagName == 'THEAD') { 16 | result = { type: 'thead' }; 17 | } 18 | 19 | return result; 20 | } 21 | } 22 | ); 23 | -------------------------------------------------------------------------------- /src/dom_components/model/ComponentComment.js: -------------------------------------------------------------------------------- 1 | import Component from './ComponentTextNode'; 2 | 3 | export default Component.extend( 4 | { 5 | defaults: { 6 | ...Component.prototype.defaults 7 | }, 8 | 9 | toHTML() { 10 | return ``; 11 | } 12 | }, 13 | { 14 | isComponent(el) { 15 | if (el.nodeType == 8) { 16 | return { 17 | tagName: 'NULL', 18 | type: 'comment', 19 | content: el.textContent 20 | }; 21 | } 22 | } 23 | } 24 | ); 25 | -------------------------------------------------------------------------------- /test/setup.js: -------------------------------------------------------------------------------- 1 | import _ from 'underscore'; 2 | import Backbone from 'backbone'; 3 | import sinon from 'sinon'; 4 | import grapesjs from './../src'; 5 | 6 | const localStorage = { 7 | getItem(key) { 8 | return this[key]; 9 | }, 10 | setItem(key, value) { 11 | this[key] = value; 12 | }, 13 | removeItem(key, value) { 14 | delete this[key]; 15 | } 16 | }; 17 | 18 | global.Backbone = Backbone; 19 | global._ = _; 20 | global.sinon = sinon; 21 | global.grapesjs = grapesjs; 22 | global.$ = Backbone.$; 23 | global.localStorage = localStorage; 24 | -------------------------------------------------------------------------------- /test/specs/trait_manager/model/TraitsModel.js: -------------------------------------------------------------------------------- 1 | import Trait from 'trait_manager/model/Trait'; 2 | import Component from 'dom_components/model/Component'; 3 | 4 | describe('TraitModels', () => { 5 | var obj; 6 | var target; 7 | var modelName = 'title'; 8 | 9 | beforeEach(() => { 10 | target = new Component(); 11 | obj = new Trait({ 12 | name: modelName, 13 | target 14 | }); 15 | }); 16 | 17 | afterEach(() => { 18 | obj = null; 19 | }); 20 | 21 | test('Object exists', () => { 22 | expect(Trait).toBeTruthy(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /src/code_manager/model/HtmlGenerator.js: -------------------------------------------------------------------------------- 1 | import Backbone from 'backbone'; 2 | 3 | export default Backbone.Model.extend({ 4 | build(model, opts = {}) { 5 | const models = model.get('components'); 6 | 7 | if (opts.exportWrapper) { 8 | return model.toHTML({ 9 | ...(opts.wrapperIsBody && { tag: 'body' }) 10 | }); 11 | } 12 | 13 | return this.buildModels(models); 14 | }, 15 | 16 | buildModels(models) { 17 | let code = ''; 18 | models.each(model => { 19 | code += model.toHTML(); 20 | }); 21 | return code; 22 | } 23 | }); 24 | -------------------------------------------------------------------------------- /src/commands/view/ComponentExit.js: -------------------------------------------------------------------------------- 1 | export default { 2 | run(ed, snd, opts = {}) { 3 | if (!ed.Canvas.hasFocus() && !opts.force) return; 4 | const toSelect = []; 5 | 6 | ed.getSelectedAll().forEach(component => { 7 | let next = component.parent(); 8 | 9 | // Recurse through the parent() chain until a selectable parent is found 10 | while (next && !next.get('selectable')) { 11 | next = next.parent(); 12 | } 13 | 14 | next && toSelect.push(next); 15 | }); 16 | 17 | toSelect.length && ed.select(toSelect); 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /docs/.vuepress/components/demos/DemoCanvasOnly.js: -------------------------------------------------------------------------------- 1 | const editor = grapesjs.init({ 2 | // Indicate where to init the editor. You can also pass an HTMLElement 3 | container: '#gjs', 4 | // Get the content for the canvas directly from the element 5 | // As an alternative we could use: `components: '

Hello World Component!

'`, 6 | fromElement: true, 7 | // Size of the editor 8 | height: '300px', 9 | width: 'auto', 10 | // Disable the storage manager for the moment 11 | storageManager: false, 12 | // Avoid any default panel 13 | panels: { defaults: [] }, 14 | }); 15 | -------------------------------------------------------------------------------- /docs/.vuepress/components/DemoBasicBlocks.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 19 | 20 | 30 | -------------------------------------------------------------------------------- /src/dom_components/model/ComponentSvgIn.js: -------------------------------------------------------------------------------- 1 | import Component from './ComponentSvg'; 2 | 3 | /** 4 | * Component for inner SVG elements 5 | */ 6 | export default Component.extend( 7 | { 8 | defaults: { 9 | ...Component.prototype.defaults, 10 | selectable: false, 11 | hoverable: false, 12 | layerable: false 13 | } 14 | }, 15 | { 16 | isComponent(el) { 17 | if (Component.isComponent(el) && el.tagName.toLowerCase() !== 'svg') { 18 | return { 19 | tagName: el.tagName, 20 | type: 'svg-in' 21 | }; 22 | } 23 | } 24 | } 25 | ); 26 | -------------------------------------------------------------------------------- /src/dom_components/model/ComponentTableCell.js: -------------------------------------------------------------------------------- 1 | import Component from './Component'; 2 | 3 | export default Component.extend( 4 | { 5 | defaults: { 6 | ...Component.prototype.defaults, 7 | type: 'cell', 8 | tagName: 'td', 9 | draggable: ['tr'] 10 | } 11 | }, 12 | { 13 | isComponent(el) { 14 | let result = ''; 15 | const tag = el.tagName; 16 | 17 | if (tag == 'TD' || tag == 'TH') { 18 | result = { 19 | type: 'cell', 20 | tagName: tag.toLowerCase() 21 | }; 22 | } 23 | 24 | return result; 25 | } 26 | } 27 | ); 28 | -------------------------------------------------------------------------------- /docs/.vuepress/theme/layouts/Layout.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 22 | -------------------------------------------------------------------------------- /src/dom_components/model/ComponentScript.js: -------------------------------------------------------------------------------- 1 | import Component from './Component'; 2 | 3 | export default Component.extend( 4 | { 5 | defaults: { 6 | ...Component.prototype.defaults, 7 | type: 'script', 8 | droppable: false, 9 | draggable: false, 10 | layerable: false 11 | } 12 | }, 13 | { 14 | isComponent(el) { 15 | if (el.tagName == 'SCRIPT') { 16 | var result = { type: 'script' }; 17 | 18 | if (el.src) { 19 | result.src = el.src; 20 | result.onload = el.onload; 21 | } 22 | 23 | return result; 24 | } 25 | } 26 | } 27 | ); 28 | -------------------------------------------------------------------------------- /src/asset_manager/model/Asset.js: -------------------------------------------------------------------------------- 1 | import Backbone from 'backbone'; 2 | 3 | export default Backbone.Model.extend({ 4 | idAttribute: 'src', 5 | 6 | defaults: { 7 | type: '', 8 | src: '' 9 | }, 10 | 11 | /** 12 | * Get filename of the asset 13 | * @return {string} 14 | * @private 15 | * */ 16 | getFilename() { 17 | return this.get('src') 18 | .split('/') 19 | .pop(); 20 | }, 21 | 22 | /** 23 | * Get extension of the asset 24 | * @return {string} 25 | * @private 26 | * */ 27 | getExtension() { 28 | return this.getFilename() 29 | .split('.') 30 | .pop(); 31 | } 32 | }); 33 | -------------------------------------------------------------------------------- /test/specs/asset_manager/model/AssetImage.js: -------------------------------------------------------------------------------- 1 | import AssetImage from 'asset_manager/model/AssetImage'; 2 | 3 | describe('AssetImage', () => { 4 | test('Object exists', () => { 5 | expect(AssetImage).toBeTruthy(); 6 | }); 7 | 8 | test('Has default values', () => { 9 | var obj = new AssetImage({}); 10 | expect(obj.get('type')).toEqual('image'); 11 | expect(obj.get('src')).toBeFalsy(); 12 | expect(obj.get('unitDim')).toEqual('px'); 13 | expect(obj.get('height')).toEqual(0); 14 | expect(obj.get('width')).toEqual(0); 15 | expect(obj.getExtension()).toBeFalsy(); 16 | expect(obj.getFilename()).toBeFalsy(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/asset_manager/model/Assets.js: -------------------------------------------------------------------------------- 1 | import Backbone from 'backbone'; 2 | import AssetImage from './AssetImage'; 3 | import AssetImageView from './../view/AssetImageView'; 4 | import TypeableCollection from 'domain_abstract/model/TypeableCollection'; 5 | 6 | export default Backbone.Collection.extend(TypeableCollection).extend({ 7 | types: [ 8 | { 9 | id: 'image', 10 | model: AssetImage, 11 | view: AssetImageView, 12 | isType(value) { 13 | if (typeof value == 'string') { 14 | return { 15 | type: 'image', 16 | src: value 17 | }; 18 | } 19 | return value; 20 | } 21 | } 22 | ] 23 | }); 24 | -------------------------------------------------------------------------------- /test/specs/commands/model/CommandModels.js: -------------------------------------------------------------------------------- 1 | import Command from 'commands/model/Command'; 2 | import Commands from 'commands'; 3 | 4 | describe('Command', () => { 5 | let obj; 6 | 7 | beforeEach(() => { 8 | obj = new Command(); 9 | }); 10 | 11 | afterEach(() => { 12 | obj = null; 13 | }); 14 | 15 | test('Has id property', () => { 16 | expect(obj.has('id')).toEqual(true); 17 | }); 18 | }); 19 | 20 | describe('Commands', () => { 21 | var obj; 22 | 23 | beforeEach(() => { 24 | obj = new Commands(); 25 | }); 26 | 27 | afterEach(() => { 28 | obj = null; 29 | }); 30 | 31 | test('Object is ok', () => { 32 | expect(obj).toBeTruthy(); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /src/css_composer/view/CssRuleView.js: -------------------------------------------------------------------------------- 1 | import Backbone from 'backbone'; 2 | 3 | export default Backbone.View.extend({ 4 | tagName: 'style', 5 | 6 | initialize(o = {}) { 7 | this.config = o.config || {}; 8 | const model = this.model; 9 | const toTrack = 'change:style change:state change:mediaText'; 10 | this.listenTo(model, toTrack, this.render); 11 | this.listenTo(model, 'destroy remove', this.remove); 12 | this.listenTo(model.get('selectors'), 'change', this.render); 13 | }, 14 | 15 | render() { 16 | const model = this.model; 17 | const important = model.get('important'); 18 | this.el.innerHTML = this.model.toCSS({ important }); 19 | return this; 20 | } 21 | }); 22 | -------------------------------------------------------------------------------- /src/panels/model/Button.js: -------------------------------------------------------------------------------- 1 | import Backbone from 'backbone'; 2 | 3 | export default Backbone.Model.extend({ 4 | defaults: { 5 | id: '', 6 | label: '', 7 | tagName: 'span', 8 | className: '', 9 | command: '', 10 | context: '', 11 | buttons: [], 12 | attributes: {}, 13 | options: {}, 14 | active: false, 15 | dragDrop: false, 16 | togglable: true, 17 | runDefaultCommand: true, 18 | stopDefaultCommand: false, 19 | disable: false 20 | }, 21 | 22 | initialize(options) { 23 | if (this.get('buttons').length) { 24 | var Buttons = require('./Buttons').default; 25 | this.set('buttons', new Buttons(this.get('buttons'))); 26 | } 27 | } 28 | }); 29 | -------------------------------------------------------------------------------- /src/commands/view/OpenBlocks.js: -------------------------------------------------------------------------------- 1 | export default { 2 | run(editor, sender) { 3 | const bm = editor.BlockManager; 4 | const pn = editor.Panels; 5 | 6 | if (!this.blocks) { 7 | bm.render(); 8 | const id = 'views-container'; 9 | const blocks = document.createElement('div'); 10 | const panels = pn.getPanel(id) || pn.addPanel({ id }); 11 | blocks.appendChild(bm.getContainer()); 12 | panels.set('appendContent', blocks).trigger('change:appendContent'); 13 | this.blocks = blocks; 14 | } 15 | 16 | this.blocks.style.display = 'block'; 17 | }, 18 | 19 | stop() { 20 | const blocks = this.blocks; 21 | blocks && (blocks.style.display = 'none'); 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /src/dom_components/model/ComponentSvg.js: -------------------------------------------------------------------------------- 1 | import Component from './Component'; 2 | 3 | export default Component.extend( 4 | { 5 | defaults: { 6 | ...Component.prototype.defaults, 7 | resizable: { ratioDefault: 1 }, 8 | highlightable: 0 9 | }, 10 | 11 | getName() { 12 | let name = this.get('tagName'); 13 | let customName = this.get('custom-name'); 14 | name = name.charAt(0).toUpperCase() + name.slice(1); 15 | return customName || name; 16 | } 17 | }, 18 | { 19 | isComponent(el) { 20 | if (SVGElement && el instanceof SVGElement) { 21 | return { 22 | tagName: el.tagName, 23 | type: 'svg' 24 | }; 25 | } 26 | } 27 | } 28 | ); 29 | -------------------------------------------------------------------------------- /src/canvas/model/Frames.js: -------------------------------------------------------------------------------- 1 | import { bindAll } from 'underscore'; 2 | import Backbone from 'backbone'; 3 | import model from './Frame'; 4 | 5 | export default Backbone.Collection.extend({ 6 | model, 7 | 8 | initialize() { 9 | bindAll(this, 'itemLoaded'); 10 | }, 11 | 12 | itemLoaded() { 13 | this.loadedItems++; 14 | 15 | if (this.loadedItems >= this.itemsToLoad) { 16 | this.trigger('loaded:all'); 17 | this.listenToLoadItems(0); 18 | } 19 | }, 20 | 21 | listenToLoad() { 22 | this.loadedItems = 0; 23 | this.itemsToLoad = this.length; 24 | this.listenToLoadItems(1); 25 | }, 26 | 27 | listenToLoadItems(on) { 28 | this.forEach(item => item[on ? 'on' : 'off']('loaded', this.itemLoaded)); 29 | } 30 | }); 31 | -------------------------------------------------------------------------------- /src/commands/view/ComponentDelete.js: -------------------------------------------------------------------------------- 1 | import { isArray } from 'underscore'; 2 | 3 | export default { 4 | run(ed, sender, opts = {}) { 5 | let components = opts.component || ed.getSelectedAll(); 6 | components = isArray(components) ? [...components] : [components]; 7 | 8 | // It's important to deselect components first otherwise, 9 | // with undo, the component will be set with the wrong `collection` 10 | ed.select(null); 11 | 12 | components.forEach(component => { 13 | if (!component || !component.get('removable')) { 14 | return this.em.logWarning('The element is not removable', { 15 | component 16 | }); 17 | } 18 | component.remove(); 19 | }); 20 | 21 | return components; 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /src/dom_components/model/ComponentTable.js: -------------------------------------------------------------------------------- 1 | import Component from './Component'; 2 | 3 | export default Component.extend( 4 | { 5 | defaults: { 6 | ...Component.prototype.defaults, 7 | type: 'table', 8 | tagName: 'table', 9 | droppable: ['tbody', 'thead', 'tfoot'] 10 | }, 11 | 12 | initialize(o, opt) { 13 | Component.prototype.initialize.apply(this, arguments); 14 | const components = this.get('components'); 15 | !components.length && components.add({ type: 'tbody' }); 16 | } 17 | }, 18 | { 19 | isComponent(el) { 20 | let result = ''; 21 | 22 | if (el.tagName == 'TABLE') { 23 | result = { type: 'table' }; 24 | } 25 | 26 | return result; 27 | } 28 | } 29 | ); 30 | -------------------------------------------------------------------------------- /.github/no-response.yml: -------------------------------------------------------------------------------- 1 | # Configuration for probot-no-response - https://github.com/probot/no-response 2 | 3 | # Number of days of inactivity before an Issue is closed for lack of response 4 | daysUntilClose: 10 5 | # Label requiring a response 6 | responseRequiredLabel: more-information-needed 7 | # Comment to post when closing an Issue for lack of response. Set to `false` to disable 8 | closeComment: > 9 | This issue has been automatically closed because there has been no response 10 | to our request for more information from the original author. With only the 11 | information that is currently in the issue, we don't have enough information 12 | to take action. Please reach out if you have or find the answers we need so 13 | that we can investigate further. 14 | -------------------------------------------------------------------------------- /src/code_manager/view/EditorView.js: -------------------------------------------------------------------------------- 1 | import { template } from 'underscore'; 2 | import Backbone from 'backbone'; 3 | 4 | export default Backbone.View.extend({ 5 | template: template(` 6 |
7 |
<%= label %>
8 |
9 |
`), 10 | 11 | initialize(o) { 12 | this.config = o.config || {}; 13 | this.pfx = this.config.stylePrefix; 14 | }, 15 | 16 | render() { 17 | var obj = this.model.toJSON(); 18 | obj.pfx = this.pfx; 19 | this.$el.html(this.template(obj)); 20 | this.$el.attr('class', this.pfx + 'editor-c'); 21 | this.$el.find('#' + this.pfx + 'code').append(this.model.get('input')); 22 | return this; 23 | } 24 | }); 25 | -------------------------------------------------------------------------------- /src/commands/view/OpenLayers.js: -------------------------------------------------------------------------------- 1 | import Backbone from 'backbone'; 2 | import Layers from 'navigator'; 3 | 4 | const $ = Backbone.$; 5 | 6 | export default { 7 | run(editor) { 8 | const lm = editor.LayerManager; 9 | const pn = editor.Panels; 10 | 11 | if (!this.layers) { 12 | const id = 'views-container'; 13 | const layers = document.createElement('div'); 14 | const panels = pn.getPanel(id) || pn.addPanel({ id }); 15 | layers.appendChild(lm.render()); 16 | panels.set('appendContent', layers).trigger('change:appendContent'); 17 | this.layers = layers; 18 | } 19 | 20 | this.layers.style.display = 'block'; 21 | }, 22 | 23 | stop() { 24 | const layers = this.layers; 25 | layers && (layers.style.display = 'none'); 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /docs/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # abort on errors 4 | set -e 5 | 6 | # build 7 | npm run docs:build 8 | 9 | # navigate into the build output directory 10 | cd docs/.vuepress/dist 11 | 12 | # I need to deploy all the documentation inside docs folder 13 | mkdir docs-new 14 | 15 | # move all the files from the current directory in docs 16 | mv `\ls -1 ./ | grep -v docs-new` ./docs-new 17 | 18 | # fetch the current site, remove the old docs dir and make current the new one 19 | git clone -b gh-pages https://github.com/artf/grapesjs.git tmp && mv tmp/* tmp/.* . && rm -rf tmp 20 | rm -fR docs 21 | mv ./docs-new ./docs 22 | 23 | # stage all and commit 24 | git add -A 25 | git commit -m 'deploy docs' 26 | git push https://github.com/artf/grapesjs.git gh-pages 27 | # surge --domain grapesjs.surge.sh 28 | cd - 29 | -------------------------------------------------------------------------------- /src/dom_components/model/ComponentTextNode.js: -------------------------------------------------------------------------------- 1 | import Component from './Component'; 2 | 3 | export default Component.extend( 4 | { 5 | defaults: { 6 | ...Component.prototype.defaults, 7 | droppable: false, 8 | layerable: false, 9 | editable: true 10 | }, 11 | 12 | toHTML() { 13 | return this.get('content') 14 | .replace(/&/g, '&') 15 | .replace(//g, '>') 17 | .replace(/"/g, '"') 18 | .replace(/'/g, '''); 19 | } 20 | }, 21 | { 22 | isComponent(el) { 23 | var result = ''; 24 | if (el.nodeType === 3) { 25 | result = { 26 | type: 'textnode', 27 | content: el.textContent 28 | }; 29 | } 30 | return result; 31 | } 32 | } 33 | ); 34 | -------------------------------------------------------------------------------- /src/trait_manager/view/TraitColorView.js: -------------------------------------------------------------------------------- 1 | import TraitView from './TraitView'; 2 | import InputColor from 'domain_abstract/ui/InputColor'; 3 | 4 | export default TraitView.extend({ 5 | templateInput: '', 6 | 7 | /** 8 | * Returns input element 9 | * @return {HTMLElement} 10 | * @private 11 | */ 12 | getInputEl() { 13 | if (!this.input) { 14 | const model = this.model; 15 | const value = this.getModelValue(); 16 | const inputColor = new InputColor({ 17 | model, 18 | target: this.config.em, 19 | contClass: this.ppfx + 'field-color', 20 | ppfx: this.ppfx 21 | }); 22 | const input = inputColor.render(); 23 | input.setValue(value, { fromTarget: 1 }); 24 | this.input = input.el; 25 | } 26 | 27 | return this.input; 28 | } 29 | }); 30 | -------------------------------------------------------------------------------- /src/css_composer/model/CssRules.js: -------------------------------------------------------------------------------- 1 | import Backbone from 'backbone'; 2 | import CssRule from './CssRule'; 3 | 4 | export default Backbone.Collection.extend({ 5 | model: CssRule, 6 | 7 | initialize(models, opt) { 8 | // Inject editor 9 | if (opt && opt.em) this.editor = opt.em; 10 | 11 | // This will put the listener post CssComposer.postLoad 12 | setTimeout(() => this.on('remove', this.onRemove)); 13 | }, 14 | 15 | onRemove(removed) { 16 | const em = this.editor; 17 | em.stopListening(removed); 18 | em.get('UndoManager').remove(removed); 19 | }, 20 | 21 | add(models, opt = {}) { 22 | if (typeof models === 'string') { 23 | models = this.editor.get('Parser').parseCss(models); 24 | } 25 | opt.em = this.editor; 26 | return Backbone.Collection.prototype.add.apply(this, [models, opt]); 27 | } 28 | }); 29 | -------------------------------------------------------------------------------- /src/style_manager/view/PropertyColorView.js: -------------------------------------------------------------------------------- 1 | import PropertyIntegerView from './PropertyIntegerView'; 2 | import InputColor from 'domain_abstract/ui/InputColor'; 3 | 4 | export default PropertyIntegerView.extend({ 5 | setValue(value, opts = {}) { 6 | opts = { ...opts, silent: 1 }; 7 | this.inputInst.setValue(value, opts); 8 | }, 9 | 10 | onRender() { 11 | if (!this.input) { 12 | const ppfx = this.ppfx; 13 | const inputColor = new InputColor({ 14 | target: this.target, 15 | model: this.model, 16 | ppfx 17 | }); 18 | const input = inputColor.render(); 19 | this.el.querySelector(`.${ppfx}fields`).appendChild(input.el); 20 | this.$input = input.inputEl; 21 | this.$color = input.colorEl; 22 | this.input = this.$input.get(0); 23 | this.inputInst = input; 24 | } 25 | } 26 | }); 27 | -------------------------------------------------------------------------------- /src/commands/view/ComponentStyleClear.js: -------------------------------------------------------------------------------- 1 | import { isArray } from 'underscore'; 2 | 3 | export default { 4 | run(ed, sender, opts = {}) { 5 | const { target } = opts; 6 | const dc = ed.DomComponents; 7 | const type = target.get('type'); 8 | const len = dc.getWrapper().find(`[data-gjs-type="${type}"]`).length; 9 | const toRemove = []; 10 | 11 | if (!len) { 12 | const rules = ed.CssComposer.getAll(); 13 | let toClear = target.get('style-signature'); 14 | toClear = isArray(toClear) ? toClear : [toClear]; 15 | 16 | rules.forEach(rule => { 17 | const selector = rule.selectorsToString(); 18 | toClear.forEach(part => { 19 | part && selector.indexOf(part) >= 0 && toRemove.push(rule); 20 | }); 21 | }); 22 | 23 | rules.remove(toRemove); 24 | } 25 | 26 | return toRemove; 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /test/specs/commands/view/SwitchVisibility.js: -------------------------------------------------------------------------------- 1 | import SwitchVisibility from 'commands/view/SwitchVisibility'; 2 | 3 | describe('SwitchVisibility command', () => { 4 | let fakeEditor, fakeFrames, fakeIsActive; 5 | 6 | beforeEach(() => { 7 | fakeFrames = []; 8 | fakeIsActive = false; 9 | 10 | fakeEditor = { 11 | Canvas: { 12 | getFrames: jest.fn(() => fakeFrames) 13 | }, 14 | 15 | Commands: { 16 | isActive: jest.fn(() => fakeIsActive) 17 | } 18 | }; 19 | }); 20 | 21 | describe('.toggleVis', () => { 22 | it('should do nothing if the preview command is active', () => { 23 | expect(fakeEditor.Canvas.getFrames).not.toHaveBeenCalled(); 24 | fakeIsActive = true; 25 | SwitchVisibility.toggleVis(fakeEditor); 26 | expect(fakeEditor.Canvas.getFrames).not.toHaveBeenCalled(); 27 | }); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /test/specs/style_manager/view/SectorsView.js: -------------------------------------------------------------------------------- 1 | import SectorsView from 'style_manager/view/SectorsView'; 2 | import Sectors from 'style_manager/model/Sectors'; 3 | 4 | describe('SectorsView', () => { 5 | var fixtures; 6 | var model; 7 | var view; 8 | 9 | beforeEach(() => { 10 | model = new Sectors([]); 11 | view = new SectorsView({ 12 | collection: model 13 | }); 14 | document.body.innerHTML = '
'; 15 | fixtures = document.body.firstChild; 16 | fixtures.appendChild(view.render().el); 17 | }); 18 | 19 | afterEach(() => { 20 | view.collection.reset(); 21 | }); 22 | 23 | test('Collection is empty', () => { 24 | expect(view.el.innerHTML).toEqual(''); 25 | }); 26 | 27 | test('Add new sectors', () => { 28 | view.collection.add([{}, {}]); 29 | expect(view.el.children.length).toEqual(2); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /docs/.vuepress/components/DemoViewer.vue: -------------------------------------------------------------------------------- 1 |