diff --git a/blocks/editable/format-toolbar/index.js b/blocks/editable/format-toolbar/index.js index 85f1e5e97fbaf..7b793edaeb884 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,20 +136,27 @@ 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 } : null; - const toolbarControls = FORMATTING_CONTROLS + const toolbarControls = FORMATTING_CONTROLS.concat( customControls ) .filter( control => enabledControls.indexOf( control.format ) !== -1 ) - .map( ( control ) => ( { - ...control, - onClick: this.toggleFormat( control.format ), - isActive: !! formats[ control.format ], - } ) ); + .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 +167,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/index.js b/blocks/editable/index.js index 1c06fd0c9bad3..59fd0395c9251 100644 --- a/blocks/editable/index.js +++ b/blocks/editable/index.js @@ -58,6 +58,19 @@ function isLinkBoundary( fragment ) { fragment.childNodes[ 0 ].text[ 0 ] === '\uFEFF'; } +function getFormatProperties( formatName, parents ) { + 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 DEFAULT_FORMATS = [ 'bold', 'italic', 'strikethrough', 'link' ]; + export default class Editable extends Component { constructor( props ) { super( ...arguments ); @@ -150,6 +163,24 @@ export default class Editable extends Component { onInit() { this.updateFocus(); + this.registerCustomFormatters(); + } + + adaptFormatter( options ) { + switch ( options.type ) { + case 'inline-style': { + return { + inline: 'span', + styles: { ...options.style }, + }; + } + } + } + + registerCustomFormatters() { + forEach( this.props.formatters, ( formatter ) => { + this.editor.formatter.register( formatter.format, this.adaptFormatter( formatter ) ); + } ); } onFocus() { @@ -480,13 +511,15 @@ 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; + 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 } ); @@ -556,8 +589,17 @@ export default class Editable extends Component { } } + componentWillReceiveProps( nextProps ) { + if ( 'development' === process.env.NODE_ENV ) { + if ( ! isEqual( this.props.formatters, nextProps.formatters ) ) { + // eslint-disable-next-line no-console + console.error( 'Formatters passed via `formatters` prop will only be registered once. Formatters can be enabled/disabled via the `formattingControls` prop.' ); + } + } + } + isFormatActive( format ) { - return !! this.state.formats[ format ]; + return this.state.formats[ format ] && this.state.formats[ format ].isActive; } removeFormat( format ) { @@ -611,6 +653,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 @@ -627,6 +670,7 @@ export default class Editable extends Component { formats={ this.state.formats } onChange={ this.changeFormats } enabledControls={ formattingControls } + customControls={ formatters } /> ); @@ -669,3 +713,8 @@ export default class Editable extends Component { Editable.contextTypes = { onUndo: noop, }; + +Editable.defaultProps = { + formattingControls: DEFAULT_FORMATS, + formatters: [], +};