├── .gitignore ├── LICENSE ├── README.md ├── index.html ├── package-lock.json ├── package.json ├── src ├── blocks.ts └── index.ts ├── tsconfig.json └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .settings/ 3 | .sass-cache/ 4 | .project 5 | .eslintrc 6 | .idea 7 | .npmrc 8 | npm-debug.log 9 | _index.html 10 | 11 | dist/ 12 | img/ 13 | images/ 14 | private/ 15 | docs/ 16 | vendor/ 17 | coverage/ 18 | node_modules/ 19 | bower_components/ 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017-current, Artur Arseniev 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | - Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | - Redistributions in binary form must reproduce the above copyright notice, this 10 | list of conditions and the following disclaimer in the documentation and/or 11 | other materials provided with the distribution. 12 | - Neither the name "GrapesJS" nor the names of its contributors may be 13 | used to endorse or promote products derived from this software without 14 | specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 20 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 23 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GrapesJS Basic Blocks 2 | 3 | This plugin contains some basic blocks for the GrapesJS editor 4 | 5 | [Demo](http://grapesjs.com/demo.html) 6 |
7 | 8 | ## Summary 9 | 10 | - Plugin name: `gjs-blocks-basic` 11 | - Blocks: `column1`, `column2`, `column3`, `column3-7`, `text`, `link`, `image`, `video`, `map` 12 | 13 | ## Options 14 | 15 | | Option | Description | Default | 16 | | --------------- | -------------------------------- | ----------------------------------------------------------------------------------------------- | 17 | | `blocks` | Which blocks to add | `['column1', 'column2', 'column3', 'column3-7', 'text', 'link', 'image', 'video', 'map']` (all) | 18 | | `category` | Category name | `Basic` | 19 | | `flexGrid` | Make use of flexbox for the grid | `false` | 20 | | `stylePrefix` | Classes prefix | `gjs-` | 21 | | `addBasicStyle` | Use basic CSS for blocks | `true` | 22 | | `labelColumn1` | 1 Column label | `1 Column` | 23 | | `labelColumn2` | 2 Columns label | `2 Columns` | 24 | | `labelColumn3` | 3 Columns label | `3 Columns` | 25 | | `labelColumn37` | 3/7 Columns label | `2 Columns 3/7` | 26 | | `labelText` | Text label | `Text` | 27 | | `labelLink` | Link label | `Link` | 28 | | `labelImage` | Image label | `Image` | 29 | | `labelVideo` | Video label | `Video` | 30 | | `labelMap` | Map label | `Map` | 31 | | `rowHeight` | Initial height | `75` | 32 | 33 | ## Download 34 | 35 | * CDN 36 | * `https://unpkg.com/grapesjs-blocks-basic` 37 | * NPM 38 | * `npm i grapesjs-blocks-basic` 39 | * GIT 40 | * `git clone https://github.com/GrapesJS/blocks-basic.git` 41 | 42 | ## Usage 43 | 44 | Directly in the browser 45 | ```html 46 | 47 | 48 | 49 | 50 |
51 | 52 | 64 | ``` 65 | 66 | Modern javascript 67 | ```js 68 | import grapesjs from 'grapesjs'; 69 | import plugin from 'grapesjs-blocks-basic'; 70 | 71 | const editor = grapesjs.init({ 72 | container : '#gjs', 73 | // ... 74 | plugins: [plugin], 75 | pluginsOpts: { 76 | [plugin]: { /* options */ } 77 | } 78 | // or 79 | plugins: [ 80 | editor => plugin(editor, { /* options */ }), 81 | ], 82 | }); 83 | ``` 84 | 85 | ## Development 86 | 87 | Clone the repository 88 | 89 | ```sh 90 | $ git clone https://github.com/GrapesJS/blocks-basic.git 91 | $ cd grapesjs-blocks-basic 92 | ``` 93 | 94 | Install it 95 | 96 | ```sh 97 | $ npm i 98 | ``` 99 | 100 | Start the dev server 101 | 102 | ```sh 103 | $ npm start 104 | ``` 105 | 106 | Build before the commit. This will also increase the patch level version of the package 107 | 108 | ```sh 109 | $ npm run build 110 | ``` 111 | 112 | 113 | ## License 114 | 115 | BSD 3-Clause 116 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | GrapesJS Plugin 6 | 7 | 8 | 14 | 15 | 16 |
17 | 18 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "grapesjs-blocks-basic", 3 | "version": "1.0.2", 4 | "description": "Basic blocks for the GrapesJS editor", 5 | "main": "dist/index.js", 6 | "files": [ 7 | "dist/" 8 | ], 9 | "scripts": { 10 | "build": "grapesjs-cli build", 11 | "start": "grapesjs-cli serve" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/GrapesJS/blocks-basic.git" 16 | }, 17 | "keywords": [ 18 | "grapesjs", 19 | "plugin", 20 | "blocks", 21 | "basic" 22 | ], 23 | "author": "Artur Arseniev", 24 | "license": "BSD-3-Clause", 25 | "devDependencies": { 26 | "grapesjs": "^0.21.2", 27 | "grapesjs-cli": "^4.1.1" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/blocks.ts: -------------------------------------------------------------------------------- 1 | import type { Editor, BlockProperties } from 'grapesjs'; 2 | import { PluginOptions } from '.'; 3 | 4 | export default function(editor: Editor, opts: Required) { 5 | const bm = editor.BlockManager; 6 | const { category, blocks, stylePrefix, flexGrid, rowHeight, addBasicStyle } = opts; 7 | const clsRow = `${stylePrefix}row`; 8 | const clsCell = `${stylePrefix}cell`; 9 | const styleRow = flexGrid 10 | ? ` 11 | .${clsRow} { 12 | display: flex; 13 | justify-content: flex-start; 14 | align-items: stretch; 15 | flex-wrap: nowrap; 16 | padding: 10px; 17 | } 18 | @media (max-width: 768px) { 19 | .${clsRow} { 20 | flex-wrap: wrap; 21 | } 22 | }` 23 | : ` 24 | .${clsRow} { 25 | display: table; 26 | padding: 10px; 27 | width: 100%; 28 | } 29 | @media (max-width: 768px) { 30 | .${stylePrefix}cell, .${stylePrefix}cell30, .${stylePrefix}cell70 { 31 | width: 100%; 32 | display: block; 33 | } 34 | }`; 35 | const styleClm = flexGrid 36 | ? ` 37 | .${clsCell} { 38 | min-height: ${rowHeight}px; 39 | flex-grow: 1; 40 | flex-basis: 100%; 41 | }` 42 | : ` 43 | .${clsCell} { 44 | width: 8%; 45 | display: table-cell; 46 | height: ${rowHeight}px; 47 | }`; 48 | const styleClm30 = ` 49 | .${stylePrefix}cell30 { 50 | width: 30%; 51 | }`; 52 | const styleClm70 = ` 53 | .${stylePrefix}cell70 { 54 | width: 70%; 55 | }`; 56 | 57 | const step = 0.2; 58 | const minDim = 1; 59 | const currentUnit = 1; 60 | const resizerBtm: Record = { 61 | tl: 0, 62 | tc: 0, 63 | tr: 0, 64 | cl: 0, 65 | cr: 0, 66 | bl: 0, 67 | br: 0, 68 | minDim 69 | }; 70 | const resizerRight: Record = { 71 | ...resizerBtm, 72 | cr: 1, 73 | bc: 0, 74 | currentUnit, 75 | minDim, 76 | step 77 | }; 78 | 79 | // Flex elements do not react on width style change therefore I use 80 | // 'flex-basis' as keyWidth for the resizer on columns 81 | if (flexGrid) { 82 | resizerRight.keyWidth = 'flex-basis'; 83 | } 84 | 85 | const rowAttr = { 86 | class: clsRow, 87 | 'data-gjs-droppable': `.${clsCell}`, 88 | 'data-gjs-resizable': resizerBtm, 89 | 'data-gjs-name': 'Row' 90 | }; 91 | 92 | const colAttr: Record = { 93 | class: clsCell, 94 | 'data-gjs-draggable': `.${clsRow}`, 95 | 'data-gjs-resizable': resizerRight, 96 | 'data-gjs-name': 'Cell' 97 | }; 98 | 99 | if (flexGrid) { 100 | colAttr['data-gjs-unstylable'] = ['width']; 101 | colAttr['data-gjs-stylable-require'] = ['flex-basis']; 102 | } 103 | 104 | // Make row and column classes private 105 | const privateCls = [`.${clsRow}`, `.${clsCell}`]; 106 | editor.on( 107 | 'selector:add', 108 | selector => 109 | privateCls.indexOf(selector.getFullName()) >= 0 && 110 | selector.set('private', 1) 111 | ); 112 | 113 | const attrsToString = (attrs: Record) => { 114 | const result = []; 115 | 116 | for (let key in attrs) { 117 | let value = attrs[key]; 118 | const toParse = value instanceof Array || value instanceof Object; 119 | value = toParse ? JSON.stringify(value) : value; 120 | result.push(`${key}=${toParse ? `'${value}'` : `'${value}'`}`); 121 | } 122 | 123 | return result.length ? ` ${result.join(' ')}` : ''; 124 | }; 125 | 126 | const toAdd = (name: string) => blocks.indexOf(name) >= 0; 127 | const attrsRow = attrsToString(rowAttr); 128 | const attrsCell = attrsToString(colAttr); 129 | const commonBlockProps: Partial = { 130 | category, 131 | select: true, 132 | }; 133 | 134 | toAdd('column1') && 135 | bm.add('column1', { 136 | ...commonBlockProps, 137 | label: opts.labelColumn1, 138 | media: ` 139 | 140 | `, 141 | content: `
142 |
143 |
144 | ${ 145 | addBasicStyle 146 | ? `` 150 | : '' 151 | }` 152 | }); 153 | 154 | toAdd('column2') && 155 | bm.add('column2', { 156 | ...commonBlockProps, 157 | label: opts.labelColumn2, 158 | media: ` 159 | 160 | `, 161 | content: `
162 |
163 |
164 |
165 | ${ 166 | addBasicStyle 167 | ? `` 171 | : '' 172 | }` 173 | }); 174 | 175 | toAdd('column3') && 176 | bm.add('column3', { 177 | ...commonBlockProps, 178 | label: opts.labelColumn3, 179 | media: ` 180 | 181 | `, 182 | content: `
183 |
184 |
185 |
186 |
187 | ${ 188 | addBasicStyle 189 | ? `` 193 | : '' 194 | }` 195 | }); 196 | 197 | toAdd('column3-7') && 198 | bm.add('column3-7', { 199 | ...commonBlockProps, 200 | label: opts.labelColumn37, 201 | media: ` 202 | 203 | `, 204 | content: `
205 |
208 |
211 |
212 | ${ 213 | addBasicStyle 214 | ? `` 220 | : '' 221 | }` 222 | }); 223 | 224 | toAdd('text') && 225 | bm.add('text', { 226 | ...commonBlockProps, 227 | activate: true, 228 | label: opts.labelText, 229 | media: ` 230 | 231 | `, 232 | content: { 233 | type: 'text', 234 | content: 'Insert your text here', 235 | style: { padding: '10px' }, 236 | } 237 | }); 238 | 239 | toAdd('link') && 240 | bm.add('link', { 241 | ...commonBlockProps, 242 | label: opts.labelLink, 243 | media: ` 244 | 245 | `, 246 | content: { 247 | type: 'link', 248 | content: 'Link', 249 | style: { color: '#d983a6' } 250 | } 251 | }); 252 | 253 | toAdd('image') && 254 | bm.add('image', { 255 | ...commonBlockProps, 256 | activate: true, 257 | label: opts.labelImage, 258 | media: ` 259 | 260 | `, 261 | content: { 262 | style: { color: 'black' }, 263 | type: 'image', 264 | } 265 | }); 266 | 267 | toAdd('video') && 268 | bm.add('video', { 269 | ...commonBlockProps, 270 | label: opts.labelVideo, 271 | media: ` 272 | 273 | `, 274 | content: { 275 | type: 'video', 276 | src: 'img/video2.webm', 277 | style: { 278 | height: '350px', 279 | width: '615px' 280 | } 281 | } 282 | }); 283 | 284 | toAdd('map') && 285 | bm.add('map', { 286 | ...commonBlockProps, 287 | label: opts.labelMap, 288 | media: ` 289 | 290 | `, 291 | content: { 292 | type: 'map', 293 | style: { height: '350px' } 294 | } 295 | }); 296 | } 297 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import type { Plugin } from 'grapesjs'; 2 | import loadBlocks from './blocks'; 3 | 4 | export type PluginOptions = { 5 | /** 6 | * Which blocks to add. 7 | * @default ['column1', 'column2', 'column3', 'column3-7', 'text', 'link', 'image', 'video', 'map'] 8 | */ 9 | blocks?: string[]; 10 | 11 | /** 12 | * Make use of flexbox for the grid 13 | * @default false 14 | */ 15 | flexGrid?: boolean; 16 | 17 | /** 18 | * Classes prefix 19 | * @default 'gjs-' 20 | */ 21 | stylePrefix?: string; 22 | 23 | /** 24 | * Use basic CSS for blocks 25 | * @default true 26 | */ 27 | addBasicStyle?: boolean; 28 | 29 | /** 30 | * Blocks category name 31 | * @default 'Basic' 32 | */ 33 | category?: string; 34 | 35 | /** 36 | * 1 Column label 37 | * @default '1 Column' 38 | */ 39 | labelColumn1?: string; 40 | 41 | /** 42 | * 2 Columns label 43 | * @default '2 Columns' 44 | */ 45 | labelColumn2?: string; 46 | 47 | /** 48 | * 3 Columns label 49 | * @default '3 Columns' 50 | */ 51 | labelColumn3?: string; 52 | 53 | /** 54 | * 3/7 Columns label 55 | * @default '2 Columns 3/7' 56 | */ 57 | labelColumn37?: string; 58 | 59 | /** 60 | * Text label 61 | * @default 'Text' 62 | */ 63 | labelText?: string; 64 | 65 | /** 66 | * Link label 67 | * @default 'Link' 68 | */ 69 | labelLink?: string; 70 | 71 | /** 72 | * Image label 73 | * @default 'Image' 74 | */ 75 | labelImage?: string; 76 | 77 | /** 78 | * Video label 79 | * @default 'Video' 80 | */ 81 | labelVideo?: string; 82 | 83 | /** 84 | * Map label 85 | * @default 'Map' 86 | */ 87 | labelMap?: string; 88 | 89 | /** 90 | * Initial row height 91 | * @default 75 92 | */ 93 | rowHeight?: number; 94 | }; 95 | 96 | const plugin: Plugin = (editor, opts = {}) => { 97 | const config: Required = { 98 | blocks: [ 99 | 'column1', 100 | 'column2', 101 | 'column3', 102 | 'column3-7', 103 | 'text', 104 | 'link', 105 | 'image', 106 | 'video', 107 | 'map' 108 | ], 109 | flexGrid: false, 110 | stylePrefix: 'gjs-', 111 | addBasicStyle: true, 112 | category: 'Basic', 113 | labelColumn1: '1 Column', 114 | labelColumn2: '2 Columns', 115 | labelColumn3: '3 Columns', 116 | labelColumn37: '2 Columns 3/7', 117 | labelText: 'Text', 118 | labelLink: 'Link', 119 | labelImage: 'Image', 120 | labelVideo: 'Video', 121 | labelMap: 'Map', 122 | rowHeight: 75, 123 | ...opts 124 | }; 125 | 126 | loadBlocks(editor, config); 127 | }; 128 | 129 | export default plugin; 130 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./node_modules/grapesjs-cli/dist/template/tsconfig.json", 3 | "include": ["src"] 4 | } -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = ({ config }) => ({ 2 | ...config, 3 | output: { 4 | ...config.output, 5 | library: 'gjs-blocks-basic', 6 | }, 7 | }); --------------------------------------------------------------------------------