Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Creating a Gutenberg block

Elio
November 03, 2018

Creating a Gutenberg block

Presentation for WordCamp Portland Maine 2018
Goes through the essential code needed to create a Gutenberg, as well as the tooling to make the process scalable.

Elio

November 03, 2018
Tweet

More Decks by Elio

Other Decks in Programming

Transcript

  1. const ElioRivero = { work: { company: 'Automattic' division: 'Jetpack',

    position: 'Code Wrangler' }, instagram: 'eliorivero', blog: 'elio.blog', indentsWith: 'tabs', likes: [ 'photography', 'chocolate', 'skyrim' ], }; Elio Rivero
  2. Gutenberg – Content [ { type: 'core/cover-image', attributes: { url:

    'image.jpg', align: 'full', hasParallax: false, hasBackgroundDim: true }, children: [ "Gutenberg content is not HTML" ] }, { type: 'core/paragraph', children: [ "Gutenberg content is an object tree" ] } ]
  3. { "name": "gutenbricks-fold", "version": "1.0.0", "description": "Blocks for Gutenberg", "repository":

    "https://github.com/eliorivero/gutenbricks", "author": "Elio Rivero", "license": "GPL-2.0+", "devDependencies": { "babel-core": "^6.25.0", "babel-loader": "^7.1.1", "babel-plugin-transform-react-jsx": "^6.24.1", "babel-preset-env": "^1.6.0", "cross-env": "^5.0.1", "webpack": "^3.1.0" }, "scripts": { "build": "cross-env BABEL_ENV=default NODE_ENV=production webpack", "watch": "cross-env BABEL_ENV=default webpack --watch" } } package.json
  4. { "presets": [ [ "env", { "modules": false, "targets": {

    "browsers": [ "last 2 Chrome versions", "last 2 Firefox versions", "last 2 Safari versions", "last 2 iOS versions", "last 1 Android version", "last 1 ChromeAndroid version", "ie 11" ] } } ] ], "plugins": [ [ "transform-react-jsx", { "pragma": "wp.element.createElement" } ] ] } .babelrc
  5. <p className="some-css-class"> { __( 'Some text' ) } </p> {

    wp.element.createElement( 'p', { className: 'some-css-class', }, __( 'Some text' ) ) } webpack.config.js ESNext ES5 babeljs.io/docs/en/babel-plugin-transform-react-jsx
  6. var webpack = require( 'webpack' ), NODE_ENV = process.env.NODE_ENV ||

    'development', path = require( 'path' ), webpackConfig = { entry: { fold: './fold/block.js' }, output: { path: path.join( __dirname, 'build' ), filename: '[name].build.js' }, module: { loaders: [ { test: /.js$/, loader: 'babel-loader', exclude: /node_modules/ } ] }, plugins: 'production' === NODE_ENV ? [ new webpack.optimize.UglifyJsPlugin() ] : [] }; module.exports = webpackConfig; webpack.config.js
  7. <?php add_action( 'init', 'gutenbricks_block_init' ); function gutenbricks_block_init() { wp_register_style( 'gutenbricks-fold-style',

    plugins_url( 'fold/style.css', __FILE__ ) ); wp_register_style( 'gutenbricks-fold-editor', plugins_url( 'fold/editor.css', __FILE__ ), array( 'wp-edit-blocks' ) ); wp_register_script( 'gutenbricks-fold', plugins_url( 'build/fold.build.js', __FILE__ ), array( 'wp-blocks', 'wp-i18n', 'wp-element' ) ); register_block_type( 'gutenbricks/fold', array( 'style' => 'gutenbricks-fold-style', 'editor_style' => 'gutenbricks-fold-editor', 'editor_script' => 'gutenbricks-fold', ) ); } block.php
  8. const { __ } = wp.i18n; wp.blocks.registerBlockType( 'gutenbricks/fold', { title:

    __( 'Fold' ), icon: 'sort', category: 'layout', attributes: { title: { source: 'html', selector: 'label', type: 'string', }, reveal: { source: 'html', selector: '.gutenbricks-fold-reveal', multiline: 'p', default: '', }, id: { type: 'string' }, }, edit: props => ( <div className="gutenbricks-fold">/* Block UI */</div> ) save: props => ( <div className="gutenbricks-fold">/* HTML to save */</div> ) } ); block.js
  9. { name: 'gutenbricks/fold', isSelected: false, attributes: { title: 'The visible

    text', reveal: '<p>The text to be revealed</p>', id: 'gutenbricks-58c58d4e-3b84-4e80-ab5d-ff4d4b351cfc' }, clientId: '25a1319c-8f9c-41b2-b691-c13887779883', isSelected: true, isSelectionEnabled: true, className: 'wp-block-gutenbricks-fold', insertBlocksAfter: ƒ (), mergeBlocks: ƒ (), onReplace: ƒ (), setAttributes: ƒ (), toggleSelection: ƒ (), } { attributes: { title: 'The visible text', reveal: '<p>The text to be revealed</p>', id: 'gutenbricks-58c58d4e-3b84-4e80-ab5d-ff4d4b351cfc' }, innerBlocks: [], } props passed to edit & save edit save
  10. const { RichText } = wp.editor; const { __ }

    = wp.i18n; wp.blocks.registerBlockType( 'gutenbricks/fold', { title: __( 'Fold' ), icon: 'sort', category: 'layout', attributes: { ... }, edit: ... save: ( { attributes: { title, reveal, id } } ) => ( <div className="gutenbricks-fold"> <input id={ id } type="checkbox" /> <label htmlFor={ id }>{ title }</label> <div className="gutenbricks-fold-reveal"> <RichText.Content multiline value={ reveal } /> </div> </div> ) } ); block.js
  11. const { __ } = wp.i18n; const { RichText }

    = wp.editor; const { Button } = wp.components; const { Component } = wp.element; const { createBlock } = wp.blocks; class FoldEdit extends Component { constructor( ...args ) { ... } render() { ... } } edit.js
  12. class FoldEdit extends Component { constructor( ...args ) { super(

    ...args ); this.props.setAttributes( { id: `gutenbricks-${ this.props.clientId }` } ); } onChangeTitle( title ) { this.props.setAttributes( { title } ); } onChangeReveal( reveal ) { this.props.setAttributes( { reveal } ); } addNewFold() { this.props.insertBlocksAfter( [ createBlock( 'gutenbricks/fold' ) ] ); } render() { const { attributes, className, isSelected } = this.props; return ( ... ); } } edit.js
  13. class FoldEdit extends Component { constructor( ...args ) { super(

    ...args ); this.props.setAttributes( { id: `gutenbricks-${ this.props.clientId }` } ); } onChangeTitle( title ) { this.props.setAttributes( { title } ); } onChangeReveal( reveal ) { this.props.setAttributes( { reveal } ); } addNewFold() { this.props.insertBlocksAfter( [ createBlock( 'gutenbricks/fold' ) ] ); } render() { const { attributes, className, isSelected } = this.props; return ( ... ); } } edit.js
  14. class FoldEdit extends Component { constructor( ...args ) { super(

    ...args ); this.props.setAttributes( { id: `gutenbricks-${ this.props.clientId }` } ); } onChangeTitle( title ) { this.props.setAttributes( { title } ); } onChangeReveal( reveal ) { this.props.setAttributes( { reveal } ); } addNewFold() { this.props.insertBlocksAfter( [ createBlock( 'gutenbricks/fold' ) ] ); } render() { const { attributes, className, isSelected } = this.props; return ( ... ); } } edit.js
  15. class FoldEdit extends Component { constructor( ...args ) { super(

    ...args ); this.props.setAttributes( { id: `gutenbricks-${ this.props.clientId }` } ); } onChangeTitle( title ) { this.props.setAttributes( { title } ); } onChangeReveal( reveal ) { this.props.setAttributes( { reveal } ); } addNewFold() { this.props.insertBlocksAfter( [ createBlock( 'gutenbricks/fold' ) ] ); } render() { const { attributes, className, isSelected } = this.props; return ( ... ); } } edit.js
  16. render() { const { attributes, className, isSelected } = this.props;

    return ( <div className={ className + ( isSelected ? ' edit' : '' ) }> <RichText tagName="label" placeholder={ __( 'Write visible text…' ) } value={ attributes.title } onChange={ this.onChangeTitle } /> { isSelected && [ <div className="gutenbricks-fold-reveal"> <RichText multiline placeholder={ __( 'And the text to reveal…' ) } value={ attributes.reveal } onChange={ this.onChangeReveal } /> </div>, <Button className="button" onClick={ this.addNewFold }> { __( 'Add new' ) } </Button> ] } </div> ); } edit.js
  17. class FoldEdit extends Component { constructor( ...args ) { super(

    ...args ); this.props.setAttributes( { id: `gutenbricks-${ this.props.clientId }` } ); } onChangeTitle( title ) { this.props.setAttributes( { title } ); } onChangeReveal( reveal ) { this.props.setAttributes( { reveal } ); } addNewFold() { this.props.insertBlocksAfter( [ createBlock( 'gutenbricks/fold' ) ] ); } render() { const { attributes, className, isSelected } = this.props; return ( ... ); } } export default FoldEdit; edit.js
  18. import FoldEdit from './edit'; const { RichText } = wp.editor;

    const { __ } = wp.i18n; wp.blocks.registerBlockType( 'gutenbricks/fold', { title: __( 'Fold' ), icon: 'sort', category: 'layout', attributes: { ... }, edit: FoldEdit, save: ... } ); block.js
  19. import FoldEdit from './edit'; const { RichText } = wp.editor;

    const { __ } = wp.i18n; wp.blocks.registerBlockType( 'gutenbricks/fold', { title: __( 'Fold' ), icon: 'sort', category: 'layout', attributes: { ... }, keywords: [__( 'faq' ), __( 'question' ), __( 'quiz' )], supports: { anchor: true }, edit: FoldEdit, save: ... } ); block.js
  20. import FoldEdit from './edit'; const { RichText } = wp.editor;

    const { __ } = wp.i18n; wp.blocks.registerBlockType( 'gutenbricks/fold', { title: __( 'Fold' ), icon: 'sort', category: 'layout', attributes: { ... }, keywords: [__( 'faq' ), __( 'question' ), __( 'quiz' )], supports: { anchor: true }, edit: FoldEdit, save: ... } ); block.js
  21. <?php add_action( 'init', 'gutenbricks_block_init' ); function gutenbricks_block_init() { wp_register_style( 'gutenbricks-fold-style',

    plugins_url( 'fold/style.css', __FILE__ ) ); wp_register_style( 'gutenbricks-fold-editor', plugins_url( 'fold/editor.css', __FILE__ ), array( 'wp-edit-blocks' ) ); wp_register_script( 'gutenbricks-fold', plugins_url( 'build/fold.build.js', __FILE__ ), array( 'wp-blocks', 'wp-i18n', 'wp-element' ) ); register_block_type( 'gutenbricks/fold', array( 'style' => 'gutenbricks-fold-style', 'editor_style' => 'gutenbricks-fold-editor', 'editor_script' => 'gutenbricks-fold', ) ); } block.php
  22. .wp-block-gutenbricks-fold input[type=checkbox], .gutenbricks-fold .gutenbricks-fold-reveal { display: none; } .wp-block-gutenbricks-fold input:checked

    ~ .gutenbricks-fold-reveal { display: block; } .wp-block-gutenbricks-fold label { position: relative; } .wp-block-gutenbricks-fold label:hover { cursor: pointer; } .wp-block-gutenbricks-fold label::after { content: "\005e"; position: absolute; right: -20px; font: normal bold 22px/1 monospace; top: -5px; transform: rotateZ(180deg); transition: transform .2s ease-out; } .gutenbricks-fold input:checked + label::after { transform: rotateZ(0deg); top: 5px; } style.css
  23. .wp-block-gutenbricks-fold label::after { content: "\005e"; position: absolute; right: -20px; font:

    normal bold 22px/1 monospace; top: -5px; transform: rotateZ(180deg); transition: transform .2s ease-out; } .wp-block-gutenbricks-fold.edit label::after { transform: rotateZ(0deg); top: 5px; } editor.css
  24. { "name": "gutenbricks-fold", "version": "1.0.0", "description": "Blocks for Gutenberg", "repository":

    "https://github.com/eliorivero/gutenbricks", "author": "Elio Rivero", "license": "GPL-2.0+", "devDependencies": { "babel-core": "^6.25.0", "babel-loader": "^7.1.1", "babel-plugin-transform-react-jsx": "^6.24.1", "babel-preset-env": "^1.6.0", "cross-env": "^5.0.1", "webpack": "^3.1.0" }, "scripts": { "build": "cross-env BABEL_ENV=default NODE_ENV=production webpack", "watch": "cross-env BABEL_ENV=default webpack --watch" } } package.json