diff --git a/block-api.js b/block-api.js new file mode 100644 index 00000000000000..24182b06756d1c --- /dev/null +++ b/block-api.js @@ -0,0 +1,245 @@ +/** + * block-api.js provides an interface for managing content blocks. + */ +( function() { + /** + * gutenberg is the top level namespace for housing the block API. + */ + var gutenberg = function() { + var control = function( config ) { + if ( typeof config === 'undefined' ) { + return {} + } + + return { + name: config.name, + type: config.type, + displayArea: config.displayArea, + render: config.render, + icon: config.icon, + handlers: config.handlers + } + } + + var block = function( config ) { + if ( typeof config === 'undefined' ) { + return {} + } + + return { + name: config.name, + type: config.type, + controls: config.controls + } + } + + /** + * The blocks registry serves as an interface for de/registering blocks. + * + * For each instance of gutenberg() there will be one instance of the + * blocksRegistry. gutenberg().blocks is equivalent to the invocation of the + * blocksRegistry. + */ + var blocksRegistry = function() { + /** + * Collection of registered blocks. + */ + var registeredBlocks = [] + + var register = function( block ) { + var blockExists = registeredBlocks.find( function( registeredBlock ) { + return registeredBlock.type === block.type + } ) + + /** + * If the block does not exist add it to the registered blocks. + * + * If it does exist do nothing and return the already registered + * blocks, this can be changed to allow overriding. + */ + if ( typeof blockExists === 'undefined' ) { + registeredBlocks.push( block ) + return [ block ]; + } + + return [] + } + + var registerBlock = function( block ) { + // Return the list of blocks with the new block if it was added. + var blocks = Array.prototype.concat.apply( [], registeredBlocks, register( block ) ) + + return Blocks( blocks ) + } + + var registerBlocks = function( blocks ) { + // Return the list of blocks with the new blocks if they were added. + var blocks = Array.prototype.concat.apply( [], registeredBlocks, blocks.map( register ) ) + + return Blocks( blocks ) + } + + var unregister = function( block ) { + var blockIndex = registeredBlocks.findIndex( function( registeredBlock ) { + return registeredBlock.type === block.type + } ) + + /** + * If the block does not exist remove it from the registered blocks. + * + * If it does exist do nothing and return the already registered + * blocks, this can be changed to allow overriding. + */ + if ( blockIndex !== -1 ) { + delete registeredBlocks[ blockIndex ] + } + + return registeredBlocks + } + + var unregisterBlock = function( block ) { + return Blocks( unregister( block ) ) + } + + var unregisterBlocks = function( blocks ) { + var blocks = Array.prototype.concat.apply( [], blocks.map( unregister ) ) + return Blocks( blocks ) + } + + /** + * Add the interface to blocks. + * + * It is important to understand that even though it will act like an + * array it is not an Array, so when array methods are used they will + * return an Array type, and must be wrapped in Blocks() if you need to + * chain methods specific to a collection of the registered blocks. + * + * @param {array} blocks List of blocks. + * @returns {Blocks} List of blocks with additional interface. + */ + var Blocks = function( blocks ) { + blocks.registerBlock = registerBlock + blocks.registerBlocks = registerBlocks + blocks.unregisterBlock = unregisterBlock + blocks.unregisterBlocks = unregisterBlocks + + return blocks + } + + /** + * The blocks property becomes an invocation of Blocks on a copy of the + * list of registered blocks. + */ + var blocks = Blocks( registeredBlocks.slice( 0 ) ) + + return blocks + } + + var blocks = blocksRegistry() + + /** + * The blocks registry serves as an interface for de/registering blocks. + * + * For each instance of gutenberg() there will be one instance of the + * blocksRegistry. gutenberg().blocks is equivalent to the invocation of the + * blocksRegistry. + */ + var editor = function( blocks ) { + var getControls = function( displayArea ) { + return function( type ) { + var block = blocks.find( function( block ) { + return block.type === type + } ) + + // If the block was not found, or no controls. + if ( typeof block === 'undefined' || typeof block.controls === 'undefined' ) { + return [] + } + + return block.controls.filter( function( control ) { + return control.displayArea === displayArea + } ) + } + } + + var getBlockControls = getControls( 'block' ) + var getInlineControls = getControls( 'inline' ) + + /** + * A rendering callback can be registered with the block. + */ + var renderControl = function( control ) { + if ( typeof control !== 'undefined' && typeof control.render === 'function' ) { + return control.render( control ) + } + + return null + } + + /** + * Interface for the editor. + */ + return { + getControls: getControls, + getBlockControls: getBlockControls, + getInlineControls: getInlineControls, + renderControl: renderControl + } + } + + /** + * The interface of the Gutenberg block API. + * + * @param {func} block() Used for creating blocks. + * @param {func} control() Used for creating controls. + * @param {func} textBlock() Used for creating text blocks. + * @param {obj} blocks Interface for the blockRegistry + * @param {func} editor Interface for the editor. + */ + return { + block: block, + control: control, + blocks: blocks, + editor: editor + } + } + + if ( typeof module !== 'undefined' ) { + module.exports = gutenberg + } + + if ( typeof window !== 'undefined' ) { + window.gutenberg = gutenberg + } + + /** + * Object assign polyfill per MDN for IE support. + * + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign + */ + if ( typeof Object.assign !== 'function' ) { + Object.assign = function( target, varArgs ) { // .length of function is 2 + 'use strict'; + + if ( target == null ) { // TypeError if undefined or null + throw new TypeError( 'Cannot convert undefined or null to object' ); + } + + var to = Object( target ); + + for ( var index = 1; index < arguments.length; index++ ) { + var nextSource = arguments[ index ]; + + if ( nextSource != null ) { // Skip over if undefined or null + for ( var nextKey in nextSource ) { + // Avoid bugs when hasOwnProperty is shadowed + if ( Object.prototype.hasOwnProperty.call( nextSource, nextKey ) ) { + to[ nextKey ] = nextSource[ nextKey ]; + } + } + } + } + return to; + }; + } +} () ) diff --git a/blocks.js b/blocks.js index dde137e7595c9c..3f2dddd217075e 100644 --- a/blocks.js +++ b/blocks.js @@ -7,10 +7,7 @@ var getNextSibling = siblingGetter( 'next' ); var getPreviousSibling = siblingGetter( 'previous' ); var getTagType = getConfig.bind( null, 'tagTypes' ); var getTypeKinds = getConfig.bind( null, 'typeKinds' ); -var setImageFullBleed = setImageState.bind( null, 'align-full-bleed' ); -var setImageAlignNone = setImageState.bind( null, '' ); -var setImageAlignLeft = setImageState.bind( null, 'align-left' ); -var setImageAlignRight = setImageState.bind( null, 'align-right' ); +var getBlockType = getConfig.bind( null, 'blockTypes' ); /** * Globals @@ -31,6 +28,11 @@ var config = { 'heading': [ 'heading', 'text' ], 'image': [ 'image' ], 'default': [] + }, + blockTypes: { + 'text': 'wp-text', + 'paragraph': 'wp-text', + 'image': 'wp-image' } }; @@ -40,12 +42,17 @@ var switcherButtons = query( '.block-switcher .type svg' ); var blockControls = queryFirst( '.block-controls' ); var inlineControls = queryFirst( '.inline-controls' ); var insertBlockButton = queryFirst( '.insert-block__button' ); -var imageFullBleed = queryFirst( '.block-image__full-width' ); -var imageAlignNone = queryFirst( '.block-image__no-align' ); -var imageAlignLeft = queryFirst( '.block-image__align-left' ); -var imageAlignRight = queryFirst( '.block-image__align-right' ); var selectedBlock = null; +/** + * Blocks + */ +var Gutenberg = window.gutenberg(); +var textBlock = window.textBlock +var imageBlock = window.imageBlock +var blocks = Gutenberg.blocks; +var registeredBlocks = blocks.registerBlocks( [ textBlock, imageBlock ] ); +var gutenbergEditor = Gutenberg.editor( registeredBlocks ); /** * Initialization @@ -98,8 +105,8 @@ function showControls( node ) { switcherButtons.forEach( function( element ) { element.style.display = 'none'; } ); - var blockType = getTagType( node.nodeName ); - var switcherQuery = '.type-icon-' + blockType; + var tagType = getTagType( node.nodeName ); + var switcherQuery = '.type-icon-' + tagType; queryFirst( switcherQuery ).style.display = 'block'; // reposition switcher @@ -108,21 +115,50 @@ function showControls( node ) { switcher.style.top = ( position.top + 18 + window.scrollY ) + 'px'; // show/hide block-specific block controls - var kinds = getTypeKinds( blockType ); - var kindClasses = kinds.map( function( kind ) { - return 'is-' + kind; - } ).join( ' ' ); - blockControls.className = 'block-controls ' + kindClasses; - blockControls.style.display = 'block'; - - // reposition block-specific block controls - blockControls.style.top = ( position.top - 36 + window.scrollY ) + 'px'; - blockControls.style.maxHeight = 'none'; + var blockType = getBlockType( tagType ); + var blockLevelControls = gutenbergEditor.getBlockControls( blockType ); + var blockControlsContainer = document.createElement( 'div' ); + blockControlsContainer.className = 'block-controls block-controls--active'; + + // Create a list of control elements. + var blockControlElements = blockLevelControls.map( function( control ) { + var element = gutenbergEditor.renderControl( control ) + + // Add event listeners. + if ( Array.isArray( control.handlers ) ) { + control.handlers.forEach( function( handler ) { + element.addEventListener( handler.type, handler.action, false ); + } ) + } + + return element; + } ); + + // Append controls. + blockControlElements.forEach( function( element ) { + blockControlsContainer.appendChild( element ); + } ); + + var body = queryFirst( 'body' ); + + blockControlsContainer.style.display = 'block'; + blockControlsContainer.style.top = ( position.top - 36 + window.scrollY ) + 'px'; + blockControlsContainer.style.maxHeight = 'none'; + + body.appendChild( blockControlsContainer ); } function hideControls() { switcher.style.opacity = 0; - blockControls.style.display = 'none'; + + var blockControls = queryFirst( '.block-controls.block-controls--active' ); + + // Potential need to remove event listeners somehow before destroyed. + if ( blockControls ) { + blockControls.parentNode.removeChild( blockControls ); + + blockControls.style.display = 'none'; + } } // Show popup on text selection @@ -173,11 +209,6 @@ function attachControlActions() { }, false ); } } ); - - imageFullBleed.addEventListener( 'click', setImageFullBleed, false ); - imageAlignNone.addEventListener( 'click', setImageAlignNone, false ); - imageAlignLeft.addEventListener( 'click', setImageAlignLeft, false ); - imageAlignRight.addEventListener( 'click', setImageAlignRight, false ); } function reselect() { diff --git a/index.html b/index.html index 53a7703c57a71a..bd57323c642529 100644 --- a/index.html +++ b/index.html @@ -17,29 +17,6 @@ -