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
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
245 changes: 245 additions & 0 deletions block-api.js
Original file line number Diff line number Diff line change
@@ -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;
};
}
} () )
83 changes: 57 additions & 26 deletions blocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -31,6 +28,11 @@ var config = {
'heading': [ 'heading', 'text' ],
'image': [ 'image' ],
'default': []
},
blockTypes: {
'text': 'wp-text',
'paragraph': 'wp-text',
'image': 'wp-image'
}
};

Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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() {
Expand Down
Loading