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 && (
);
- 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: [],
+};