Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal for Block API #48

Closed
wants to merge 4 commits into from
Closed

Proposal for Block API #48

wants to merge 4 commits into from

Conversation

BE-Webdesign
Copy link
Contributor

Fixes #27. Here is my intial proposal for a blocks API for Gutenberg. If es6 were available for use this could be cleaned up big time. I still need to add some polyfills for compatability, but overall, I think this came out decently well. I would love some feedback!

How to use:

gutenberg() is currently used as a namespace for three different things:

  • Factories for blocks and controls.
  • Interface for registering blocks to a specific instance of gutenberg().
  • Interface for the editor to interact with registered blocks.

Using factories:

// Create gutenberg() instance.
const create = gutenberg()

const renderListControl( control ) {
  // Do stuff here to create DOM element. This can be further abstracted to become vDOM.
}

const setListState = function( classes, event ) {
    event.stopPropagation();
    selectedBlock.className = 'is-selected ' + classes;
}

const setListDisplayOpenDiscs = setListState.bind( null, 'open-discs' );

const listBlock = create.Block( {
  name: 'List',
  type: 'wp-list',
  controls: [
    create.control( {
      name: 'Display Open Discs',
      type: 'open-discs',
      displayArea: 'block',
      render: renderListControl,
      icon: {
        path: 'svg path data'
      },
      handlers: [
          {
              'type': 'click',
              'action': setListDisplayOpenDiscs
          }
      ]
    } )
  ]
} )

In the above we are creating an instance of gutenberg() as create. We use the block() and control factories to create a list block with one control. All you need to do now is associate a dom element with the block and the Oila, a block element is already created.

Using the interface for registering blocks:

const blocks = gutenberg().blocks

let registeredBlocks = blocks.registerBlock( imageBlock ).registerBlocks( [ listBlock, quoteBlock, textBlock ] )

You can chain a set of registered blocks using registerBlock(), registerBlocks(), unregisterBlock(), and unregisterBlocks()

The return of the methods will act like an array but it is not an array. It is also import to understand that the blocks are encapsulated and the reference you store is meant to be generated in one go and not changed later on during run time.

Using the editor interface:

const blocks = gutenberg().blocks

let registeredBlocks = blocks.registerBlock( imageBlock ).registerBlocks( [ listBlock, quoteBlock, textBlock ] )

// Create instance of the editor interface as Gutenberg
const Gutenberg = gutenberg().editor( registeredBlocks )

// Get the block area controls for the wp-text block.
const textBlockControls = Gutenberg.getBlockControls( 'wp-text' )

// Create a DOM or vDOM, or whatever you want really, representation of the control.
const mySuperAwesomeVDOMnode = control => // Cool VDOM node stuff.
const control = create.control( {
  name: 'My Control',
  type: 'my-control',
  //
  render: mySuperAwesomeVDOMnode
} )
const controlElement = Gutenberg.renderControl( control )

There is a lot more that the API can currently handle and I have replaced block level controls with the API in its current standing.

Let me know what you think.

Fixes #27. Here is my intial proposal for a blocks API for Gutenberg.
If es6 were available for use this could be cleaned up big time.  I
still need to add some polyfills for compatability, but overall, I think
this came out decently well.
I would love some feedback!
@mtias mtias added [Type] Question Questions about the design or development of the editor. UI Prototype labels Feb 10, 2017
@BE-Webdesign
Copy link
Contributor Author

BE-Webdesign commented Feb 11, 2017

In areas where I thought this was flexible, it was instead limiting. Closing it out. Going to take a different approach.

@aduth
Copy link
Member

aduth commented Feb 11, 2017

In areas, where I thought this was flexible it was instead limiting.

Can you expand on this? Curious if you have any specifics on what you thought to be limiting.

@BE-Webdesign
Copy link
Contributor Author

BE-Webdesign commented Feb 11, 2017

