diff --git a/tinymce-per-block/src/assets/stylesheets/main.scss b/tinymce-per-block/src/assets/stylesheets/main.scss index 9bc8e97957e6d..d45f9dc9fab7a 100644 --- a/tinymce-per-block/src/assets/stylesheets/main.scss +++ b/tinymce-per-block/src/assets/stylesheets/main.scss @@ -11,6 +11,7 @@ @import '~renderers/block/block-list/style'; @import '~renderers/html/html-editor/style'; @import '~controls/editable-format-toolbar/style'; +@import '~controls/transform-block-toolbar/style'; @import '~inserter/style'; * { diff --git a/tinymce-per-block/src/blocks/embed-block/form.js b/tinymce-per-block/src/blocks/embed-block/form.js index 39ccf42e226de..dfd3b66ef55b4 100644 --- a/tinymce-per-block/src/blocks/embed-block/form.js +++ b/tinymce-per-block/src/blocks/embed-block/form.js @@ -24,7 +24,7 @@ export default class EmbedBlockForm extends Component { render() { const { block, isSelected, change, moveCursorUp, moveCursorDown, - remove, focusConfig, focus, moveBlockUp, moveBlockDown, appendBlock } = this.props; + remove, focusConfig, focus, moveBlockUp, moveBlockDown, appendBlock, unselect } = this.props; const removePrevious = () => { if ( ! block.url ) { @@ -81,7 +81,10 @@ export default class EmbedBlockForm extends Component { moveCursorDown={ moveCursorDown } splitValue={ splitValue } value={ block.caption } - onChange={ ( value ) => change( { caption: value } ) } + onChange={ ( value ) => { + change( { caption: value } ); + unselect(); + } } placeholder="Write caption" focusConfig={ focusConfig } onFocusChange={ focus } diff --git a/tinymce-per-block/src/blocks/heading-block/form.js b/tinymce-per-block/src/blocks/heading-block/form.js index 7d59ef8dcb507..15757032b8956 100644 --- a/tinymce-per-block/src/blocks/heading-block/form.js +++ b/tinymce-per-block/src/blocks/heading-block/form.js @@ -12,6 +12,7 @@ import { import InlineTextBlockForm from '../inline-text-block/form'; import EditableFormatToolbar from 'controls/editable-format-toolbar'; import BlockArrangement from 'controls/block-arrangement'; +import TransformBlockToolbar from 'controls/transform-block-toolbar'; export default class HeadingBlockForm extends Component { bindForm = ( ref ) => { @@ -32,7 +33,7 @@ export default class HeadingBlockForm extends Component { }; render() { - const { block, isSelected, moveBlockUp, moveBlockDown } = this.props; + const { block, isSelected, moveBlockUp, moveBlockDown, select, transform } = this.props; const sizes = [ { id: 'h1', icon: EditorHeading1Icon }, { id: 'h2', icon: EditorHeading2Icon }, @@ -45,6 +46,9 @@ export default class HeadingBlockForm extends Component { moveBlockUp={ moveBlockUp } moveBlockDown={ moveBlockDown } /> } { isSelected && (
+
+ +
{ sizes.map( ( { id, icon: Icon } ) =>
) } -
+
{ + return { + blockType: 'heading', + size: 'h2', + content + }; +}; + registerBlock( 'heading', { title: 'Heading', form: form, @@ -40,11 +48,11 @@ registerBlock( 'heading', { rawContent }; }, - create: () => { - return { - blockType: 'heading', - content: '', - size: 'h2' - }; - } + create: createHeadingBlockWithContent, + transformations: [ + { + blocks: [ 'text', 'quote' ], + transform: ( block ) => createHeadingBlockWithContent( block.content ) + } + ] } ); diff --git a/tinymce-per-block/src/blocks/html-block/form.js b/tinymce-per-block/src/blocks/html-block/form.js index 9ca616f7a0282..0fcfc3b7ec80e 100644 --- a/tinymce-per-block/src/blocks/html-block/form.js +++ b/tinymce-per-block/src/blocks/html-block/form.js @@ -40,7 +40,7 @@ export default class HtmlBlockForm extends Component { render() { const { block, isSelected, change, moveCursorUp, moveCursorDown, appendBlock, - mergeWithPrevious, remove, focusConfig, focus, moveBlockUp, moveBlockDown } = this.props; + mergeWithPrevious, remove, focusConfig, focus, moveBlockUp, moveBlockDown, select, unselect } = this.props; const splitValue = ( left, right ) => { change( { content: left } ); if ( right ) { @@ -72,7 +72,7 @@ export default class HtmlBlockForm extends Component {
) } -
+
change( { content: value } ) } focusConfig={ focusConfig } onFocusChange={ focus } + onType={ unselect } />
diff --git a/tinymce-per-block/src/blocks/image-block/form.js b/tinymce-per-block/src/blocks/image-block/form.js index ae6d1389bac47..d756194df5a38 100644 --- a/tinymce-per-block/src/blocks/image-block/form.js +++ b/tinymce-per-block/src/blocks/image-block/form.js @@ -20,7 +20,7 @@ export default class ImageBlockForm extends Component { render() { const { block, change, moveCursorDown, moveCursorUp, remove, appendBlock, - isSelected, focusConfig, focus, moveBlockUp, moveBlockDown } = this.props; + isSelected, focusConfig, focus, moveBlockUp, moveBlockDown, select, unselect } = this.props; const removePrevious = () => { if ( ! block.caption ) { remove(); @@ -45,25 +45,30 @@ export default class ImageBlockForm extends Component {
} - { - ! focusConfig && focus(); - } } - /> -
- change( { caption: value } ) } - placeholder="Write caption" - focusConfig={ focusConfig } - onFocusChange={ focus } +
+ { + ! focusConfig && focus(); + } } /> +
+ { + change( { caption: value } ); + unselect(); + } } + placeholder="Write caption" + focusConfig={ focusConfig } + onFocusChange={ focus } + /> +
); diff --git a/tinymce-per-block/src/blocks/inline-text-block/form.js b/tinymce-per-block/src/blocks/inline-text-block/form.js index cecb549288363..fe3bb430b4d44 100644 --- a/tinymce-per-block/src/blocks/inline-text-block/form.js +++ b/tinymce-per-block/src/blocks/inline-text-block/form.js @@ -37,7 +37,7 @@ export default class InlineTextBlockForm extends Component { render() { const { block, change, moveCursorUp, moveCursorDown, appendBlock, - mergeWithPrevious, remove, setToolbarState, focus, focusConfig } = this.props; + mergeWithPrevious, remove, setToolbarState, focus, focusConfig, unselect } = this.props; const splitValue = ( left, right ) => { change( { content: left } ); @@ -65,6 +65,7 @@ export default class InlineTextBlockForm extends Component { setToolbarState={ setToolbarState } focusConfig={ focusConfig } onFocusChange={ focus } + onType={ unselect } inline single /> diff --git a/tinymce-per-block/src/blocks/quote-block/form.js b/tinymce-per-block/src/blocks/quote-block/form.js index bc53bfec94a0c..0f7ecfcf2b3cc 100644 --- a/tinymce-per-block/src/blocks/quote-block/form.js +++ b/tinymce-per-block/src/blocks/quote-block/form.js @@ -3,11 +3,13 @@ */ import { createElement, Component } from 'wp-elements'; -import { EditableComponent, EnhancedInputComponent } from 'wp-blocks'; -import { serialize } from 'serializers/block'; -import { parse } from 'parsers/block'; +/** + * Internal dependencies + */ +import { EditableComponent } from 'wp-blocks'; import EditableFormatToolbar from 'controls/editable-format-toolbar'; import BlockArrangement from 'controls/block-arrangement'; +import TransformBlockToolbar from 'controls/transform-block-toolbar'; export default class QuoteBlockForm extends Component { bindContent = ( ref ) => { @@ -59,7 +61,7 @@ export default class QuoteBlockForm extends Component { render() { const { block, change, moveCursorUp, moveCursorDown, remove, mergeWithPrevious, appendBlock, isSelected, focusConfig, focus, - moveBlockUp, moveBlockDown } = this.props; + moveBlockUp, moveBlockDown, select, unselect, transform } = this.props; const splitValue = ( left, right ) => { change( { cite: left } ); appendBlock( { @@ -78,13 +80,17 @@ export default class QuoteBlockForm extends Component { moveBlockUp={ moveBlockUp } moveBlockDown={ moveBlockDown } /> } { isSelected &&
+
+ +
+
} -
+
focus( Object.assign( { input: 'content' }, config ) ) } + onType={ unselect } inline />
@@ -113,6 +120,7 @@ export default class QuoteBlockForm extends Component { setToolbarState={ focusInput === 'cite' ? this.setToolbarState : undefined } focusConfig={ focusInput === 'cite' ? focusConfig : null } onFocusChange={ ( config ) => focus( Object.assign( { input: 'cite' }, config ) ) } + onType={ unselect } inline single /> diff --git a/tinymce-per-block/src/blocks/quote-block/index.js b/tinymce-per-block/src/blocks/quote-block/index.js index 1935ed58da72a..ef2812c77c458 100644 --- a/tinymce-per-block/src/blocks/quote-block/index.js +++ b/tinymce-per-block/src/blocks/quote-block/index.js @@ -11,6 +11,14 @@ import { */ import form from './form'; +const createQuoteBlockWithContent = ( content = '' ) => { + return { + blockType: 'quote', + cite: '', + content + }; +}; + registerBlock( 'quote', { title: 'Quote', form: form, @@ -55,11 +63,11 @@ registerBlock( 'quote', { rawContent }; }, - create: () => { - return { - blockType: 'quote', - cite: '', - content: '' - }; - } + create: createQuoteBlockWithContent, + transformations: [ + { + blocks: [ 'text', 'heading' ], + transform: ( block ) => createQuoteBlockWithContent( block.content ) + } + ] } ); diff --git a/tinymce-per-block/src/blocks/text-block/form.js b/tinymce-per-block/src/blocks/text-block/form.js index b5b3f288257c8..007c56d273ee3 100644 --- a/tinymce-per-block/src/blocks/text-block/form.js +++ b/tinymce-per-block/src/blocks/text-block/form.js @@ -6,6 +6,7 @@ import { createElement, Component } from 'wp-elements'; import EditableFormatToolbar from 'controls/editable-format-toolbar'; import AlignmentToolbar from 'controls/alignment-toolbar'; import BlockArrangement from 'controls/block-arrangement'; +import TransformBlockToolbar from 'controls/transform-block-toolbar'; import InlineTextBlockForm from 'blocks/inline-text-block/form'; import InserterButton from 'inserter/button'; @@ -28,7 +29,7 @@ export default class TextBlockForm extends Component { }; render() { - const { block, isSelected, focusConfig, moveBlockUp, moveBlockDown, replace } = this.props; + const { block, isSelected, focusConfig, moveBlockUp, moveBlockDown, replace, select, transform } = this.props; const selectedTextAlign = block.align || 'left'; const style = { textAlign: selectedTextAlign @@ -40,6 +41,10 @@ export default class TextBlockForm extends Component { moveBlockUp={ moveBlockUp } moveBlockDown={ moveBlockDown } /> } { isSelected &&
+
+ +
+
@@ -50,7 +55,7 @@ export default class TextBlockForm extends Component {
} -
+
{ ! block.content.trim() && ! isSelected && focusConfig && replace( id ) } /> } diff --git a/tinymce-per-block/src/blocks/text-block/index.js b/tinymce-per-block/src/blocks/text-block/index.js index 147d437bfbd30..82a41f93c8c6b 100644 --- a/tinymce-per-block/src/blocks/text-block/index.js +++ b/tinymce-per-block/src/blocks/text-block/index.js @@ -9,6 +9,14 @@ import { EditorParagraphIcon } from 'dashicons'; */ import form from './form'; +const createTextBlockWithContent = ( content = '' ) => { + return { + blockType: 'text', + align: 'no-align', + content + }; +}; + registerBlock( 'text', { title: 'Text', form: form, @@ -44,11 +52,11 @@ registerBlock( 'text', { rawContent }; }, - create: () => { - return { - blockType: 'text', - content: '', - align: 'no-align' - }; - } + create: () => createTextBlockWithContent, + transformations: [ + { + blocks: [ 'heading', 'quote' ], + transform: ( block ) => createTextBlockWithContent( block.content ) + } + ] } ); diff --git a/tinymce-per-block/src/controls/block-arrangement.js b/tinymce-per-block/src/controls/block-arrangement.js index 84c8afe3f8903..d53067c205844 100644 --- a/tinymce-per-block/src/controls/block-arrangement.js +++ b/tinymce-per-block/src/controls/block-arrangement.js @@ -8,22 +8,14 @@ import { ArrowDownAlt2Icon, ArrowUpAlt2Icon } from 'dashicons'; export default function BlockArrangement( { block, moveBlockUp, moveBlockDown } ) { const blockDefinition = getBlock( block.blockType ); const Icon = blockDefinition.icon; - const onMoveUp = ( event ) => { - event.stopPropagation(); - moveBlockUp(); - }; - const onMoveDown = ( event ) => { - event.stopPropagation(); - moveBlockDown(); - }; return (
- -
diff --git a/tinymce-per-block/src/controls/editable-format-toolbar/index.js b/tinymce-per-block/src/controls/editable-format-toolbar/index.js index 4d6a56c2741d6..3e10f88a8148f 100644 --- a/tinymce-per-block/src/controls/editable-format-toolbar/index.js +++ b/tinymce-per-block/src/controls/editable-format-toolbar/index.js @@ -51,8 +51,7 @@ export default class EditableFormatToolbar extends Component { } ); } - toggleLinkModal = ( event ) => { - event.stopPropagation(); + toggleLinkModal = () => { this.setState( { linkModal: { open: ! this.state.linkModal.open, diff --git a/tinymce-per-block/src/controls/transform-block-toolbar/index.js b/tinymce-per-block/src/controls/transform-block-toolbar/index.js new file mode 100644 index 0000000000000..873df1cb6b0dc --- /dev/null +++ b/tinymce-per-block/src/controls/transform-block-toolbar/index.js @@ -0,0 +1,53 @@ +/** + * External dependencies + */ +import { createElement, Component } from 'wp-elements'; +import { getBlock, getBlocks } from 'wp-blocks'; +import { reduce } from 'lodash'; + +export default class TransformBlockToolbar extends Component { + state = { + open: false + }; + + toggleMenu = () => { + this.setState( { + open: ! this.state.open + } ); + }; + + render() { + const blockDefinition = getBlock( this.props.blockType ); + const allowedBlocks = reduce( getBlocks(), ( memo, block ) => { + const transformation = block.transformations && + block.transformations.find( t => t.blocks.indexOf( this.props.blockType ) !== -1 ); + return transformation ? memo.concat( [ block ] ) : memo; + }, [] ); + if ( ! allowedBlocks.length ) { + return null; + } + const BlockIcon = blockDefinition.icon; + + return ( +
+ + { this.state.open && +
+ { allowedBlocks.map( ( { id, title, icon: Icon } ) => ( +
this.props.onTransform( id ) } + className="transform-block-toolbar__menu-item" + > + { title } +
+ ) ) } +
+ } +
+ ); + } +} diff --git a/tinymce-per-block/src/controls/transform-block-toolbar/style.scss b/tinymce-per-block/src/controls/transform-block-toolbar/style.scss new file mode 100644 index 0000000000000..0db6481f0d5c5 --- /dev/null +++ b/tinymce-per-block/src/controls/transform-block-toolbar/style.scss @@ -0,0 +1,71 @@ +.transform-block-toolbar .block-list__block-control { + width: auto; + + .dashicon { + display: inline-block; + vertical-align: middle; + } + + .transform-block-toolbar__arrow { + display: inline-block; + vertical-align: middle; + border: 6px dashed $gray-dark-900; + height: 0; + line-height: 0; + width: 0; + z-index: 1; + border-top-style: solid; + border-bottom: none; + border-left-color: transparent; + border-right-color: transparent; + } +} + +.transform-block-toolbar__menu { + position: absolute; + top: 50px; + box-shadow: 0px 3px 20px rgba( 18, 24, 30, .1 ), 0px 1px 3px rgba( 18, 24, 30, .1 ); + border: 1px solid #e0e5e9; + background: #fff; + z-index: 1; + + input { + font-size: 13px; + } + + &:before { + content: ''; + border: 10px dashed #e0e5e9; + height: 0; + line-height: 0; + position: absolute; + width: 0; + top: -10px; + left: 10px; + border-bottom-style: solid; + border-top: none; + border-left-color: transparent; + border-right-color: transparent; + } + + + .transform-block-toolbar__menu-item { + padding: 8px; + font-size: 12px; + cursor: pointer; + display: flex; + align-items: center; + + &.is-active, + &:hover { + background: #f0f2f4; + } + + .dashicon { + margin-right: 8px; + fill: #191e23; + width: 24px; + height: 24px; + } + } +} diff --git a/tinymce-per-block/src/external/dashicons/icons/image-full-width.js b/tinymce-per-block/src/external/dashicons/icons/image-full-width.js index 31b22d7843726..064652427a33f 100644 --- a/tinymce-per-block/src/external/dashicons/icons/image-full-width.js +++ b/tinymce-per-block/src/external/dashicons/icons/image-full-width.js @@ -5,7 +5,7 @@ import { createElement } from 'wp-elements'; // This is a gridicon export default () => ( - + ); diff --git a/tinymce-per-block/src/external/wp-blocks/editable/index.js b/tinymce-per-block/src/external/wp-blocks/editable/index.js index 291d6561dac03..ecc51fba892c7 100644 --- a/tinymce-per-block/src/external/wp-blocks/editable/index.js +++ b/tinymce-per-block/src/external/wp-blocks/editable/index.js @@ -35,6 +35,7 @@ export default class EditableComponent extends Component { onChange: () => {}, splitValue: () => {}, onFocusChange: () => {}, + onType: () => {}, initialContent: '', inline: false, single: false, @@ -197,6 +198,7 @@ export default class EditableComponent extends Component { editor.on( 'paste', this.onPaste ); editor.on( 'nodechange', this.syncToolbar ); editor.on( 'focusin', this.onFocus ); + editor.on( 'paste keydown undo redo', this.props.onType ); }; onInit = () => { diff --git a/tinymce-per-block/src/inserter/button.js b/tinymce-per-block/src/inserter/button.js index e4c3f5ed25159..add7902fc39f7 100644 --- a/tinymce-per-block/src/inserter/button.js +++ b/tinymce-per-block/src/inserter/button.js @@ -21,7 +21,6 @@ export default class InserterButtonComponent extends Component { toggleInserter = ( event ) => { event.preventDefault(); - event.stopPropagation(); this.setState( { opened: ! this.state.opened } ); diff --git a/tinymce-per-block/src/inserter/index.js b/tinymce-per-block/src/inserter/index.js index 5ef31c9d4fed4..e004b77c345ae 100644 --- a/tinymce-per-block/src/inserter/index.js +++ b/tinymce-per-block/src/inserter/index.js @@ -22,13 +22,12 @@ export default class InserterComponent extends Component { render() { const addBlock = ( id ) => () => this.props.onAdd( id ); - const stopPropagation = ( event ) => event.stopPropagation(); const blocks = getBlocks().filter( ( block ) => block.title.toLowerCase().indexOf( this.state.filterValue.toLowerCase() ) !== -1 ); return ( -
+
diff --git a/tinymce-per-block/src/inserter/style.scss b/tinymce-per-block/src/inserter/style.scss index 315b5980248b5..feb24e16c8a86 100644 --- a/tinymce-per-block/src/inserter/style.scss +++ b/tinymce-per-block/src/inserter/style.scss @@ -33,7 +33,6 @@ background: #fff; .inserter__arrow { - content: ''; border: 10px dashed #e0e5e9; height: 0; line-height: 0; diff --git a/tinymce-per-block/src/renderers/block/block-list/_style.scss b/tinymce-per-block/src/renderers/block/block-list/_style.scss index b4e82ef8cc681..f604520211cea 100644 --- a/tinymce-per-block/src/renderers/block/block-list/_style.scss +++ b/tinymce-per-block/src/renderers/block/block-list/_style.scss @@ -31,7 +31,7 @@ position: absolute; left: 0; top: -36px; - z-index: 1; + z-index: 2; } .block-list__block-controls-group { diff --git a/tinymce-per-block/src/renderers/block/block-list/block.js b/tinymce-per-block/src/renderers/block/block-list/block.js index 552dd94a6a5cd..4d408c4f90546 100644 --- a/tinymce-per-block/src/renderers/block/block-list/block.js +++ b/tinymce-per-block/src/renderers/block/block-list/block.js @@ -80,6 +80,11 @@ export default class BlockListBlock extends Component { type: 'select' } ); }, + unselect() { + executeCommand( { + type: 'unselect' + } ); + }, moveBlockUp() { executeCommand( { type: 'moveBlockUp' @@ -95,6 +100,12 @@ export default class BlockListBlock extends Component { type: 'replace', id } ); + }, + transform( id ) { + executeCommand( { + type: 'transform', + id + } ); } }; @@ -104,7 +115,6 @@ export default class BlockListBlock extends Component { tabIndex={ tabIndex } onFocus={ onFocus } className={ classes } - onClick={ state.select } >
{ const createdBlock = commandBlock @@ -198,6 +197,24 @@ class BlockList extends Component { ]; this.onChange( newBlocks ); this.focus( newBlockUid ); + }, + transform: ( { id } ) => { + const newBlockUid = uniqueId(); + const currentBlockType = content[ index ].blockType; + const blockDefinition = getBlock( id ); + const transformation = blockDefinition.transformations + .find( t => t.blocks.indexOf( currentBlockType ) !== -1 ); + if ( ! transformation ) { + return; + } + const newBlock = Object.assign( { uid: newBlockUid }, transformation.transform( content[ index ] ) ); + const newBlocks = [ + ...this.content.slice( 0, index ), + newBlock, + ...this.content.slice( index + 1 ) + ]; + this.onChange( newBlocks ); + this.focus( newBlockUid ); } };