From f6663518b14adf83a8cf00b085ae47cc12688555 Mon Sep 17 00:00:00 2001 From: BE-Webdesign Date: Thu, 9 Feb 2017 18:32:55 -0500 Subject: [PATCH 1/2] Initial stab at basic blocks api. --- block-api.js | 249 +++++++++++++++++++++++++++++++++++++++++++++++++ index.html | 1 + package.json | 8 +- test/tests.js | 254 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 510 insertions(+), 2 deletions(-) create mode 100644 block-api.js create mode 100644 test/tests.js diff --git a/block-api.js b/block-api.js new file mode 100644 index 00000000000000..7a161308faac44 --- /dev/null +++ b/block-api.js @@ -0,0 +1,249 @@ +/** + * block-api.js provides an interface for managing content blocks. + */ + +/** + * 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 + } + } + + var block = function( config ) { + if ( typeof config === 'undefined' ) { + return {} + } + + return { + name: config.name, + type: config.type, + controls: config.controls + } + } + + // Standard text block. + var textBlock = function( config ) { + if ( typeof config === 'undefined' ) { + config = {} + } + + return Object.assign( + {}, + config, + block( { + name: 'Text', + type: 'wp-text', + controls: [ + gutenberg().control( + { + name: 'Align Left', + type: 'left-align', + displayArea: 'block' + } + ), + gutenberg().control( + { + name: 'Align Center', + type: 'center-align', + displayArea: 'block' + } + ), + gutenberg().control( + { + name: 'Align Right', + type: 'right-align', + displayArea: 'block' + } + ), + gutenberg().control( + { + name: 'Make Text Bold', + type: 'bold', + displayArea: 'inline' + } + ), + gutenberg().control( + { + name: 'Italicize Text', + type: 'italics', + displayArea: 'inline' + } + ), + gutenberg().control( + { + name: 'Add A Link', + type: 'link', + displayArea: 'inline' + } + ), + gutenberg().control( + { + name: 'Strikethrough Text', + type: 'strikethrough', + displayArea: 'inline' + } + ), + gutenberg().control( + { + name: 'Underline Text', + type: 'underline', + displayArea: 'inline' + } + ) + ] + } ) + ) + } + + /** + * 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() { + 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() + + return { + block: block, + control: control, + textBlock: textBlock, + blocks: blocks + } +} + +if ( typeof module !== 'undefined' ) { + module.exports = 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/index.html b/index.html index 22744808aba3a8..b55c919bc1ea17 100644 --- a/index.html +++ b/index.html @@ -83,5 +83,6 @@

1.0 Is The Loneliest Number

+ diff --git a/package.json b/package.json index 157ef3e5da322e..5fedf5f9599941 100644 --- a/package.json +++ b/package.json @@ -3,9 +3,12 @@ "version": "1.0.0", "description": "Prototyping a new WordPress editor experience", "main": "index.html", + "bin": { + "mocha": "./node_modules/.bin/mocha" + }, "scripts": { "start": "http-server -p 5000", - "test": "echo \"Error: no test specified\" && exit 1" + "test": "mocha" }, "repository": { "type": "git", @@ -23,6 +26,7 @@ }, "homepage": "https://github.com/Automattic/gutenberg#readme", "devDependencies": { - "http-server": "0.9.0" + "http-server": "0.9.0", + "mocha": "^3.2.0" } } diff --git a/test/tests.js b/test/tests.js new file mode 100644 index 00000000000000..61d53b8f79be16 --- /dev/null +++ b/test/tests.js @@ -0,0 +1,254 @@ +/** + * Test the blocks API. + */ +const gutenberg = require( '../block-api.js' ) +const assert = require( 'assert' ) + +describe( 'gutenberg()', function() { + it( 'should return an object with the block api.', function() { + let Gutenberg = gutenberg() + + let expected = 'function' + let actual = typeof Gutenberg.block + + assert.equal( actual, expected ) + } ); + + describe( 'gutenberg.control', function() { + it( 'should return a control with matching properties.', function() { + let Gutenberg = gutenberg() + + let expected = { + name: 'Align Left', + type: 'left-align', + displayArea: 'block', + } + let actual = Gutenberg.control( + { + name: 'Align Left', + type: 'left-align', + displayArea: 'block', + } + ) + + assert.deepEqual( actual, expected ) + } ) + } ) + + describe( 'gutenberg.block', function() { + it( 'should return a block with matching properties.', function() { + let Gutenberg = gutenberg() + + let expected = { + name: 'Text', + type: 'wp-text', + controls: [ + { + name: 'Align Left', + type: 'left-align', + handler: {} + } + ] + } + let actual = Gutenberg.block( + { + name: 'Text', + type: 'wp-text', + controls: [ + { + name: 'Align Left', + type: 'left-align', + handler: {} + } + ] + } + ) + + assert.deepEqual( actual, expected ) + } ) + } ) + + describe( 'gutenberg.textBlock', function() { + it( 'should return a text block with matching properties.', function() { + let Gutenberg = gutenberg() + + let expected = { + name: 'Text', + type: 'wp-text', + controls: [ + { + name: 'Align Left', + type: 'left-align', + displayArea: 'block' + }, + { + name: 'Align Center', + type: 'center-align', + displayArea: 'block' + }, + { + name: 'Align Right', + type: 'right-align', + displayArea: 'block' + }, + { + name: 'Make Text Bold', + type: 'bold', + displayArea: 'inline' + }, + { + name: 'Italicize Text', + type: 'italics', + displayArea: 'inline' + }, + { + name: 'Add A Link', + type: 'link', + displayArea: 'inline' + }, + { + name: 'Strikethrough Text', + type: 'strikethrough', + displayArea: 'inline' + }, + { + name: 'Underline Text', + type: 'underline', + displayArea: 'inline' + } + ] + } + let actual = Gutenberg.textBlock() + + assert.deepEqual( actual, expected ) + } ) + } ) + + describe( 'gutenberg.blocks.registerBlock', function() { + it( 'should return a list of registered blocks.', function() { + let Gutenberg = gutenberg() + let blocks = Gutenberg.blocks + + let expected = [ Gutenberg.textBlock() ] + // Blocks() is a monad that sprinkles on some extra methods. Just compare the arrays. + let actual = blocks.registerBlock( Gutenberg.textBlock() ).slice( 0 ) + + assert.deepEqual( actual, expected ) + } ) + + it( 'should not allow blocks of the same type to exist.', function() { + let Gutenberg = gutenberg() + let blocks = Gutenberg.blocks + + let expected = [ Gutenberg.textBlock() ] + // Blocks() is a monad that sprinkles on some extra methods. Just compare the arrays. + let actual = blocks.registerBlock( Gutenberg.textBlock() ).registerBlock( Gutenberg.textBlock() ).slice( 0 ) + + assert.deepEqual( actual, expected ) + } ) + + it( 'should have two blocks.', function() { + let Gutenberg = gutenberg() + let blocks = Gutenberg.blocks + + let expected = [ Gutenberg.textBlock(), Gutenberg.block( { type: 'yolo' } ) ] + // Blocks() is a monad that sprinkles on some extra methods. Just compare the arrays. + let actual = blocks.registerBlock( Gutenberg.textBlock() ).registerBlock( Gutenberg.block( { type: 'yolo' } ) ).slice( 0 ) + + assert.deepEqual( actual, expected ) + } ) + } ) + + describe( 'gutenberg.blocks.registerBlocks', function() { + it( 'should allow the registry of multiple blocks.', function() { + let Gutenberg = gutenberg() + let blocks = Gutenberg.blocks + + let expected = [ Gutenberg.textBlock(), Gutenberg.block( { type: 'yolo' } ) ] + let actual = blocks.registerBlocks( expected ).slice( 0 ) + + assert.deepEqual( actual, expected ) + } ) + } ) + + describe( 'gutenberg.blocks.registerBlocks.registerBlock', function() { + it( 'should allow the registry of multiple blocks in a chain.', function() { + let Gutenberg = gutenberg() + let blocks = Gutenberg.blocks + + let someBlock = Gutenberg.block( { type: 'yolo' } ) + let someBlocks = [ Gutenberg.textBlock(), Gutenberg.block( { type: 'image' } ) ] + + // Look for the whole list. + let expected = [].concat( someBlocks, [ someBlock ] ) + let actual = blocks.registerBlocks( someBlocks ).registerBlock( someBlock ).slice( 0 ) + + assert.deepEqual( actual, expected ) + } ) + } ) + + describe( 'gutenberg.blocks.registerBlock.registerBlocks', function() { + it( 'should allow the registry of multiple blocks in a chain.', function() { + let Gutenberg = gutenberg() + let blocks = Gutenberg.blocks + + let someBlock = Gutenberg.block( { type: 'yolo' } ) + let someBlocks = [ Gutenberg.textBlock() ] + + // Look for the whole list. + let expected = [].concat( someBlocks, [ someBlock ] ) + let actual = blocks.registerBlocks( someBlocks ).registerBlock( someBlock ).slice( 0 ) + + assert.deepEqual( actual, expected ) + } ) + } ) + + describe( 'gutenberg.blocks.unregisterBlock', function() { + it( 'should allow the registry of multiple blocks in a chain.', function() { + let Gutenberg = gutenberg() + let blocks = Gutenberg.blocks + + let someBlock = Gutenberg.textBlock() + + blocks.registerBlock( someBlock ) + + // Look for the empty list. + let expected = [] + let actual = blocks.unregisterBlock( someBlock ).splice( 0 ) + + assert.deepEqual( actual, expected ) + } ) + + it( 'should do nothing if the blocks do not exist.', function() { + let Gutenberg = gutenberg() + let blocks = Gutenberg.blocks + + let someBlock = Gutenberg.textBlock() + let blockDoesNotExist = Gutenberg.block( { type: 'I do not exist' } ) + + blocks.registerBlock( someBlock ) + + // Look for the empty list. + let expected = [ someBlock ] + let actual = blocks.unregisterBlock( blockDoesNotExist ).splice( 0 ) + + assert.deepEqual( actual, expected ) + } ) + } ) + + describe( 'gutenberg.blocks.unregisterBlocks', function() { + it( 'should allow the registry of multiple blocks in a chain.', function() { + let Gutenberg = gutenberg() + let blocks = Gutenberg.blocks + + let someBlocks = [ Gutenberg.textBlock(), Gutenberg.block( { type: 'yolo' } ), Gutenberg.block( { type: 'image' } ) ] + let someBlocksToTakeAway = [ Gutenberg.block( { type: 'yolo' } ), Gutenberg.block( { type: 'image' } ) ] + + // Look for partial list. + let expected = [] + let actual = blocks.unregisterBlocks( someBlocksToTakeAway ).splice( 0 ) + + assert.deepEqual( actual, expected ) + } ) + } ) +} ); From 8aff5b46c8ed46daab7f9ff609d5be462261e5b9 Mon Sep 17 00:00:00 2001 From: BE-Webdesign Date: Fri, 10 Feb 2017 01:52:04 -0500 Subject: [PATCH 2/2] Initial Block API proposal 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! --- block-api.js | 396 +++++++++++++++++++++++++------------------------- blocks.js | 83 +++++++---- index.html | 27 +--- style.css | 16 +- test/tests.js | 235 +++++++++++++++++++++++------- wp-image.js | 152 +++++++++++++++++++ wp-text.js | 136 +++++++++++++++++ 7 files changed, 737 insertions(+), 308 deletions(-) create mode 100644 wp-image.js create mode 100644 wp-text.js diff --git a/block-api.js b/block-api.js index 7a161308faac44..24182b06756d1c 100644 --- a/block-api.js +++ b/block-api.js @@ -1,249 +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 {} + } -/** - * 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 - } - } - - var block = 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 + } } - return { - name: config.name, - type: config.type, - controls: config.controls - } - } + var block = function( config ) { + if ( typeof config === 'undefined' ) { + return {} + } - // Standard text block. - var textBlock = function( config ) { - if ( typeof config === 'undefined' ) { - config = {} + return { + name: config.name, + type: config.type, + controls: config.controls + } } - return Object.assign( - {}, - config, - block( { - name: 'Text', - type: 'wp-text', - controls: [ - gutenberg().control( - { - name: 'Align Left', - type: 'left-align', - displayArea: 'block' - } - ), - gutenberg().control( - { - name: 'Align Center', - type: 'center-align', - displayArea: 'block' - } - ), - gutenberg().control( - { - name: 'Align Right', - type: 'right-align', - displayArea: 'block' - } - ), - gutenberg().control( - { - name: 'Make Text Bold', - type: 'bold', - displayArea: 'inline' - } - ), - gutenberg().control( - { - name: 'Italicize Text', - type: 'italics', - displayArea: 'inline' - } - ), - gutenberg().control( - { - name: 'Add A Link', - type: 'link', - displayArea: 'inline' - } - ), - gutenberg().control( - { - name: 'Strikethrough Text', - type: 'strikethrough', - displayArea: 'inline' - } - ), - gutenberg().control( - { - name: 'Underline Text', - type: 'underline', - displayArea: 'inline' - } - ) - ] - } ) - ) - } + /** + * 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 ]; + } - /** - * 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() { - var registeredBlocks = [] + return [] + } - var register = function( block ) { - var blockExists = registeredBlocks.find( function( registeredBlock ) { - return registeredBlock.type === block.type - } ) + 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 ) ) - /** - * 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 Blocks( blocks ) } - return [] - } + 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 ) ) - 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 ) + } - 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 ] + } - 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 registeredBlocks + } - return Blocks( blocks ) - } + var unregisterBlock = function( block ) { + return Blocks( unregister( block ) ) + } - var unregister = function( block ) { - var blockIndex = registeredBlocks.findIndex( function( registeredBlock ) { - return registeredBlock.type === block.type - } ) + var unregisterBlocks = function( blocks ) { + var blocks = Array.prototype.concat.apply( [], blocks.map( unregister ) ) + return Blocks( blocks ) + } /** - * If the block does not exist remove it from the registered 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. * - * If it does exist do nothing and return the already registered - * blocks, this can be changed to allow overriding. + * @param {array} blocks List of blocks. + * @returns {Blocks} List of blocks with additional interface. */ - if ( blockIndex !== -1 ) { - delete registeredBlocks[ blockIndex ] + var Blocks = function( blocks ) { + blocks.registerBlock = registerBlock + blocks.registerBlocks = registerBlocks + blocks.unregisterBlock = unregisterBlock + blocks.unregisterBlocks = unregisterBlocks + + return blocks } - return registeredBlocks - } + /** + * The blocks property becomes an invocation of Blocks on a copy of the + * list of registered blocks. + */ + var blocks = Blocks( registeredBlocks.slice( 0 ) ) - var unregisterBlock = function( block ) { - return Blocks( unregister( block ) ) + return blocks } - var unregisterBlocks = function( blocks ) { - var blocks = Array.prototype.concat.apply( [], blocks.map( unregister ) ) - return Blocks( blocks ) - } + var blocks = blocksRegistry() /** - * Add the interface to blocks. + * The blocks registry serves as an interface for de/registering 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. + * For each instance of gutenberg() there will be one instance of the + * blocksRegistry. gutenberg().blocks is equivalent to the invocation of the + * blocksRegistry. */ - var Blocks = function( blocks ) { - blocks.registerBlock = registerBlock - blocks.registerBlocks = registerBlocks - blocks.unregisterBlock = unregisterBlock - blocks.unregisterBlocks = unregisterBlocks + 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 blocks + 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 blocks property becomes an invocation of Blocks on a copy of the - * list of registered blocks. + * 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. */ - var blocks = Blocks( registeredBlocks.slice( 0 ) ) - - return blocks + return { + block: block, + control: control, + blocks: blocks, + editor: editor + } } - var blocks = blocksRegistry() - - return { - block: block, - control: control, - textBlock: textBlock, - blocks: blocks + if ( typeof module !== 'undefined' ) { + module.exports = gutenberg } -} -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'; + /** + * 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' ); - } + if ( target == null ) { // TypeError if undefined or null + throw new TypeError( 'Cannot convert undefined or null to object' ); + } - var to = Object( target ); + var to = Object( target ); - for ( var index = 1; index < arguments.length; index++ ) { - var nextSource = arguments[ index ]; + 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 ]; + 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; - }; -} + 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 4d7ffd56bc1913..bd57323c642529 100644 --- a/index.html +++ b/index.html @@ -17,29 +17,6 @@ Image -
- - - - - - - -
@@ -85,7 +62,9 @@

1.0 Is The Loneliest Number

- + + + diff --git a/style.css b/style.css index 5b50dcdddfb7d2..6f63118cb65c77 100644 --- a/style.css +++ b/style.css @@ -259,7 +259,7 @@ p { } .block-controls button { - display: none; + /*display: none;*/ } .block-controls.is-image .block-image, @@ -344,7 +344,7 @@ p { margin-right: 4px; } -img.align-full-bleed { +img.align-full-width { margin-left: calc(50% - 50vw); width: 100vw; max-width: none; @@ -363,3 +363,15 @@ img.align-right { width: 340px; margin: 0 0 0 16px; } + +p.align-left { + text-align: left; +} + +p.align-center { + text-align: center; +} + +p.align-right { + text-align: right; +} diff --git a/test/tests.js b/test/tests.js index 61d53b8f79be16..78bd51223d7353 100644 --- a/test/tests.js +++ b/test/tests.js @@ -3,6 +3,7 @@ */ const gutenberg = require( '../block-api.js' ) const assert = require( 'assert' ) +const textBlock = require( '../wp-text.js' ) describe( 'gutenberg()', function() { it( 'should return an object with the block api.', function() { @@ -22,6 +23,9 @@ describe( 'gutenberg()', function() { name: 'Align Left', type: 'left-align', displayArea: 'block', + render: undefined, + icon: undefined, + handlers: undefined } let actual = Gutenberg.control( { @@ -68,6 +72,9 @@ describe( 'gutenberg()', function() { } ) } ) + /** + * textBlock will be deleted soon. + */ describe( 'gutenberg.textBlock', function() { it( 'should return a text block with matching properties.', function() { let Gutenberg = gutenberg() @@ -75,50 +82,12 @@ describe( 'gutenberg()', function() { let expected = { name: 'Text', type: 'wp-text', - controls: [ - { - name: 'Align Left', - type: 'left-align', - displayArea: 'block' - }, - { - name: 'Align Center', - type: 'center-align', - displayArea: 'block' - }, - { - name: 'Align Right', - type: 'right-align', - displayArea: 'block' - }, - { - name: 'Make Text Bold', - type: 'bold', - displayArea: 'inline' - }, - { - name: 'Italicize Text', - type: 'italics', - displayArea: 'inline' - }, - { - name: 'Add A Link', - type: 'link', - displayArea: 'inline' - }, - { - name: 'Strikethrough Text', - type: 'strikethrough', - displayArea: 'inline' - }, - { - name: 'Underline Text', - type: 'underline', - displayArea: 'inline' - } - ] + controls: [] } - let actual = Gutenberg.textBlock() + + // Override controls so function calls do not have to match. + textBlock.controls = []; + let actual = textBlock assert.deepEqual( actual, expected ) } ) @@ -129,9 +98,9 @@ describe( 'gutenberg()', function() { let Gutenberg = gutenberg() let blocks = Gutenberg.blocks - let expected = [ Gutenberg.textBlock() ] + let expected = [ textBlock ] // Blocks() is a monad that sprinkles on some extra methods. Just compare the arrays. - let actual = blocks.registerBlock( Gutenberg.textBlock() ).slice( 0 ) + let actual = blocks.registerBlock( textBlock ).slice( 0 ) assert.deepEqual( actual, expected ) } ) @@ -140,9 +109,9 @@ describe( 'gutenberg()', function() { let Gutenberg = gutenberg() let blocks = Gutenberg.blocks - let expected = [ Gutenberg.textBlock() ] + let expected = [ textBlock ] // Blocks() is a monad that sprinkles on some extra methods. Just compare the arrays. - let actual = blocks.registerBlock( Gutenberg.textBlock() ).registerBlock( Gutenberg.textBlock() ).slice( 0 ) + let actual = blocks.registerBlock( textBlock ).registerBlock( textBlock ).slice( 0 ) assert.deepEqual( actual, expected ) } ) @@ -151,9 +120,9 @@ describe( 'gutenberg()', function() { let Gutenberg = gutenberg() let blocks = Gutenberg.blocks - let expected = [ Gutenberg.textBlock(), Gutenberg.block( { type: 'yolo' } ) ] + let expected = [ textBlock, Gutenberg.block( { type: 'yolo' } ) ] // Blocks() is a monad that sprinkles on some extra methods. Just compare the arrays. - let actual = blocks.registerBlock( Gutenberg.textBlock() ).registerBlock( Gutenberg.block( { type: 'yolo' } ) ).slice( 0 ) + let actual = blocks.registerBlock( textBlock ).registerBlock( Gutenberg.block( { type: 'yolo' } ) ).slice( 0 ) assert.deepEqual( actual, expected ) } ) @@ -164,7 +133,10 @@ describe( 'gutenberg()', function() { let Gutenberg = gutenberg() let blocks = Gutenberg.blocks - let expected = [ Gutenberg.textBlock(), Gutenberg.block( { type: 'yolo' } ) ] + let imageBlock = Object.assign( {}, textBlock ); + imageBlock.type = 'wp-image' + + let expected = [ textBlock, imageBlock ] let actual = blocks.registerBlocks( expected ).slice( 0 ) assert.deepEqual( actual, expected ) @@ -177,7 +149,7 @@ describe( 'gutenberg()', function() { let blocks = Gutenberg.blocks let someBlock = Gutenberg.block( { type: 'yolo' } ) - let someBlocks = [ Gutenberg.textBlock(), Gutenberg.block( { type: 'image' } ) ] + let someBlocks = [ textBlock, Gutenberg.block( { type: 'image' } ) ] // Look for the whole list. let expected = [].concat( someBlocks, [ someBlock ] ) @@ -193,7 +165,7 @@ describe( 'gutenberg()', function() { let blocks = Gutenberg.blocks let someBlock = Gutenberg.block( { type: 'yolo' } ) - let someBlocks = [ Gutenberg.textBlock() ] + let someBlocks = [ textBlock ] // Look for the whole list. let expected = [].concat( someBlocks, [ someBlock ] ) @@ -208,8 +180,7 @@ describe( 'gutenberg()', function() { let Gutenberg = gutenberg() let blocks = Gutenberg.blocks - let someBlock = Gutenberg.textBlock() - + let someBlock = textBlock blocks.registerBlock( someBlock ) // Look for the empty list. @@ -223,7 +194,7 @@ describe( 'gutenberg()', function() { let Gutenberg = gutenberg() let blocks = Gutenberg.blocks - let someBlock = Gutenberg.textBlock() + let someBlock = textBlock let blockDoesNotExist = Gutenberg.block( { type: 'I do not exist' } ) blocks.registerBlock( someBlock ) @@ -241,7 +212,23 @@ describe( 'gutenberg()', function() { let Gutenberg = gutenberg() let blocks = Gutenberg.blocks - let someBlocks = [ Gutenberg.textBlock(), Gutenberg.block( { type: 'yolo' } ), Gutenberg.block( { type: 'image' } ) ] + let someBlocks = [ textBlock, Gutenberg.block( { type: 'yolo' } ), Gutenberg.block( { type: 'image' } ) ] + let someBlocksToTakeAway = [ Gutenberg.block( { type: 'yolo' } ), Gutenberg.block( { type: 'image' } ) ] + + // Look for partial list. + let expected = [] + let actual = blocks.unregisterBlocks( someBlocksToTakeAway ).splice( 0 ) + + assert.deepEqual( actual, expected ) + } ) + } ) + + describe( 'gutenberg.blocks.unregisterBlocks', function() { + it( 'should allow the registry of multiple blocks in a chain.', function() { + let Gutenberg = gutenberg() + let blocks = Gutenberg.blocks + + let someBlocks = [ textBlock, Gutenberg.block( { type: 'yolo' } ), Gutenberg.block( { type: 'image' } ) ] let someBlocksToTakeAway = [ Gutenberg.block( { type: 'yolo' } ), Gutenberg.block( { type: 'image' } ) ] // Look for partial list. @@ -251,4 +238,140 @@ describe( 'gutenberg()', function() { assert.deepEqual( actual, expected ) } ) } ) + + describe( 'gutenberg.editor.getControls', function() { + it( 'should grab controls.', function() { + let Gutenberg = gutenberg() + + // Register blocks. + + let someBlocks = [ textBlock ] + let editor = Gutenberg.editor( someBlocks ) + + // Look for partial list. + let expected = textBlock.controls.filter( control => control.displayArea === 'block' ) + let actual = editor.getControls( 'block' )( 'wp-text' ) + + assert.deepEqual( actual, expected ) + } ) + it( 'should return empty when no blocks are present.', function() { + let Gutenberg = gutenberg() + + // Register blocks. + let someBlocks = [] + let editor = Gutenberg.editor( someBlocks ) + + // Look for partial list. + let expected = [] + let actual = editor.getControls( 'block' )( 'wp-text' ) + + assert.deepEqual( actual, expected ) + } ) + it( 'should return empty when no controls are present.', function() { + let Gutenberg = gutenberg() + + // Register blocks. + let aBlockWithoutControls = Gutenberg.block( { type: 'no-controls' } ) + let someBlocks = [ aBlockWithoutControls ] + let editor = Gutenberg.editor( someBlocks ) + + // Look for partial list. + let expected = [] + let actual = editor.getControls( 'block' )( 'no-controls' ) + + assert.deepEqual( actual, expected ) + } ) + } ) + + describe( 'gutenberg.editor.getBlockControls', function() { + it( 'should grab controls.', function() { + let Gutenberg = gutenberg() + + // Register blocks. + let someBlocks = [ textBlock ] + let editor = Gutenberg.editor( someBlocks ) + + // Look for partial list. + let expected = textBlock.controls.filter( control => control.displayArea === 'block' ) + let actual = editor.getBlockControls( 'wp-text' ) + + assert.deepEqual( actual, expected ) + } ) + } ) + + describe( 'gutenberg.editor.getInlineControls', function() { + it( 'should grab controls.', function() { + let Gutenberg = gutenberg() + + // Register blocks. + let someBlocks = [ textBlock ] + let editor = Gutenberg.editor( someBlocks ) + + // Look for partial list. + let expected = textBlock.controls.filter( control => control.displayArea === 'inline' ) + let actual = editor.getInlineControls( 'wp-text' ) + + assert.deepEqual( actual, expected ) + } ) + } ) + + describe( 'gutenberg.editor.renderControl', function() { + it( 'should apply the callback for control.render().', function() { + let Gutenberg = gutenberg() + + // Register blocks. + + let render = function( control ) { + return control + } + let someBlocks = [ textBlock ] + let editor = Gutenberg.editor( someBlocks ) + + // Override the textBlock controls. + textBlock.controls.push( { type: 'yolo' } ) + let index = textBlock.controls.findIndex( control => 'yolo' === control.type ) + let yoloControl = textBlock.controls[ index ] + yoloControl.render = render + + // Look for partial list. + let expected = { type: 'yolo', render } + let actual = editor.renderControl( yoloControl ) + + assert.deepEqual( actual, expected ) + } ) + + it( 'should return null when control is not defined.', function() { + let Gutenberg = gutenberg() + + // Register blocks. + let aBlockWithoutControls = Gutenberg.block( { type: 'no-controls' } ) + let someBlocks = [ aBlockWithoutControls ] + let editor = Gutenberg.editor( someBlocks ) + + // Look for partial list. + let expected = null + let actual = editor.renderControl( aBlockWithoutControls ) + + assert.deepEqual( actual, expected ) + } ) + + it( 'should return null when control.render() is not a function.', function() { + let Gutenberg = gutenberg() + + // Register blocks. + let controlWithoutRender = Gutenberg.control( { type: 'yolo' } ) + let aBlock = Gutenberg.block( { + type: 'no-controls', + controls: [ controlWithoutRender ] + } ) + let someBlocks = [ aBlock ] + let editor = Gutenberg.editor( someBlocks ) + + // Look for partial list. + let expected = null + let actual = editor.renderControl( aBlock ) + + assert.deepEqual( actual, expected ) + } ) + } ) } ); diff --git a/wp-image.js b/wp-image.js new file mode 100644 index 00000000000000..d1febd09d3b9a2 --- /dev/null +++ b/wp-image.js @@ -0,0 +1,152 @@ +/** + * wp-image.js is the standard block for image handling. + */ + +( function() { + var create + + // In Browser. + if ( typeof window !== 'undefined' ) { + create = window.gutenberg() + } + + // In Node.js + if ( typeof module !== 'undefined' && typeof module.exports !== 'undefined' ) { + create = require( './block-api.js' )() + } + + /** + * Control renderer. + */ + var renderBlockImageControl = function( control ) { + if ( typeof document !== 'undefined' ) { + var button = document.createElement( 'button' ); + button.className = 'block-text is-active'; + + var icon = document.createElementNS( 'http://www.w3.org/2000/svg', 'svg' ); + icon.setAttribute( 'xmlns', 'http://www.w3.org/2000/svg' ); + icon.setAttribute( 'viewBox', '0 0 24 24' ); + + var title = document.createElement( 'title' ); + var titleText = document.createTextNode( control.name ); + title.appendChild( titleText ); + + var rect = document.createElementNS( 'http://www.w3.org/2000/svg', 'rect' ) + rect.setAttribute( 'x', '0' ); + rect.setAttribute( 'fill', 'none' ); + rect.setAttribute( 'width', '24' ); + rect.setAttribute( 'heigh', '24' ); + + var group = document.createElementNS( 'http://www.w3.org/2000/svg', 'g' ); + + var path = document.createElementNS( 'http://www.w3.org/2000/svg', 'path' ); + path.setAttribute( 'd', control.icon.path ); + + group.appendChild( path ); + + icon.appendChild( title ); + icon.appendChild( rect ); + icon.appendChild( group ); + + button.appendChild( icon ); + + return button; + } + } + + /** + * Control actions. + */ + var setImageState = function( classes, event ) { + event.stopPropagation(); + selectedBlock.className = 'is-selected ' + classes; + } + + var setImageFullWidth = setImageState.bind( null, 'align-full-width' ); + var setImageAlignNone = setImageState.bind( null, '' ); + var setImageAlignLeft = setImageState.bind( null, 'align-left' ); + var setImageAlignRight = setImageState.bind( null, 'align-right' ); + + var imageBlock = create.block( { + name: 'Image', + type: 'wp-image', + controls: [ + create.control( + { + name: 'No Alignment', + type: 'no-align', + displayArea: 'block', + render: renderBlockImageControl, + icon: { + path: 'M3 5h18v2H3V5zm0 14h18v-2H3v2zm5-4h8V9H8v6z' + }, + handlers: [ + { + 'type': 'click', + 'action': setImageAlignNone + } + ] + } + ), + create.control( + { + name: 'Align Left', + type: 'left-align', + displayArea: 'block', + render: renderBlockImageControl, + icon: { + path: 'M3 5h18v2H3V5zm0 14h18v-2H3v2zm0-4h8V9H3v6zm10 0h8v-2h-8v2zm0-4h8V9h-8v2z' + }, + handlers: [ + { + 'type': 'click', + 'action': setImageAlignLeft + } + ] + } + ), + create.control( + { + name: 'Align Right', + type: 'right-align', + displayArea: 'block', + render: renderBlockImageControl, + icon: { + path: 'M21 7H3V5h18v2zm0 10H3v2h18v-2zm0-8h-8v6h8V9zm-10 4H3v2h8v-2zm0-4H3v2h8V9z' + }, + handlers: [ + { + 'type': 'click', + 'action': setImageAlignRight + } + ] + } + ), + create.control( + { + name: 'Make Full Width', + type: 'full-wdith', + displayArea: 'block', + render: renderBlockImageControl, + icon: { + path: 'M21 3v6h-2V6.41l-3.29 3.3-1.42-1.42L17.59 5H15V3zM3 3v6h2V6.41l3.29 3.3 1.42-1.42L6.41 5H9V3zm18 18v-6h-2v2.59l-3.29-3.29-1.41 1.41L17.59 19H15v2zM9 21v-2H6.41l3.29-3.29-1.41-1.42L5 17.59V15H3v6z' + }, + handlers: [ + { + 'type': 'click', + 'action': setImageFullWidth + } + ] + } + ) + ] + } ) + + if ( typeof window !== 'undefined' ) { + window.imageBlock = imageBlock + } + + if ( typeof module !== 'undefined' ) { + module.exports = imageBlock + } +} () ) diff --git a/wp-text.js b/wp-text.js new file mode 100644 index 00000000000000..2341c8aff20cc8 --- /dev/null +++ b/wp-text.js @@ -0,0 +1,136 @@ +/** + * wp-text.js is the standard block for text handling. + */ +( function() { + var create + + // In Browser. + if ( typeof window !== 'undefined' ) { + create = window.gutenberg() + } + + // In Node.js + if ( typeof module !== 'undefined' && typeof module.exports !== 'undefined' ) { + create = require( './block-api.js' )() + } + + /** + * Control render functions. + */ + var renderBlockTextControl = function( control ) { + if ( typeof document !== 'undefined' ) { + var button = document.createElement( 'button' ); + button.className = 'block-text is-active'; + + var icon = document.createElementNS( 'http://www.w3.org/2000/svg', 'svg' ); + icon.setAttribute( 'xmlns', 'http://www.w3.org/2000/svg' ); + icon.setAttribute( 'viewBox', '0 0 24 24' ); + + var title = document.createElement( 'title' ); + var titleText = document.createTextNode( control.name ); + title.appendChild( titleText ); + + var rect = document.createElementNS( 'http://www.w3.org/2000/svg', 'rect' ) + rect.setAttribute( 'x', '0' ); + rect.setAttribute( 'fill', 'none' ); + rect.setAttribute( 'width', '24' ); + rect.setAttribute( 'heigh', '24' ); + + var group = document.createElementNS( 'http://www.w3.org/2000/svg', 'g' ); + + var path = document.createElementNS( 'http://www.w3.org/2000/svg', 'path' ); + path.setAttribute( 'd', control.icon.path ); + + group.appendChild( path ); + + icon.appendChild( title ); + icon.appendChild( rect ); + icon.appendChild( group ); + + button.appendChild( icon ); + + return button; + } + } + + /** + * Control actions. + */ + var setTextState = function( classes, event ) { + event.stopPropagation(); + selectedBlock.className = 'is-selected ' + classes; + } + + var setTextAlignLeft = setTextState.bind( null, 'align-left' ); + var setTextAlignCenter = setTextState.bind( null, 'align-center' ); + var setTextAlignRight = setTextState.bind( null, 'align-right' ); + + /** + * Block. + */ + var textBlock = create.block( { + name: 'Text', + type: 'wp-text', + controls: [ + create.control( + { + name: 'Align Left', + type: 'left-align', + displayArea: 'block', + render: renderBlockTextControl, + icon: { + path: 'M4 19h16v-2H4v2zm10-6H4v2h10v-2zM4 9v2h16V9H4zm10-4H4v2h10V5z' + }, + handlers: [ + { + 'type': 'click', + 'action': setTextAlignLeft + } + ] + } + ), + create.control( + { + name: 'Align Center', + type: 'center-align', + displayArea: 'block', + render: renderBlockTextControl, + icon: { + path: 'M4 19h16v-2H4v2zm13-6H7v2h10v-2zM4 9v2h16V9H4zm13-4H7v2h10V5z' + }, + handlers: [ + { + 'type': 'click', + 'action': setTextAlignCenter + } + ] + } + ), + create.control( + { + name: 'Align Right', + type: 'right-align', + displayArea: 'block', + render: renderBlockTextControl, + icon: { + path: 'M20 17H4v2h16v-2zm-10-2h10v-2H10v2zM4 9v2h16V9H4zm6-2h10V5H10v2z' + }, + handlers: [ + { + 'type': 'click', + 'action': setTextAlignRight + } + ] + } + ) + ] + } ) + + if ( typeof window !== 'undefined' ) { + window.textBlock = textBlock + } + + if ( typeof module !== 'undefined' ) { + module.exports = textBlock + } +} () );