├── .eslintrc ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── app ├── create.js ├── input.js ├── install.js ├── rename.js └── validate.js ├── examples ├── 1-simple-block │ ├── .babelrc │ ├── .eslintrc │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── screenshot.png │ ├── src │ │ ├── index.js │ │ ├── scripts.js │ │ └── simple-block │ │ │ ├── index.js │ │ │ └── style.scss │ └── webpack.config.js └── 2-template-block │ ├── .babelrc │ ├── .eslintrc │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── screenshot.png │ ├── src │ ├── index.js │ ├── scripts.js │ └── template-block │ │ ├── index.js │ │ └── style.scss │ └── webpack.config.js ├── index.js └── package.json /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parserOptions": { 3 | "ecmaVersion": 2018 4 | }, 5 | "env": { 6 | "node": true, 7 | "es6": true 8 | }, 9 | "extends": "eslint:recommended", 10 | "rules": { 11 | "arrow-parens": ["warn", "as-needed"], 12 | "arrow-spacing": "warn", 13 | "block-scoped-var": "warn", 14 | "brace-style": [ "warn", "stroustrup", { 15 | "allowSingleLine": true 16 | }], 17 | 18 | "comma-spacing": "warn", 19 | "comma-style": "warn", 20 | 21 | "curly": "warn", 22 | "dot-notation": "warn", 23 | "eol-last": "error", 24 | "eqeqeq": "warn", 25 | 26 | "func-call-spacing": [ "warn", "never" ], 27 | "func-style": [ "error", "declaration", { 28 | "allowArrowFunctions": true 29 | }], 30 | "generator-star-spacing": "warn", 31 | "indent": [ "warn", 2, { 32 | "MemberExpression": 0, 33 | "SwitchCase": 1 34 | }], 35 | "jsx-quotes": "error", 36 | "new-parens": "error", 37 | 38 | "no-caller": "error", 39 | "no-console": "off", 40 | "no-duplicate-imports": "error", 41 | "no-else-return": "warn", 42 | "no-eval": "error", 43 | "no-extra-label": "warn", 44 | "no-floating-decimal": "warn", 45 | "no-lonely-if": "warn", 46 | "no-mixed-operators": "off", 47 | "no-multi-str": "error", 48 | "no-negated-in-lhs": "warn", 49 | "no-shadow": "warn", 50 | "no-undef-init": "error", 51 | "no-unused-expressions": "error", 52 | "no-useless-computed-key": "warn", 53 | "no-useless-constructor": "warn", 54 | "no-useless-rename": "warn", 55 | "no-useless-return": "warn", 56 | "no-var": "error", 57 | "no-whitespace-before-property": "warn", 58 | 59 | "object-curly-spacing": [ "error", "always" ], 60 | "prefer-const": "warn", 61 | "prefer-rest-params": "warn", 62 | "quotes": [ "warn", "single", { 63 | "allowTemplateLiterals": true 64 | }], 65 | 66 | "rest-spread-spacing": ["warn", "never"], 67 | "semi-spacing": "warn", 68 | "semi-style": [ "warn", "last" ], 69 | "semi": [ "error", "always" ], 70 | 71 | "space-before-blocks": "warn", 72 | "space-before-function-paren": "warn", 73 | "space-infix-ops": "warn", 74 | "space-in-parens": ["warn", "never"], 75 | "spaced-comment": ["warn", "always"], 76 | "template-curly-spacing": "warn", 77 | "yield-star-spacing": "warn", 78 | 79 | "valid-jsdoc": ["warn", { 80 | "requireReturn": false, 81 | "requireReturnDescription": false, 82 | "requireParamDescription": false 83 | }] 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | examples/1-simple-block/build/* 2 | examples/2-template-block/build/* 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 [Frontkom](https://www.frontkom.no/) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Create Cloud Block 2 | A boilerplate generator for building [Gutenberg Cloud](https://gutenbergcloud.org/) blocks. 3 | 4 | This generator is inspired by both [Create React App](https://github.com/facebook/create-react-app) and [Create Guten Block](https://github.com/ahmadawais/create-guten-block). However, it is only intended to serve as a boilerplate and not a zero-configuration toolkit. 5 | 6 | 7 | ## Overview 8 | 9 | ``` 10 | $ npm init cloud-block my-block 11 | $ cd my-block 12 | ``` 13 | 14 | ## Development 15 | 16 | You can now start editing the block. To test the block with [g-editor](https://github.com/front/g-editor) (a minimalist version of the Gutenberg editor) run: 17 | 18 | ``` 19 | $ npm start 20 | ``` 21 | 22 | ## Deployment 23 | 24 | Once you're ready you can follow the deployment steps. 25 | 26 | ``` 27 | $ npm run build 28 | $ npm publish 29 | ``` 30 | -------------------------------------------------------------------------------- /app/create.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const chalk = require('chalk'); 5 | 6 | const input = require('./input'); 7 | const validate = require('./validate'); 8 | const rename = require('./rename'); 9 | const install = require('./install'); 10 | 11 | 12 | // 1. Get and validate the project name 13 | const { projectName, includeEditor } = input.getProjectName(); 14 | validate.checkProjectName(projectName); 15 | 16 | 17 | // 2. Create the directory 18 | const appRoot = path.resolve(projectName); 19 | install.createDir(appRoot); 20 | 21 | 22 | // 3. Check for file conflicts 23 | validate.checkFileConflicts(appRoot); 24 | console.log(`Creating a new Cloud Block in ${chalk.green(appRoot)}`); 25 | 26 | 27 | // 4. Copy files from the example 28 | console.log('Extracting example files'); 29 | install.copyExample(appRoot); 30 | install.addGitIgnore(appRoot); 31 | 32 | 33 | // 5. Rename the project files 34 | const appName = path.basename(appRoot); 35 | 36 | rename.updatePkg(appRoot, projectName, includeEditor); 37 | rename.updateFiles(appRoot, appName); 38 | rename.renameBlock(appRoot, appName); 39 | 40 | 41 | // 6. Install packages 42 | console.log('Installing packages. This might take a couple of minutes.'); 43 | install.runNPM(appRoot); 44 | 45 | 46 | // 7. Cleanup and finish 47 | console.log(`Success! Created ${chalk.green(projectName)} at ${appRoot}`); 48 | console.log(`Inside that directory, you can run several commands:`); 49 | if(includeEditor) { 50 | console.log(); 51 | console.log(` ${chalk.cyan('npm start')}`); 52 | console.log(` Starts the development editor (no live reload currently).`); 53 | } 54 | console.log(); 55 | console.log(` ${chalk.cyan('npm run build')}`); 56 | console.log(` Bundles the app into static files for production.`); 57 | console.log(); 58 | console.log(` ${chalk.cyan('npm publish')}`); 59 | console.log(` Publish the production static files to NPM.`); 60 | console.log(); 61 | console.log(`You can start by typing:`); 62 | console.log(` ${chalk.cyan('cd')} ${chalk.green(projectName)}`); 63 | if(includeEditor) { 64 | console.log(` ${chalk.cyan('npm start')}`); 65 | } 66 | console.log(); 67 | console.log('🚀'); 68 | -------------------------------------------------------------------------------- /app/input.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chalk = require('chalk'); 4 | const minimist = require('minimist'); 5 | const pkg = require('../package.json'); 6 | 7 | 8 | module.exports = { 9 | getProjectName () { 10 | const argv = minimist(process.argv.slice(2), { 11 | boolean: true, 12 | }); 13 | const projectName = argv._[0]; 14 | const includeEditor = !argv['exclude-editor']; 15 | 16 | if(argv.version || argv.v) { 17 | console.log(pkg.version); 18 | process.exit(0); 19 | } 20 | 21 | if(argv.help || argv.h) { 22 | console.log(` Only ${chalk.green('')} is required.`); 23 | console.log(); 24 | console.log('For example:'); 25 | console.log(` ${chalk.cyan(pkg.name)} ${chalk.green('my-block')}`); 26 | console.log(); 27 | process.exit(0); 28 | } 29 | 30 | if (!projectName) { 31 | console.error('Please specify the project directory:'); 32 | console.log(` ${chalk.cyan(pkg.name)} ${chalk.green('')}`); 33 | console.log(); 34 | console.log('For example:'); 35 | console.log(` ${chalk.cyan(pkg.name)} ${chalk.green('my-block')}`); 36 | console.log(); 37 | process.exit(1); 38 | } 39 | 40 | return { projectName, includeEditor }; 41 | } 42 | }; 43 | -------------------------------------------------------------------------------- /app/install.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs-extra'); 4 | const path = require('path'); 5 | const spawn = require('cross-spawn'); 6 | 7 | 8 | module.exports = { 9 | createDir (root) { 10 | fs.ensureDirSync(root); 11 | }, 12 | 13 | copyExample (root) { 14 | const example = path.resolve(__dirname, '../examples/1-simple-block'); 15 | fs.copySync(example, root); 16 | }, 17 | 18 | addGitIgnore (root) { 19 | const data = ['build', 'node_modules', 'package-lock.json', '.DS_Store', ''].join('\n'); 20 | fs.writeFileSync(path.resolve(root, '.gitignore'), data); 21 | }, 22 | 23 | runNPM (root) { 24 | return spawn.sync( 25 | 'npm', 26 | ['--prefix', root, 'install'], 27 | { stdio: 'inherit' } 28 | ); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /app/rename.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs-extra'); 4 | const path = require('path'); 5 | 6 | const example = 'simple-block'; 7 | const exFiles = [ 8 | 'src/index.js', 9 | 'src/simple-block/index.js', 10 | 'src/simple-block/style.scss', 11 | ]; 12 | 13 | 14 | function replaceStringInFile (root, file, oldStr, newStr) { 15 | let data = fs.readFileSync(path.resolve(root, file)).toString(); 16 | data = data.replace(oldStr, newStr); 17 | fs.writeFileSync(path.resolve(root, file), data); 18 | } 19 | 20 | 21 | module.exports = { 22 | updatePkg (root, project, includeEditor = true) { 23 | const pkg = require(path.resolve(root, 'package.json')); 24 | pkg.name = project; 25 | pkg.files = ['build/', 'screenshot.png']; 26 | if(!includeEditor) { 27 | delete pkg.devDependencies['@frontkom/g-editor']; 28 | delete pkg.scripts.start; 29 | } 30 | fs.writeFileSync(path.resolve(root, 'package.json'), JSON.stringify(pkg, null, 2)); 31 | }, 32 | 33 | updateFiles (root, app) { 34 | exFiles.forEach(file => replaceStringInFile(root, file, example, app)); 35 | }, 36 | 37 | renameBlock (root, app) { 38 | fs.moveSync(path.resolve(root, `src/${example}`), path.resolve(root, `src/${app}`)); 39 | }, 40 | }; 41 | -------------------------------------------------------------------------------- /app/validate.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs-extra'); 4 | const chalk = require('chalk'); 5 | const validatePkgName = require('validate-npm-package-name'); 6 | 7 | 8 | module.exports = { 9 | checkProjectName (projectName) { 10 | const valid = validatePkgName(projectName); 11 | if(!valid.validForNewPackages) { 12 | console.error(`Sorry, ${chalk.red(projectName)} is not a valid NPM project name`); 13 | console.log(); 14 | if(valid.errors) { 15 | console.log('Error:'); 16 | console.log(` ${chalk.yellow(valid.errors.join('; '))}`); 17 | console.log(); 18 | } 19 | if(valid.warnings) { 20 | console.log('Warning:'); 21 | console.log(` ${chalk.yellow(valid.warnings.join('; '))}`); 22 | console.log(); 23 | } 24 | process.exit(1); 25 | } 26 | 27 | return true; 28 | }, 29 | 30 | checkFileConflicts (appRoot) { 31 | const appFiles = [ 32 | '.babelrc', 33 | '.eslintrc', 34 | '.gitignore', 35 | 'README.md', 36 | 'package.json', 37 | 'screenshot.png', 38 | 'src', 39 | 'webpack.config.js', 40 | ]; 41 | 42 | const conflicts = fs.readdirSync(appRoot).filter(file => appFiles.includes(file)); 43 | if(conflicts.length > 0) { 44 | console.log(`The directory ${chalk.green(appRoot)} contains files that could conflict:`); 45 | for (const file of conflicts) { 46 | console.log(` ${file}`); 47 | } 48 | console.log(); 49 | console.log('Either try using a new directory name, or remove the files listed above.'); 50 | process.exit(1); 51 | } 52 | 53 | return true; 54 | } 55 | }; 56 | -------------------------------------------------------------------------------- /examples/1-simple-block/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-react"] 3 | } 4 | -------------------------------------------------------------------------------- /examples/1-simple-block/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": [ 4 | "eslint:recommended", 5 | "plugin:react/recommended" 6 | ], 7 | "env": { 8 | "browser": false, 9 | "es6": true, 10 | "node": true 11 | }, 12 | "parserOptions": { 13 | "sourceType": "module", 14 | "ecmaFeatures": { 15 | "jsx": true 16 | } 17 | }, 18 | "globals": { 19 | "window": true, 20 | "document": true, 21 | "localStorage": true, 22 | "sessionStorage": true, 23 | "XMLHttpRequest": true 24 | }, 25 | "plugins": [ 26 | "react" 27 | ], 28 | "settings": { 29 | "react": { 30 | "pragma": "React" 31 | } 32 | }, 33 | "rules": { 34 | "arrow-parens": ["warn", "as-needed"], 35 | "arrow-spacing": "warn", 36 | "block-scoped-var": "warn", 37 | "brace-style": [ "warn", "stroustrup", { 38 | "allowSingleLine": true 39 | }], 40 | 41 | "comma-dangle": [ "warn", "always-multiline" ], 42 | "comma-spacing": "warn", 43 | "comma-style": "warn", 44 | 45 | "curly": "warn", 46 | "dot-notation": "warn", 47 | "eol-last": "error", 48 | "eqeqeq": "warn", 49 | 50 | "func-call-spacing": [ "warn", "never" ], 51 | "func-style": [ "error", "declaration", { 52 | "allowArrowFunctions": true 53 | }], 54 | "generator-star-spacing": "warn", 55 | "indent": [ "warn", 2, { 56 | "MemberExpression": 0, 57 | "SwitchCase": 1 58 | }], 59 | "jsx-quotes": "error", 60 | "new-parens": "error", 61 | 62 | "no-caller": "error", 63 | "no-console": "off", 64 | "no-duplicate-imports": "error", 65 | "no-else-return": "warn", 66 | "no-eval": "error", 67 | "no-extra-label": "warn", 68 | "no-floating-decimal": "warn", 69 | "no-lonely-if": "warn", 70 | "no-mixed-operators": "warn", 71 | "no-multi-str": "error", 72 | "no-negated-in-lhs": "warn", 73 | "no-shadow": "warn", 74 | "no-undef-init": "error", 75 | "no-unused-expressions": "error", 76 | "no-useless-computed-key": "warn", 77 | "no-useless-constructor": "warn", 78 | "no-useless-rename": "warn", 79 | "no-useless-return": "warn", 80 | "no-var": "error", 81 | "no-whitespace-before-property": "warn", 82 | 83 | "object-curly-spacing": [ "error", "always" ], 84 | "prefer-const": "warn", 85 | "prefer-rest-params": "warn", 86 | "quote-props": [ "error", "as-needed" ], 87 | "quotes": [ "warn", "single", { 88 | "allowTemplateLiterals": true 89 | }], 90 | 91 | "rest-spread-spacing": ["warn", "never"], 92 | "semi-spacing": "warn", 93 | "semi-style": [ "warn", "last" ], 94 | "semi": [ "error", "always" ], 95 | 96 | "space-before-blocks": "warn", 97 | "space-before-function-paren": "warn", 98 | "space-infix-ops": "warn", 99 | "space-in-parens": ["warn", "never"], 100 | "spaced-comment": ["warn", "always"], 101 | "template-curly-spacing": "warn", 102 | "yield-star-spacing": "warn", 103 | 104 | "valid-jsdoc": ["warn", { 105 | "requireReturn": false, 106 | "requireReturnDescription": false, 107 | "requireParamDescription": false 108 | }], 109 | 110 | "react/display-name": "off", 111 | "react/jsx-curly-spacing": "off", 112 | "react/jsx-equals-spacing": "error", 113 | "react/jsx-indent": [ "warn", 2 ], 114 | "react/jsx-indent-props": [ "warn", 2 ], 115 | "react/jsx-uses-react": "error", 116 | "react/jsx-uses-vars": "error", 117 | "react/jsx-tag-spacing": "warn", 118 | "react/no-children-prop": "off", 119 | "react/no-find-dom-node": "warn", 120 | "react/no-unescaped-entities": "off", 121 | "react/prop-types": "off" 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /examples/1-simple-block/.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | node_modules 3 | package-lock.json 4 | -------------------------------------------------------------------------------- /examples/1-simple-block/README.md: -------------------------------------------------------------------------------- 1 | # Simple Block 2 | > This project was bootstrapped with [Create Cloud Block](https://github.com/front/create-cloud-block). 3 | 4 | 5 | Below you will find some information on how to run scripts. 6 | 7 | ## `npm start` 8 | - Use to compile and run the block in development mode. 9 | - Live reload is not supported currently. 10 | 11 | 12 | ## `npm run build` 13 | - Use to build production code for your block inside `build` folder. 14 | 15 | 16 | ## `npm run deploy` 17 | - Use to publish the block to NPM 18 | - It will become available in Gutenberg Cloud in a few minutes 19 | -------------------------------------------------------------------------------- /examples/1-simple-block/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simple-block", 3 | "version": "1.0.0", 4 | "description": "A custom block for Gutenberg Cloud", 5 | "main": "build/index.js", 6 | "gutenbergCloud": { 7 | "js": "build/index.js", 8 | "css": "build/style.css", 9 | "screenshot": "screenshot.png", 10 | "name": "My Custom Block" 11 | }, 12 | "scripts": { 13 | "build": "NODE_ENV=production webpack", 14 | "start": "npm run build && BLOCK_DIR=$PWD npm --prefix node_modules/@frontkom/g-editor start", 15 | "deploy": "npm run build && npm publish --access public" 16 | }, 17 | "keywords": [ 18 | "gutenberg", 19 | "gutenberg-cloud" 20 | ], 21 | "peerDependencies": { 22 | "react": "^16.8.0" 23 | }, 24 | "devDependencies": { 25 | "@babel/core": "^7.8.4", 26 | "@babel/preset-react": "^7.8.3", 27 | "@frontkom/g-editor": "^1.4.1", 28 | "babel-eslint": "^10.0.3", 29 | "babel-loader": "^8.0.6", 30 | "clean-webpack-plugin": "^3.0.0", 31 | "css-loader": "^3.4.2", 32 | "eslint": "^6.8.0", 33 | "eslint-plugin-react": "^7.18.3", 34 | "extract-text-webpack-plugin": "4.0.0-beta.0", 35 | "node-sass": "^4.13.1", 36 | "postcss-loader": "^3.0.0", 37 | "sass-loader": "^8.0.2", 38 | "url-loader": "^3.0.0", 39 | "webpack": "^4.41.5", 40 | "webpack-cli": "^3.3.10" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /examples/1-simple-block/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/front/create-cloud-block/071f7fedae0f2791ebe3e2228e87163a2abcfddb/examples/1-simple-block/screenshot.png -------------------------------------------------------------------------------- /examples/1-simple-block/src/index.js: -------------------------------------------------------------------------------- 1 | 2 | import { blocks, data, i18n } from 'wp'; 3 | const { registerBlockType } = blocks; 4 | const { dispatch, select } = data; 5 | const { __ } = i18n; 6 | 7 | // TODO: Import each block herer 8 | import * as block1 from './simple-block'; 9 | 10 | 11 | // Category name and slug 12 | const category = { 13 | slug: 'cloudblocks', // needs to match the css class of the block container: ".wp-block-cloudblocks-[block-name]" 14 | title: __('Cloud Blocks'), 15 | }; 16 | 17 | // Register the new category and blocks 18 | export function registerBlocks () { 19 | // Add the new category to the list 20 | const currentCategories = select('core/blocks').getCategories().filter(item => item.slug !== category.slug); 21 | dispatch('core/blocks').setCategories([ category, ...currentCategories ]); 22 | 23 | // TODO: Register each block 24 | registerBlockType(`${category.slug}/${block1.name}`, { category: category.slug, ...block1.settings }); 25 | } 26 | 27 | registerBlocks(); 28 | -------------------------------------------------------------------------------- /examples/1-simple-block/src/scripts.js: -------------------------------------------------------------------------------- 1 | // Place your frontend scripts here 2 | 3 | /* 4 | If you use frontend scripts, please uncomment the 5 | corresponding line on "webpack.config.js" 6 | */ 7 | -------------------------------------------------------------------------------- /examples/1-simple-block/src/simple-block/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | import React from 'react'; 5 | import { element, i18n, components, editor } from 'wp'; 6 | 7 | /** 8 | * Internal dependencies 9 | */ 10 | import './style.scss'; 11 | 12 | 13 | const { Fragment } = element; 14 | const { __ } = i18n; 15 | 16 | // TODO: Chooose components for the sidebar settings 17 | const { PanelBody, FontSizePicker } = components; 18 | const { InspectorControls, PanelColorSettings, RichText } = editor; 19 | 20 | // TODO: Add here the editable block attributes 21 | const BLOCK_ATTRIBUTES = { 22 | title: { 23 | type: 'array', 24 | source: 'children', 25 | selector: 'h1', 26 | default: 'My Custom Block', 27 | }, 28 | fontSize: { 29 | type: 'number', 30 | default: 56, 31 | }, 32 | fontColor: { 33 | type: 'string', 34 | }, 35 | backgroundColor: { 36 | type: 'string', 37 | }, 38 | }; 39 | 40 | const FONT_SIZES = [ 41 | { name: 'small', shortName: 'S', size: 28 }, 42 | { name: 'regular', shortName: 'M', size: 40 }, 43 | { name: 'large', shortName: 'L', size: 56 }, 44 | { name: 'larger', shortName: 'XL', size: 72 }, 45 | ]; 46 | 47 | 48 | export const name = 'simple-block'; 49 | 50 | export const settings = { 51 | title: __('My Custom Block'), 52 | 53 | description: __('A custom block for Gutenberg Cloud'), 54 | 55 | icon: 'cover-image', 56 | 57 | attributes: BLOCK_ATTRIBUTES, 58 | 59 | edit ({ attributes, className, setAttributes }) { 60 | const { title, fontSize, fontColor, backgroundColor } = attributes; 61 | const containerStyle = { 62 | backgroundColor, 63 | }; 64 | const titleStyle = { 65 | fontSize: fontSize && `${fontSize}px`, 66 | color: fontColor, 67 | }; 68 | 69 | return ( 70 | 71 | {/* Block markup (main editor) */} 72 |
73 | setAttributes({ title: value }) } 77 | inlineToolbar 78 | /> 79 |
80 | 81 | 82 | {/* Block settings (sidebar) */} 83 | 84 | setAttributes({ fontSize: value }) } 87 | /> 88 | 89 | 90 | setAttributes({ fontColor: value }), 96 | label: __('Font Color'), 97 | }, { 98 | value: backgroundColor, 99 | onChange: value => setAttributes({ backgroundColor: value }), 100 | label: __('Background Color'), 101 | }, 102 | ] } /> 103 | 104 |
105 | ); 106 | }, 107 | 108 | save ({ attributes, className }) { 109 | const { title, fontSize, fontColor, backgroundColor } = attributes; 110 | const containerStyle = { 111 | backgroundColor, 112 | }; 113 | const titleStyle = { 114 | fontSize: fontSize && `${fontSize}px`, 115 | color: fontColor, 116 | }; 117 | 118 | return ( 119 |
120 | 121 |
122 | ); 123 | }, 124 | }; 125 | -------------------------------------------------------------------------------- /examples/1-simple-block/src/simple-block/style.scss: -------------------------------------------------------------------------------- 1 | 2 | // The class name needs to match the names of the category and block 3 | .wp-block-cloudblocks-simple-block { 4 | padding: 60px 20px; 5 | text-align: center; 6 | 7 | h1 { 8 | margin: 0; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/1-simple-block/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { CleanWebpackPlugin } = require('clean-webpack-plugin'); 3 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 4 | 5 | const cleanBuild = new CleanWebpackPlugin({ 6 | cleanOnceBeforeBuildPatterns: ['build'], 7 | }); 8 | const blockCSS = new ExtractTextPlugin('style.css'); 9 | const editorCSS = new ExtractTextPlugin('editor.css'); 10 | 11 | module.exports = { 12 | entry: { 13 | index: './src/index.js', 14 | // scripts: './src/scripts.js', 15 | }, 16 | externals: { 17 | react: 'React', 18 | wp: 'wp', 19 | }, 20 | output: { 21 | filename: '[name].js', 22 | path: path.resolve(__dirname, 'build'), 23 | libraryTarget: 'umd', 24 | }, 25 | module: { 26 | rules: [ 27 | { 28 | test: /\.js$/, 29 | exclude: /node_modules/, 30 | use: 'babel-loader', 31 | }, 32 | { 33 | test: /\.s?css$/, 34 | exclude: [/node_modules/, /editor\.s?css$/], 35 | use: blockCSS.extract(['css-loader', 'sass-loader']), 36 | }, 37 | { 38 | test: /editor\.s?css$/, 39 | exclude: /node_modules/, 40 | use: editorCSS.extract(['css-loader', 'sass-loader']), 41 | }, 42 | { 43 | test: /\.(png|svg|jpg|gif)$/, 44 | loader: 'url-loader', 45 | }, 46 | ], 47 | }, 48 | mode: 'production', 49 | plugins: [ 50 | cleanBuild, 51 | blockCSS, 52 | editorCSS, 53 | ], 54 | }; 55 | -------------------------------------------------------------------------------- /examples/2-template-block/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-react"] 3 | } 4 | 5 | -------------------------------------------------------------------------------- /examples/2-template-block/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": [ 4 | "eslint:recommended", 5 | "plugin:react/recommended" 6 | ], 7 | "env": { 8 | "browser": false, 9 | "es6": true, 10 | "node": true 11 | }, 12 | "parserOptions": { 13 | "sourceType": "module", 14 | "ecmaFeatures": { 15 | "jsx": true 16 | } 17 | }, 18 | "globals": { 19 | "window": true, 20 | "document": true, 21 | "localStorage": true, 22 | "sessionStorage": true, 23 | "XMLHttpRequest": true 24 | }, 25 | "plugins": [ 26 | "react" 27 | ], 28 | "settings": { 29 | "react": { 30 | "pragma": "React" 31 | } 32 | }, 33 | "rules": { 34 | "arrow-parens": ["warn", "as-needed"], 35 | "arrow-spacing": "warn", 36 | "block-scoped-var": "warn", 37 | "brace-style": [ "warn", "stroustrup", { 38 | "allowSingleLine": true 39 | }], 40 | 41 | "comma-dangle": [ "warn", "always-multiline" ], 42 | "comma-spacing": "warn", 43 | "comma-style": "warn", 44 | 45 | "curly": "warn", 46 | "dot-notation": "warn", 47 | "eol-last": "error", 48 | "eqeqeq": "warn", 49 | 50 | "func-call-spacing": [ "warn", "never" ], 51 | "func-style": [ "error", "declaration", { 52 | "allowArrowFunctions": true 53 | }], 54 | "generator-star-spacing": "warn", 55 | "indent": [ "warn", 2, { 56 | "MemberExpression": 0, 57 | "SwitchCase": 1 58 | }], 59 | "jsx-quotes": "error", 60 | "new-parens": "error", 61 | 62 | "no-caller": "error", 63 | "no-console": "off", 64 | "no-duplicate-imports": "error", 65 | "no-else-return": "warn", 66 | "no-eval": "error", 67 | "no-extra-label": "warn", 68 | "no-floating-decimal": "warn", 69 | "no-lonely-if": "warn", 70 | "no-mixed-operators": "warn", 71 | "no-multi-str": "error", 72 | "no-negated-in-lhs": "warn", 73 | "no-shadow": "warn", 74 | "no-undef-init": "error", 75 | "no-unused-expressions": "error", 76 | "no-useless-computed-key": "warn", 77 | "no-useless-constructor": "warn", 78 | "no-useless-rename": "warn", 79 | "no-useless-return": "warn", 80 | "no-var": "error", 81 | "no-whitespace-before-property": "warn", 82 | 83 | "object-curly-spacing": [ "error", "always" ], 84 | "prefer-const": "warn", 85 | "prefer-rest-params": "warn", 86 | "quote-props": [ "error", "as-needed" ], 87 | "quotes": [ "warn", "single", { 88 | "allowTemplateLiterals": true 89 | }], 90 | 91 | "rest-spread-spacing": ["warn", "never"], 92 | "semi-spacing": "warn", 93 | "semi-style": [ "warn", "last" ], 94 | "semi": [ "error", "always" ], 95 | 96 | "space-before-blocks": "warn", 97 | "space-before-function-paren": "warn", 98 | "space-infix-ops": "warn", 99 | "space-in-parens": ["warn", "never"], 100 | "spaced-comment": ["warn", "always"], 101 | "template-curly-spacing": "warn", 102 | "yield-star-spacing": "warn", 103 | 104 | "valid-jsdoc": ["warn", { 105 | "requireReturn": false, 106 | "requireReturnDescription": false, 107 | "requireParamDescription": false 108 | }], 109 | 110 | "react/display-name": "off", 111 | "react/jsx-curly-spacing": "off", 112 | "react/jsx-equals-spacing": "error", 113 | "react/jsx-indent": [ "warn", 2 ], 114 | "react/jsx-indent-props": [ "warn", 2 ], 115 | "react/jsx-uses-react": "error", 116 | "react/jsx-uses-vars": "error", 117 | "react/jsx-tag-spacing": "warn", 118 | "react/no-children-prop": "off", 119 | "react/no-find-dom-node": "warn", 120 | "react/no-unescaped-entities": "off", 121 | "react/prop-types": "off" 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /examples/2-template-block/.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | node_modules 3 | package-lock.json 4 | -------------------------------------------------------------------------------- /examples/2-template-block/README.md: -------------------------------------------------------------------------------- 1 | # Template Block 2 | > This project was bootstrapped with [Create Cloud Block](https://github.com/front/create-cloud-block). 3 | 4 | 5 | Below you will find some information on how to run scripts. 6 | 7 | ## `npm start` 8 | - Use to compile and run the block in development mode. 9 | - Live reload is not supported currently. 10 | 11 | 12 | ## `npm run build` 13 | - Use to build production code for your block inside `build` folder. 14 | 15 | 16 | ## `npm run deploy` 17 | - Use to publish the block to NPM 18 | - It will become available in Gutenberg Cloud in a few minutes 19 | -------------------------------------------------------------------------------- /examples/2-template-block/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "template-block", 3 | "version": "1.0.0", 4 | "description": "A custom block for Gutenberg Cloud", 5 | "main": "build/index.js", 6 | "gutenbergCloud": { 7 | "js": "build/index.js", 8 | "css": "build/style.css", 9 | "screenshot": "screenshot.png", 10 | "name": "My Custom Block" 11 | }, 12 | "scripts": { 13 | "build": "NODE_ENV=production webpack", 14 | "start": "npm run build && BLOCK_DIR=$PWD npm --prefix node_modules/@frontkom/g-editor start", 15 | "deploy": "npm run build && npm publish --access public" 16 | }, 17 | "keywords": [ 18 | "gutenberg", 19 | "gutenberg-cloud" 20 | ], 21 | "peerDependencies": { 22 | "react": "^16.8.0" 23 | }, 24 | "devDependencies": { 25 | "@babel/core": "^7.8.4", 26 | "@babel/preset-react": "^7.8.3", 27 | "@frontkom/g-editor": "^1.4.1", 28 | "babel-eslint": "^10.0.3", 29 | "babel-loader": "^8.0.6", 30 | "clean-webpack-plugin": "^3.0.0", 31 | "css-loader": "^3.4.2", 32 | "eslint": "^6.8.0", 33 | "eslint-plugin-react": "^7.18.3", 34 | "extract-text-webpack-plugin": "4.0.0-beta.0", 35 | "node-sass": "^4.13.1", 36 | "postcss-loader": "^3.0.0", 37 | "sass-loader": "^8.0.2", 38 | "url-loader": "^3.0.0", 39 | "webpack": "^4.41.5", 40 | "webpack-cli": "^3.3.10" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /examples/2-template-block/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/front/create-cloud-block/071f7fedae0f2791ebe3e2228e87163a2abcfddb/examples/2-template-block/screenshot.png -------------------------------------------------------------------------------- /examples/2-template-block/src/index.js: -------------------------------------------------------------------------------- 1 | 2 | import { blocks, data, i18n } from 'wp'; 3 | const { registerBlockType } = blocks; 4 | const { dispatch, select } = data; 5 | const { __ } = i18n; 6 | 7 | // TODO: Import each block herer 8 | import * as block1 from './template-block'; 9 | 10 | 11 | // TODO: Set category name and slug 12 | const category = { 13 | slug: 'cloudblocks', // needs to match the css class of the block container: ".wp-block-cloudblocks-[block-name]" 14 | title: __('Cloud Blocks'), 15 | }; 16 | 17 | // Register the new category and blocks 18 | export function registerBlocks () { 19 | // Add the new category to the list 20 | const currentCategories = select('core/blocks').getCategories().filter(item => item.slug !== category.slug); 21 | dispatch('core/blocks').setCategories([ category, ...currentCategories ]); 22 | 23 | // TODO: Register each block 24 | registerBlockType(`${category.slug}/${block1.name}`, { category: category.slug, ...block1.settings }); 25 | } 26 | 27 | registerBlocks(); 28 | -------------------------------------------------------------------------------- /examples/2-template-block/src/scripts.js: -------------------------------------------------------------------------------- 1 | // Place your frontend scripts here 2 | 3 | /* 4 | If you use frontend scripts, please uncomment the 5 | corresponding line on "webpack.config.js" 6 | */ 7 | -------------------------------------------------------------------------------- /examples/2-template-block/src/template-block/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | import React from 'react'; 5 | import { element, i18n, editor } from 'wp'; 6 | 7 | /** 8 | * Internal dependencies 9 | */ 10 | import './style.scss'; 11 | 12 | 13 | const { Fragment } = element; 14 | const { __ } = i18n; 15 | 16 | // TODO: Chooose components for the sidebar settings 17 | const { InspectorControls, PanelColorSettings, InnerBlocks } = editor; 18 | 19 | // TODO: Add here the editable block attributes 20 | const BLOCK_ATTRIBUTES = { 21 | backgroundColor: { 22 | type: 'string', 23 | }, 24 | }; 25 | 26 | // TODO: Define a template of the inner blocks 27 | const TEMPLATE = [ 28 | ['core/heading', { 29 | placeholder: 'Title', 30 | content: 'My Custom Block', 31 | level: 1, 32 | }], 33 | ]; 34 | 35 | 36 | export const name = 'template-block'; 37 | 38 | export const settings = { 39 | title: __('My Template Block'), 40 | 41 | description: __('A template block for Gutenberg Cloud'), 42 | 43 | icon: 'cover-image', 44 | 45 | attributes: BLOCK_ATTRIBUTES, 46 | 47 | edit ({ attributes, className, setAttributes }) { 48 | const { backgroundColor } = attributes; 49 | const containerStyle = { 50 | backgroundColor, 51 | }; 52 | 53 | return ( 54 | 55 | {/* Block markup (main editor) */} 56 |
57 | 58 |
59 | 60 | 61 | {/* Block settings (sidebar) */} 62 | setAttributes({ backgroundColor: value }), 68 | label: __('Background Color'), 69 | }, 70 | ] } /> 71 | 72 |
73 | ); 74 | }, 75 | 76 | save ({ attributes, className }) { 77 | const { backgroundColor } = attributes; 78 | const containerStyle = { 79 | backgroundColor, 80 | }; 81 | 82 | return ( 83 |
84 | 85 |
86 | ); 87 | }, 88 | }; 89 | -------------------------------------------------------------------------------- /examples/2-template-block/src/template-block/style.scss: -------------------------------------------------------------------------------- 1 | 2 | // The class name needs to match the names of the category and block 3 | .wp-block-cloudblocks-template-block { 4 | padding: 60px 20px; 5 | text-align: center; 6 | } 7 | -------------------------------------------------------------------------------- /examples/2-template-block/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { CleanWebpackPlugin } = require('clean-webpack-plugin'); 3 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 4 | 5 | const cleanBuild = new CleanWebpackPlugin({ 6 | cleanOnceBeforeBuildPatterns: ['build'], 7 | }); 8 | const blockCSS = new ExtractTextPlugin('style.css'); 9 | const editorCSS = new ExtractTextPlugin('editor.css'); 10 | 11 | module.exports = { 12 | entry: { 13 | index: './src/index.js', 14 | // scripts: './src/scripts.js', 15 | }, 16 | externals: { 17 | react: 'React', 18 | wp: 'wp', 19 | }, 20 | output: { 21 | filename: '[name].js', 22 | path: path.resolve(__dirname, 'build'), 23 | libraryTarget: 'umd', 24 | }, 25 | module: { 26 | rules: [ 27 | { 28 | test: /\.js$/, 29 | exclude: /node_modules/, 30 | use: 'babel-loader', 31 | }, 32 | { 33 | test: /\.s?css$/, 34 | exclude: [/node_modules/, /editor\.s?css$/], 35 | use: blockCSS.extract(['css-loader', 'sass-loader']), 36 | }, 37 | { 38 | test: /editor\.s?css$/, 39 | exclude: /node_modules/, 40 | use: editorCSS.extract(['css-loader', 'sass-loader']), 41 | }, 42 | { 43 | test: /\.(png|svg|jpg|gif)$/, 44 | loader: 'url-loader', 45 | }, 46 | ], 47 | }, 48 | mode: 'production', 49 | plugins: [ 50 | cleanBuild, 51 | blockCSS, 52 | editorCSS, 53 | ], 54 | }; 55 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | const chalk = require('chalk'); 5 | const currentNodeVersion = process.versions.node; 6 | const semver = currentNodeVersion.split('.'); 7 | const major = semver[0]; 8 | 9 | if(major < 8) { 10 | console.error( 11 | chalk.red( 12 | 'You are running Node ' + currentNodeVersion + '.\n' + 13 | 'Create Cloud Block requires Node 8 or higher. \n' + 14 | 'Please update your version of Node.' 15 | ) 16 | ); 17 | process.exit(1); 18 | } 19 | 20 | process.on('unhandledRejection', err => { 21 | throw err; 22 | }); 23 | 24 | require('./app/create'); 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create-cloud-block", 3 | "version": "0.7.3", 4 | "description": "A boilerplate generator for building Gutenberg Cloud blocks", 5 | "main": "index.js", 6 | "scripts": { 7 | "create-cloud-block": "node ./index.js" 8 | }, 9 | "bin": { 10 | "create-cloud-block": "./index.js" 11 | }, 12 | "author": { 13 | "name": "Frontkom", 14 | "email": "webadmin@front.no", 15 | "url": "https://www.frontkom.no/" 16 | }, 17 | "license": "MIT", 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/front/create-cloud-block.git" 21 | }, 22 | "bugs": { 23 | "url": "https://github.com/front/create-cloud-block/issues" 24 | }, 25 | "homepage": "https://github.com/front/create-cloud-block#readme", 26 | "keywords": [ 27 | "gutenberg", 28 | "create-app" 29 | ], 30 | "dependencies": { 31 | "chalk": "^3.0.0", 32 | "cross-spawn": "^7.0.1", 33 | "fs-extra": "^8.1.0", 34 | "minimist": "^1.2.0", 35 | "validate-npm-package-name": "^3.0.0" 36 | }, 37 | "devDependencies": { 38 | "eslint": "^6.8.0" 39 | } 40 | } 41 | --------------------------------------------------------------------------------