This API mostly served as a sort of blockType registry of sorts, and some other stuff. Mostly it helped to get some thoughts out and see how they worked. It worked alright and made things easy to use with this current UI Prototype. As the actual UI changes, a block API should be less biased towards how things are structured with the current UI. The naming is also off, so I would like to change that as well.

One hangup is that blockTypes, blocks, controls, and controlTypes, should all be composable for themselves, in this attempt they are not. So something more like this example code:

// Define the properties of a text block.  T
const textBlockType = blockType( properties )

// You could compose a quote out of potentially shared properties form the textType or not, it would be up to you.
const quoteBlockType = blockType( textBlockType, blockType( properties ) )

// Create a poetry type.
const poetryBlockType = blockType( textBlockType, blockType( otherTextProperties ), blockType( poetryProperties ) )

// List Type. Calling it on itself won't matter.
const listType = blockType( blockType( listProperties ) )

// Ordered List Type. blockTypes should also support something like this.
let orderedListType = listType( orderedListProperties )

// So they can be overridden to create a different type, depending on your needs, with other properties.
unorderedListType = listType( orderedListProperties )( oopsUseTheseProperties, iWantedUnorderedProperties )

// More concrete example:
const text = blockType( {
    name: 'Text',
    type: 'wp-text',
    controls: [
        leftAlignControlType,
        centerAlignControl,
        rightAlignControl,
    ],
    // Create a textBlock that will by default match this type.
    create: ( blockType = this ) => textBlock( blockType )
} )

// Example text block.
const textBlock = blockType => {
    return block ( {
        /**
         * State and props would be objects that would be passed in via data held somewhere. i.e. the UI
         *
         * The render method is defined by you, so you can make it work the way you want it to.
         */
        render: ( state = {}, props = {} ) => {
            // Do some stuff relating to the blockType for this block.

            // Render something. Could be DOM, vDOM, React, TinyMCE, JSON, XML, HTML, whatever you want.
            // Here is DOM:
            if ( typeof document !== 'undefined' ) {
              pElement = document.createElement( 'p' )
              textNode = document.createTextNode( props.text )
              pElement.appendChild( textNode )
              return pElement
            }
            
            // Here is vDOM:
            if ( typeof h !== 'undefined' ) {
                return h( 'p', '.wp-text', [
                    props.text
                ] )
            }
        }
    } )
}

Another hangup is that rather than types providing a render() method they should instead provide a create() method which will create a single block of that type. Same would apply for controls. This would basically make the interface between a block and its type be create(). Any type that is created as long as it provides a create() method will work. Then each block or control as long as it provides a render() method will be usable by a UI or whatever. This is somewhat illustrated above. To try and make it clear here are some more examples of what would be done after blocks and their types are created.

// blockProperties would include the render() method.
const textBlock = block( blockProperties )

// textTypeProperties could contain a create method returning textBlock().
const textType = blockType( textTypeProperties )

const aTextBlock = textType.create()
const anotherTextBlock = textType.create( textType( withDifferentPropertiesIfYouWant ) )

// containerTypeProperties's create() method would create the actual block.
const containerType = blockType( containerTypeProperties )

// containerProperties's render() method could provide a way to render both aTextBlock and anotherTextBlock inside of it.
const blockContainer = block( containerProperties )

let container = containerType(
  {
    blocks: [ aTextBlock, anotherTextBlock ],
    create: ( blockType = this ) => {
      return blockContainer( { blockType } )
    }
  }
).create()

// If all of the block render methods are React Components, creating a react version of the editor is simple as this.
import 'render' from 'react-dom'
render(
    container.render(),
    document.getElementById( 'editor' )
)

Not sure whether this would be a good approach, but it at least seems like a cool idea to me.

@aduth aduth mentioned this pull request Feb 17, 2017
hypest pushed a commit that referenced this pull request Nov 2, 2018
…interface

Improve callback for contentSize.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Type] Question Questions about the design or development of the editor.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants