From 513bd1d52686ebc73e6b8e21ee19239a3025fa02 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Wed, 19 Apr 2017 10:16:12 +0100 Subject: [PATCH] Editable: Double Enter splits the current paragraph into two blocks (#409) --- blocks/components/editable/index.js | 32 ++++++++++++++++++++++ blocks/components/editable/style.scss | 14 ++++++++-- blocks/library/text/index.js | 39 +++++++++++++++++++-------- editor/modes/visual-editor/block.js | 13 +++++++-- editor/modes/visual-editor/style.scss | 5 ---- editor/state.js | 14 +++++----- editor/test/state.js | 27 +++++++++++++++++++ languages/gutenberg.pot | 8 +++--- post-content.js | 6 ++--- 9 files changed, 125 insertions(+), 33 deletions(-) diff --git a/blocks/components/editable/index.js b/blocks/components/editable/index.js index ff37f8692f98a5..0e2e15a85cb321 100644 --- a/blocks/components/editable/index.js +++ b/blocks/components/editable/index.js @@ -2,6 +2,7 @@ * External dependencies */ import classnames from 'classnames'; +import { last } from 'lodash'; /** * Internal dependencies @@ -14,6 +15,7 @@ export default class Editable extends wp.element.Component { this.onInit = this.onInit.bind( this ); this.onSetup = this.onSetup.bind( this ); this.onChange = this.onChange.bind( this ); + this.onNewBlock = this.onNewBlock.bind( this ); this.bindNode = this.bindNode.bind( this ); } @@ -42,6 +44,7 @@ export default class Editable extends wp.element.Component { this.editor = editor; editor.on( 'init', this.onInit ); editor.on( 'focusout', this.onChange ); + editor.on( 'NewBlock', this.onNewBlock ); } onInit() { @@ -58,6 +61,35 @@ export default class Editable extends wp.element.Component { this.props.onChange( value ); } + onNewBlock() { + if ( this.props.tagName || ! this.props.onSplit ) { + return; + } + + // Getting the content before and after the cursor + const childNodes = Array.from( this.editor.getBody().childNodes ); + const splitIndex = childNodes.indexOf( this.editor.selection.getStart() ); + const getHtml = ( nodes ) => nodes.reduce( ( memo, node ) => memo + node.outerHTML, '' ); + const beforeNodes = childNodes.slice( 0, splitIndex ); + const lastNodeBeforeCursor = last( beforeNodes ); + // Avoid splitting on single enter + if ( + ! lastNodeBeforeCursor || + beforeNodes.length < 2 || + !! lastNodeBeforeCursor.textContent + ) { + return; + } + const before = getHtml( beforeNodes.slice( 0, beforeNodes.length - 1 ) ); + const after = getHtml( childNodes.slice( splitIndex ) ); + + // Splitting into two blocks + this.editor.setContent( this.props.value || '' ); + const hasAfter = !! childNodes.slice( splitIndex ) + .reduce( ( memo, node ) => memo + node.textContent, '' ); + this.props.onSplit( before, hasAfter ? after : '' ); + } + bindNode( ref ) { this.node = ref; } diff --git a/blocks/components/editable/style.scss b/blocks/components/editable/style.scss index 1a143b68d8ea17..468315ca41743b 100644 --- a/blocks/components/editable/style.scss +++ b/blocks/components/editable/style.scss @@ -1,3 +1,13 @@ -.blocks-editable:focus { - outline: none; +.blocks-editable { + > p:first-child { + margin-top: 0; + } + + > p:last-child { + margin-bottom: 0; + } + + &:focus { + outline: none; + } } diff --git a/blocks/library/text/index.js b/blocks/library/text/index.js index 3d3033d2181933..540c8c01d232a9 100644 --- a/blocks/library/text/index.js +++ b/blocks/library/text/index.js @@ -1,10 +1,13 @@ /** * Internal dependencies */ -import { registerBlock, query } from 'api'; +import { registerBlock, query as hpq } from 'api'; import Editable from 'components/editable'; -const { html, prop } = query; +const { html, parse, query } = hpq; + +const fromValueToParagraphs = ( value ) => value ? value.map( ( paragraph ) => `

${ paragraph }

` ).join( '' ) : ''; +const fromParagraphsToValue = ( paragraphs ) => parse( paragraphs, query( 'p', html() ) ); registerBlock( 'core/text', { title: wp.i18n.__( 'Text' ), @@ -14,8 +17,7 @@ registerBlock( 'core/text', { category: 'common', attributes: { - content: html( 'p' ), - align: prop( 'p', 'style.textAlign' ) + content: query( 'p', html() ), }, controls: [ @@ -45,15 +47,22 @@ registerBlock( 'core/text', { } ], - edit( { attributes, setAttributes } ) { + edit( { attributes, setAttributes, insertBlockAfter } ) { const { content, align } = attributes; return ( setAttributes( { content: value } ) } + value={ fromValueToParagraphs( content ) } + onChange={ ( paragraphs ) => setAttributes( { + content: fromParagraphsToValue( paragraphs ) + } ) } style={ align ? { textAlign: align } : null } + onSplit={ ( before, after ) => { + setAttributes( { content: fromParagraphsToValue( before ) } ); + insertBlockAfter( wp.blocks.createBlock( 'core/text', { + content: fromParagraphsToValue( after ) + } ) ); + } } /> ); }, @@ -61,10 +70,18 @@ registerBlock( 'core/text', { save( { attributes } ) { const { align, content } = attributes; + // Todo: Remove the temporary
wrapper once the serializer supports returning an array return ( -

+

+ { content && content.map( ( paragraph, i ) => ( +

+ ) ) } +

); } } ); diff --git a/editor/modes/visual-editor/block.js b/editor/modes/visual-editor/block.js index 49eef6a1d2dd3c..bf8b1bf8e105e4 100644 --- a/editor/modes/visual-editor/block.js +++ b/editor/modes/visual-editor/block.js @@ -30,7 +30,7 @@ function VisualEditorBlock( props ) { 'is-hovered': isHovered } ); - const { onChange, onSelect, onDeselect, onMouseEnter, onMouseLeave } = props; + const { onChange, onSelect, onDeselect, onMouseEnter, onMouseLeave, onInsertAfter } = props; function setAttributes( attributes ) { onChange( { @@ -75,7 +75,9 @@ function VisualEditorBlock( props ) { + setAttributes={ setAttributes } + insertBlockAfter={ onInsertAfter } + />
); /* eslint-enable jsx-a11y/no-static-element-interactions */ @@ -122,6 +124,13 @@ export default connect( hovered: false, uid: ownProps.uid } ); + }, + onInsertAfter( block ) { + dispatch( { + type: 'INSERT_BLOCK', + after: ownProps.uid, + block + } ); } } ) )( VisualEditorBlock ); diff --git a/editor/modes/visual-editor/style.scss b/editor/modes/visual-editor/style.scss index 60c27fb03eac4e..04ef444ebc526b 100644 --- a/editor/modes/visual-editor/style.scss +++ b/editor/modes/visual-editor/style.scss @@ -8,11 +8,6 @@ font-size: $editor-font-size; line-height: $editor-line-height; } - - p { - margin-top: 0; - margin-bottom: 0; - } } .editor-visual-editor__block { diff --git a/editor/state.js b/editor/state.js index 11dcf707a7128f..d5825bf2aa1bab 100644 --- a/editor/state.js +++ b/editor/state.js @@ -48,6 +48,14 @@ export const blocks = combineUndoableReducers( { case 'REPLACE_BLOCKS': return action.blockNodes.map( ( { uid } ) => uid ); + case 'INSERT_BLOCK': + const position = action.after ? state.indexOf( action.after ) + 1 : state.length; + return [ + ...state.slice( 0, position ), + action.block.uid, + ...state.slice( position ) + ]; + case 'MOVE_BLOCK_UP': if ( action.uid === state[ 0 ] ) { return state; @@ -73,12 +81,6 @@ export const blocks = combineUndoableReducers( { action.uid, ...state.slice( index + 2 ) ]; - - case 'INSERT_BLOCK': - return [ - ...state, - action.block.uid - ]; } return state; diff --git a/editor/test/state.js b/editor/test/state.js index 0174602989ae8e..6c3983f2f6f476 100644 --- a/editor/test/state.js +++ b/editor/test/state.js @@ -235,6 +235,33 @@ describe( 'state', () => { expect( state ).to.equal( 'chicken' ); } ); + + it( 'should insert after the specified block uid', () => { + const original = blocks( undefined, { + type: 'REPLACE_BLOCKS', + blockNodes: [ { + uid: 'kumquat', + blockType: 'core/test-block', + attributes: {} + }, { + uid: 'loquat', + blockType: 'core/test-block', + attributes: {} + } ] + } ); + + const state = blocks( original, { + type: 'INSERT_BLOCK', + after: 'kumquat', + block: { + uid: 'persimmon', + blockType: 'core/freeform' + } + } ); + + expect( Object.keys( state.byUid ) ).to.have.lengthOf( 3 ); + expect( state.order ).to.eql( [ 'kumquat', 'persimmon', 'loquat' ] ); + } ); } ); describe( 'mode()', () => { diff --git a/languages/gutenberg.pot b/languages/gutenberg.pot index 7cb00bf18c731a..88a6bcbc8b5f94 100644 --- a/languages/gutenberg.pot +++ b/languages/gutenberg.pot @@ -28,17 +28,17 @@ msgid "List" msgstr "" #: blocks/library/list/index.js:25 -#: blocks/library/text/index.js:24 +#: blocks/library/text/index.js:26 msgid "Align left" msgstr "" #: blocks/library/list/index.js:33 -#: blocks/library/text/index.js:32 +#: blocks/library/text/index.js:34 msgid "Align center" msgstr "" #: blocks/library/list/index.js:41 -#: blocks/library/text/index.js:40 +#: blocks/library/text/index.js:42 msgid "Align right" msgstr "" @@ -50,7 +50,7 @@ msgstr "" msgid "Quote" msgstr "" -#: blocks/library/text/index.js:10 +#: blocks/library/text/index.js:13 msgid "Text" msgstr "" diff --git a/post-content.js b/post-content.js index 844d28282abd2d..152d43f9ec3507 100644 --- a/post-content.js +++ b/post-content.js @@ -9,7 +9,7 @@ window._wpGutenbergPost = { '', '', - '

I imagine prior to the launch of the iPod, or the iPhone, there were teams saying the same thing: the copy + paste guys are so close to being ready and we know Walt Mossberg is going to ding us for this so let\'s just not ship to the manufacturers in China for just a few more weeks… The Apple teams were probably embarrassed. But if you\'re not embarrassed when you ship your first version you waited too long.

', + '

I imagine prior to the launch of the iPod, or the iPhone, there were teams saying the same thing: the copy + paste guys are so close to being ready and we know Walt Mossberg is going to ding us for this so let\'s just not ship to the manufacturers in China for just a few more weeks… The Apple teams were probably embarrassed. But if you\'re not embarrassed when you ship your first version you waited too long.

', '', '', @@ -17,7 +17,7 @@ window._wpGutenbergPost = { '', '', - '

A beautiful thing about Apple is how quickly they obsolete their own products. I imagine this also makes the discipline of getting things out there easier. Like I mentioned before, the longer it’s been since the last release the more pressure there is, but if you know that if your bit of code doesn’t make this version but there’s the +0.1 coming out in 6 weeks, then it’s not that bad. It’s like flights from San Francisco to LA, if you miss one you know there’s another one an hour later so it’s not a big deal. Amazon has done a fantastic job of this with the Kindle as well, with a new model every year.

', + '

A beautiful thing about Apple is how quickly they obsolete their own products. I imagine this also makes the discipline of getting things out there easier. Like I mentioned before, the longer it’s been since the last release the more pressure there is, but if you know that if your bit of code doesn’t make this version but there’s the +0.1 coming out in 6 weeks, then it’s not that bad. It’s like flights from San Francisco to LA, if you miss one you know there’s another one an hour later so it’s not a big deal. Amazon has done a fantastic job of this with the Kindle as well, with a new model every year.

', '', '', @@ -29,7 +29,7 @@ window._wpGutenbergPost = { '', '', - '

By shipping early and often you have the unique competitive advantage of hearing from real people what they think of your work, which in best case helps you anticipate market direction, and in worst case gives you a few people rooting for you that you can email when your team pivots to a new idea. Nothing can recreate the crucible of real usage.

', + '

By shipping early and often you have the unique competitive advantage of hearing from real people what they think of your work, which in best case helps you anticipate market direction, and in worst case gives you a few people rooting for you that you can email when your team pivots to a new idea. Nothing can recreate the crucible of real usage.

', '', '',