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 @@ Image -
- - - - - - - -
@@ -85,6 +62,9 @@

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/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 new file mode 100644 index 00000000000000..78bd51223d7353 --- /dev/null +++ b/test/tests.js @@ -0,0 +1,377 @@ +/** + * Test the blocks API. + */ +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() { + 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', + render: undefined, + icon: undefined, + handlers: undefined + } + 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 ) + } ) + } ) + + /** + * textBlock will be deleted soon. + */ + 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: [] + } + + // Override controls so function calls do not have to match. + textBlock.controls = []; + let actual = 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 = [ textBlock ] + // Blocks() is a monad that sprinkles on some extra methods. Just compare the arrays. + let actual = blocks.registerBlock( 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 = [ textBlock ] + // Blocks() is a monad that sprinkles on some extra methods. Just compare the arrays. + let actual = blocks.registerBlock( textBlock ).registerBlock( textBlock ).slice( 0 ) + + assert.deepEqual( actual, expected ) + } ) + + it( 'should have two blocks.', function() { + let Gutenberg = gutenberg() + let blocks = Gutenberg.blocks + + 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( 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 imageBlock = Object.assign( {}, textBlock ); + imageBlock.type = 'wp-image' + + let expected = [ textBlock, imageBlock ] + 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 = [ 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 = [ 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 = 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 = 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 = [ 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. + let expected = [] + let actual = blocks.unregisterBlocks( someBlocksToTakeAway ).splice( 0 ) + + 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 + } +} () );