From bde767cbee95a8648b2e1cbbf61df2f1daaf8408 Mon Sep 17 00:00:00 2001 From: Tim Gardner Date: Thu, 19 Oct 2017 17:29:15 +1000 Subject: [PATCH 1/4] Created simple inline style formatter --- blocks/editable/format-toolbar/index.js | 34 ++++++++++++----------- blocks/editable/formatter.js | 11 ++++++++ blocks/editable/index.js | 36 ++++++++++++++++++++----- blocks/library/paragraph/index.js | 5 ++++ 4 files changed, 64 insertions(+), 22 deletions(-) create mode 100644 blocks/editable/formatter.js diff --git a/blocks/editable/format-toolbar/index.js b/blocks/editable/format-toolbar/index.js index 85f1e5e97fbaf..e85f712bd2fb5 100644 --- a/blocks/editable/format-toolbar/index.js +++ b/blocks/editable/format-toolbar/index.js @@ -32,6 +32,11 @@ const FORMATTING_CONTROLS = [ title: __( 'Strikethrough' ), format: 'strikethrough', }, + { + icon: 'admin-links', + title: __( 'Link' ), + format: 'link', + }, ]; // Default controls shown if no `enabledControls` prop provided @@ -131,8 +136,12 @@ class FormatToolbar extends Component { } } + isFormatActive( format ) { + return this.props.formats[ format ] && this.props.formats[ format ].isActive; + } + render() { - const { formats, focusPosition, enabledControls = DEFAULT_CONTROLS } = this.props; + const { formats, focusPosition, enabledControls = DEFAULT_CONTROLS, customControls = [] } = this.props; const { isAddingLink, isEditingLink, newLinkValue, settingsVisible, opensInNewWindow } = this.state; const linkStyle = focusPosition ? { position: 'absolute', ...focusPosition } @@ -140,11 +149,15 @@ class FormatToolbar extends Component { const toolbarControls = FORMATTING_CONTROLS .filter( control => enabledControls.indexOf( control.format ) !== -1 ) - .map( ( control ) => ( { - ...control, - onClick: this.toggleFormat( control.format ), - isActive: !! formats[ control.format ], - } ) ); + .concat( customControls ) + .map( ( control ) => { + const isLink = control.format === 'link'; + return { + ...control, + onClick: isLink ? this.addLink : this.toggleFormat( control.format ), + isActive: this.isFormatActive( control.format ) || ( isLink && isAddingLink ), + }; + } ); const linkSettings = settingsVisible && (
@@ -155,15 +168,6 @@ class FormatToolbar extends Component {
); - if ( enabledControls.indexOf( 'link' ) !== -1 ) { - toolbarControls.push( { - icon: 'admin-links', - title: __( 'Link' ), - onClick: this.addLink, - isActive: isAddingLink || !! formats.link, - } ); - } - return (
diff --git a/blocks/editable/formatter.js b/blocks/editable/formatter.js new file mode 100644 index 0000000000000..7715e0fe73ec9 --- /dev/null +++ b/blocks/editable/formatter.js @@ -0,0 +1,11 @@ +export function createInlineStyleFormatter( name, icon, title, style ) { + return { + format: name, + icon, + title, + formatter: { + inline: 'span', + styles: style, + }, + }; +} diff --git a/blocks/editable/index.js b/blocks/editable/index.js index bd30109df0560..aaf7688d5c08f 100644 --- a/blocks/editable/index.js +++ b/blocks/editable/index.js @@ -13,6 +13,8 @@ import { find, defer, noop, + map, + pick, } from 'lodash'; import { nodeListToReact } from 'dom-react'; import 'element-closest'; @@ -58,6 +60,16 @@ function isLinkBoundary( fragment ) { fragment.childNodes[ 0 ].text[ 0 ] === '\uFEFF'; } +function getLinkProperties( element ) { + return { value: element.getAttribute( 'href' ) || '', target: element.getAttribute( 'target' ) || '', node: element }; +} + +function getFormatProperties( formatName, parents ) { + return formatName === 'link' ? getLinkProperties( find( parents, node => node.nodeName.toLowerCase() === 'a' ) ) : {}; +} + +const FORMATS = [ 'bold', 'italic', 'strikethrough', 'link' ]; + export default class Editable extends Component { constructor( props ) { super( ...arguments ); @@ -143,6 +155,13 @@ export default class Editable extends Component { onInit() { this.updateFocus(); + this.registerCustomFormatters(); + } + + registerCustomFormatters() { + forEach( this.props.formatters, ( formatter ) => { + this.editor.formatter.register( formatter.format, formatter.formatter ); + } ); } onFocus() { @@ -474,12 +493,14 @@ export default class Editable extends Component { onNodeChange( { parents } ) { const formats = {}; - const link = find( parents, ( node ) => node.nodeName.toLowerCase() === 'a' ); - if ( link ) { - formats.link = { value: link.getAttribute( 'href' ) || '', target: link.getAttribute( 'target' ) || '', node: link }; - } - const activeFormats = this.editor.formatter.matchAll( [ 'bold', 'italic', 'strikethrough' ] ); - activeFormats.forEach( ( activeFormat ) => formats[ activeFormat ] = true ); + const formatNames = this.props.formattingControls || FORMATS; + + forEach( this.editor.formatter.matchAll( formatNames ), ( activeFormat ) => { + formats[ activeFormat ] = { + isActive: true, + ...getFormatProperties( activeFormat, parents ), + }; + } ); const focusPosition = this.getFocusPosition(); this.setState( { formats, focusPosition, selectedNodeId: this.state.selectedNodeId + 1 } ); @@ -550,7 +571,7 @@ export default class Editable extends Component { } isFormatActive( format ) { - return !! this.state.formats[ format ]; + return this.state.formats[ format ] && this.state.formats[ format ].isActive; } removeFormat( format ) { @@ -620,6 +641,7 @@ export default class Editable extends Component { formats={ this.state.formats } onChange={ this.changeFormats } enabledControls={ formattingControls } + customControls={ map( this.props.formatters, ( formatter ) => pick( formatter, [ 'format', 'title', 'icon' ] ) ) } /> ); diff --git a/blocks/library/paragraph/index.js b/blocks/library/paragraph/index.js index b3550f18d26f6..721c2e3973e88 100644 --- a/blocks/library/paragraph/index.js +++ b/blocks/library/paragraph/index.js @@ -20,6 +20,7 @@ import BlockAlignmentToolbar from '../../block-alignment-toolbar'; import BlockControls from '../../block-controls'; import BlockAutocomplete from '../../block-autocomplete'; import Editable from '../../editable'; +import { createInlineStyleFormatter } from '../../editable/formatter'; import InspectorControls from '../../inspector-controls'; import ToggleControl from '../../inspector-controls/toggle-control'; import RangeControl from '../../inspector-controls/range-control'; @@ -183,6 +184,10 @@ registerBlockType( 'core/paragraph', { onMerge={ mergeBlocks } onReplace={ onReplace } placeholder={ placeholder || __( 'New Paragraph' ) } + formattingControls={ [ 'bold', 'italic', 'strikethrough', 'link', 'red' ] } + formatters={ [ + createInlineStyleFormatter( 'red', 'hammer', 'Red', { color: 'red' } ), + ] } /> , ]; From 80ab769c78d5642e8606945c2cc3eb0aecc5f0fc Mon Sep 17 00:00:00 2001 From: Tim Gardner Date: Fri, 20 Oct 2017 15:26:09 +1000 Subject: [PATCH 2/4] Changes for code review --- blocks/editable/index.js | 54 +++++++++++++++++++++---------- blocks/library/paragraph/index.js | 2 +- 2 files changed, 38 insertions(+), 18 deletions(-) diff --git a/blocks/editable/index.js b/blocks/editable/index.js index aaf7688d5c08f..3cdb2a2cf3f00 100644 --- a/blocks/editable/index.js +++ b/blocks/editable/index.js @@ -13,8 +13,6 @@ import { find, defer, noop, - map, - pick, } from 'lodash'; import { nodeListToReact } from 'dom-react'; import 'element-closest'; @@ -60,15 +58,17 @@ function isLinkBoundary( fragment ) { fragment.childNodes[ 0 ].text[ 0 ] === '\uFEFF'; } -function getLinkProperties( element ) { - return { value: element.getAttribute( 'href' ) || '', target: element.getAttribute( 'target' ) || '', node: element }; -} - function getFormatProperties( formatName, parents ) { - return formatName === 'link' ? getLinkProperties( find( parents, node => node.nodeName.toLowerCase() === 'a' ) ) : {}; + switch ( formatName ) { + case 'link' : + const anchor = find( parents, node => node.nodeName.toLowerCase() === 'a' ); + return !! anchor ? { value: anchor.getAttribute( 'href' ) || '', target: anchor.getAttribute( 'target' ) || '', node: anchor } : {}; + default: + return {}; + } } -const FORMATS = [ 'bold', 'italic', 'strikethrough', 'link' ]; +const DEFAULT_FORMATS = [ 'bold', 'italic', 'strikethrough', 'link' ]; export default class Editable extends Component { constructor( props ) { @@ -159,8 +159,8 @@ export default class Editable extends Component { } registerCustomFormatters() { - forEach( this.props.formatters, ( formatter ) => { - this.editor.formatter.register( formatter.format, formatter.formatter ); + forEach( this.props.initialFormatters, ( formatter ) => { + this.editor.formatter.register( formatter.format, { ...formatter.formatter } ); } ); } @@ -492,15 +492,15 @@ export default class Editable extends Component { } onNodeChange( { parents } ) { - const formats = {}; - const formatNames = this.props.formattingControls || FORMATS; - - forEach( this.editor.formatter.matchAll( formatNames ), ( activeFormat ) => { - formats[ activeFormat ] = { + const formatNames = this.props.formattingControls; + const formats = this.editor.formatter.matchAll( formatNames ).reduce( ( accFormats, activeFormat ) => { + accFormats[ activeFormat ] = { isActive: true, ...getFormatProperties( activeFormat, parents ), }; - } ); + + return accFormats; + }, {} ); const focusPosition = this.getFocusPosition(); this.setState( { formats, focusPosition, selectedNodeId: this.state.selectedNodeId + 1 } ); @@ -570,6 +570,15 @@ export default class Editable extends Component { } } + componentWillReceiveProps( nextProps ) { + if ( 'development' === process.env.NODE_ENV ) { + if ( ! isEqual( this.props.initialFormatters, nextProps.initialFormatters ) ) { + // eslint-disable-next-line no-console + console.error( 'Formatters passed via `initialFormatters` will only be registered once. Formatters can be enabled/disabled via the `formattingControls` prop.' ); + } + } + } + isFormatActive( format ) { return this.state.formats[ format ] && this.state.formats[ format ].isActive; } @@ -612,6 +621,12 @@ export default class Editable extends Component { this.editor.setDirty( true ); } + getToolbarCustomControls() { + const { initialFormatters, formattingControls } = this.props; + return initialFormatters + .filter( f => formattingControls.indexOf( f.format ) > -1 ); + } + render() { const { tagName: Tagname = 'div', @@ -641,7 +656,7 @@ export default class Editable extends Component { formats={ this.state.formats } onChange={ this.changeFormats } enabledControls={ formattingControls } - customControls={ map( this.props.formatters, ( formatter ) => pick( formatter, [ 'format', 'title', 'icon' ] ) ) } + customControls={ this.getToolbarCustomControls() } /> ); @@ -684,3 +699,8 @@ export default class Editable extends Component { Editable.contextTypes = { onUndo: noop, }; + +Editable.defaultProps = { + formattingControls: DEFAULT_FORMATS, + initialFormatters: [], +}; diff --git a/blocks/library/paragraph/index.js b/blocks/library/paragraph/index.js index 721c2e3973e88..4471e9a557a57 100644 --- a/blocks/library/paragraph/index.js +++ b/blocks/library/paragraph/index.js @@ -185,7 +185,7 @@ registerBlockType( 'core/paragraph', { onReplace={ onReplace } placeholder={ placeholder || __( 'New Paragraph' ) } formattingControls={ [ 'bold', 'italic', 'strikethrough', 'link', 'red' ] } - formatters={ [ + initialFormatters={ [ createInlineStyleFormatter( 'red', 'hammer', 'Red', { color: 'red' } ), ] } /> From 3d213e0a7bbff53b5158effe0b49e4427b47f07f Mon Sep 17 00:00:00 2001 From: Tim Gardner Date: Mon, 23 Oct 2017 16:42:51 +1000 Subject: [PATCH 3/4] Changes for code review --- blocks/editable/format-toolbar/index.js | 3 +-- blocks/editable/formatter.js | 11 --------- blocks/editable/index.js | 33 +++++++++++++++---------- blocks/library/paragraph/index.js | 12 ++++++--- 4 files changed, 29 insertions(+), 30 deletions(-) delete mode 100644 blocks/editable/formatter.js diff --git a/blocks/editable/format-toolbar/index.js b/blocks/editable/format-toolbar/index.js index e85f712bd2fb5..7b793edaeb884 100644 --- a/blocks/editable/format-toolbar/index.js +++ b/blocks/editable/format-toolbar/index.js @@ -147,9 +147,8 @@ class FormatToolbar extends Component { ? { position: 'absolute', ...focusPosition } : null; - const toolbarControls = FORMATTING_CONTROLS + const toolbarControls = FORMATTING_CONTROLS.concat( customControls ) .filter( control => enabledControls.indexOf( control.format ) !== -1 ) - .concat( customControls ) .map( ( control ) => { const isLink = control.format === 'link'; return { diff --git a/blocks/editable/formatter.js b/blocks/editable/formatter.js deleted file mode 100644 index 7715e0fe73ec9..0000000000000 --- a/blocks/editable/formatter.js +++ /dev/null @@ -1,11 +0,0 @@ -export function createInlineStyleFormatter( name, icon, title, style ) { - return { - format: name, - icon, - title, - formatter: { - inline: 'span', - styles: style, - }, - }; -} diff --git a/blocks/editable/index.js b/blocks/editable/index.js index 3cdb2a2cf3f00..e7d3e40305178 100644 --- a/blocks/editable/index.js +++ b/blocks/editable/index.js @@ -60,9 +60,10 @@ function isLinkBoundary( fragment ) { function getFormatProperties( formatName, parents ) { switch ( formatName ) { - case 'link' : + case 'link' : { const anchor = find( parents, node => node.nodeName.toLowerCase() === 'a' ); return !! anchor ? { value: anchor.getAttribute( 'href' ) || '', target: anchor.getAttribute( 'target' ) || '', node: anchor } : {}; + } default: return {}; } @@ -158,9 +159,20 @@ export default class Editable extends Component { this.registerCustomFormatters(); } + adaptFormatter( options ) { + switch ( options.type ) { + case 'inline-style': { + return { + inline: 'span', + styles: { ...options.style }, + }; + } + } + } + registerCustomFormatters() { - forEach( this.props.initialFormatters, ( formatter ) => { - this.editor.formatter.register( formatter.format, { ...formatter.formatter } ); + forEach( this.props.formatters, ( formatter ) => { + this.editor.formatter.register( formatter.format, this.adaptFormatter( formatter ) ); } ); } @@ -572,9 +584,9 @@ export default class Editable extends Component { componentWillReceiveProps( nextProps ) { if ( 'development' === process.env.NODE_ENV ) { - if ( ! isEqual( this.props.initialFormatters, nextProps.initialFormatters ) ) { + if ( ! isEqual( this.props.formatters, nextProps.formatters ) ) { // eslint-disable-next-line no-console - console.error( 'Formatters passed via `initialFormatters` will only be registered once. Formatters can be enabled/disabled via the `formattingControls` prop.' ); + console.error( 'Formatters passed via `formatters` prop will only be registered once. Formatters can be enabled/disabled via the `formattingControls` prop.' ); } } } @@ -621,12 +633,6 @@ export default class Editable extends Component { this.editor.setDirty( true ); } - getToolbarCustomControls() { - const { initialFormatters, formattingControls } = this.props; - return initialFormatters - .filter( f => formattingControls.indexOf( f.format ) > -1 ); - } - render() { const { tagName: Tagname = 'div', @@ -640,6 +646,7 @@ export default class Editable extends Component { placeholder, multiline: MultilineTag, keepPlaceholderOnFocus = false, + formatters, } = this.props; // Generating a key that includes `tagName` ensures that if the tag @@ -656,7 +663,7 @@ export default class Editable extends Component { formats={ this.state.formats } onChange={ this.changeFormats } enabledControls={ formattingControls } - customControls={ this.getToolbarCustomControls() } + customControls={ formatters } /> ); @@ -702,5 +709,5 @@ Editable.contextTypes = { Editable.defaultProps = { formattingControls: DEFAULT_FORMATS, - initialFormatters: [], + formatters: [], }; diff --git a/blocks/library/paragraph/index.js b/blocks/library/paragraph/index.js index 4471e9a557a57..85a3ca07c73ce 100644 --- a/blocks/library/paragraph/index.js +++ b/blocks/library/paragraph/index.js @@ -20,7 +20,6 @@ import BlockAlignmentToolbar from '../../block-alignment-toolbar'; import BlockControls from '../../block-controls'; import BlockAutocomplete from '../../block-autocomplete'; import Editable from '../../editable'; -import { createInlineStyleFormatter } from '../../editable/formatter'; import InspectorControls from '../../inspector-controls'; import ToggleControl from '../../inspector-controls/toggle-control'; import RangeControl from '../../inspector-controls/range-control'; @@ -185,9 +184,14 @@ registerBlockType( 'core/paragraph', { onReplace={ onReplace } placeholder={ placeholder || __( 'New Paragraph' ) } formattingControls={ [ 'bold', 'italic', 'strikethrough', 'link', 'red' ] } - initialFormatters={ [ - createInlineStyleFormatter( 'red', 'hammer', 'Red', { color: 'red' } ), - ] } + formatters={ [ { + type: 'inline-style', + format: 'red', + title: 'Red', + icon: 'hammer', + style: { color: 'red' }, + } ] + } /> , ]; From e76eb2d05ae7cce02aeba719b0473694a3360b5a Mon Sep 17 00:00:00 2001 From: Tim Gardner Date: Tue, 24 Oct 2017 11:56:51 +1000 Subject: [PATCH 4/4] Removed example style --- blocks/library/paragraph/index.js | 9 --------- 1 file changed, 9 deletions(-) diff --git a/blocks/library/paragraph/index.js b/blocks/library/paragraph/index.js index 765129c3e61b6..1b8ad470f9973 100644 --- a/blocks/library/paragraph/index.js +++ b/blocks/library/paragraph/index.js @@ -182,15 +182,6 @@ registerBlockType( 'core/paragraph', { onMerge={ mergeBlocks } onReplace={ onReplace } placeholder={ placeholder || __( 'Add text or type / to insert content' ) } - formattingControls={ [ 'bold', 'italic', 'strikethrough', 'link', 'red' ] } - formatters={ [ { - type: 'inline-style', - format: 'red', - title: 'Red', - icon: 'hammer', - style: { color: 'red' }, - } ] - } /> , ];