Skip to content

Commit e6d42c4

Browse files
authored
Merge pull request #507 from WordPress/update/editable-slot-fill
Try Slot & Fill pattern for rendering Editable formatting controls
2 parents d2dd9f7 + 0958fe8 commit e6d42c4

File tree

7 files changed

+99
-99
lines changed

7 files changed

+99
-99
lines changed

blocks/components/editable/index.js

+69-28
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,59 @@
22
* External dependencies
33
*/
44
import classnames from 'classnames';
5-
import { forEach, last } from 'lodash';
5+
import { last, isEqual } from 'lodash';
66
import { Parser as HtmlToReactParser } from 'html-to-react';
7+
import { Fill } from 'react-slot-fill';
78

89
/**
910
* Internal dependencies
1011
*/
1112
import './style.scss';
1213

14+
// TODO: We mustn't import by relative path traversing from blocks to editor
15+
// as we're doing here; instead, we should consider a common components path.
16+
import Toolbar from '../../../editor/components/toolbar';
17+
1318
const htmlToReactParser = new HtmlToReactParser();
1419
const formatMap = {
1520
strong: 'bold',
1621
em: 'italic',
1722
del: 'strikethrough'
1823
};
1924

25+
const formattingControls = [
26+
{
27+
icon: 'editor-bold',
28+
title: wp.i18n.__( 'Bold' ),
29+
format: 'bold'
30+
},
31+
{
32+
icon: 'editor-italic',
33+
title: wp.i18n.__( 'Italic' ),
34+
format: 'italic'
35+
},
36+
{
37+
icon: 'editor-strikethrough',
38+
title: wp.i18n.__( 'Strikethrough' ),
39+
format: 'strikethrough'
40+
}
41+
];
42+
2043
export default class Editable extends wp.element.Component {
2144
constructor() {
2245
super( ...arguments );
46+
2347
this.onInit = this.onInit.bind( this );
2448
this.onSetup = this.onSetup.bind( this );
2549
this.onChange = this.onChange.bind( this );
2650
this.onNewBlock = this.onNewBlock.bind( this );
27-
this.bindNode = this.bindNode.bind( this );
51+
this.bindEditorNode = this.bindEditorNode.bind( this );
2852
this.onFocus = this.onFocus.bind( this );
2953
this.onNodeChange = this.onNodeChange.bind( this );
30-
this.formats = {};
54+
55+
this.state = {
56+
formats: {}
57+
};
3158
}
3259

3360
componentDidMount() {
@@ -36,7 +63,7 @@ export default class Editable extends wp.element.Component {
3663

3764
initialize() {
3865
const config = {
39-
target: this.node,
66+
target: this.editorNode,
4067
theme: false,
4168
inline: true,
4269
toolbar: false,
@@ -57,10 +84,7 @@ export default class Editable extends wp.element.Component {
5784
editor.on( 'focusout', this.onChange );
5885
editor.on( 'NewBlock', this.onNewBlock );
5986
editor.on( 'focusin', this.onFocus );
60-
61-
if ( this.props.onFormatChange ) {
62-
editor.on( 'nodechange', this.onNodeChange );
63-
}
87+
editor.on( 'nodechange', this.onNodeChange );
6488
}
6589

6690
onInit() {
@@ -131,7 +155,7 @@ export default class Editable extends wp.element.Component {
131155
}
132156

133157
onNodeChange( { parents } ) {
134-
this.formats = parents.reduce( ( result, node ) => {
158+
const formats = parents.reduce( ( result, node ) => {
135159
const tag = node.nodeName.toLowerCase();
136160

137161
if ( formatMap.hasOwnProperty( tag ) ) {
@@ -141,11 +165,13 @@ export default class Editable extends wp.element.Component {
141165
return result;
142166
}, {} );
143167

144-
this.props.onFormatChange( this.formats );
168+
if ( ! isEqual( this.state.formats, formats ) ) {
169+
this.setState( { formats } );
170+
}
145171
}
146172

147-
bindNode( ref ) {
148-
this.node = ref;
173+
bindEditorNode( ref ) {
174+
this.editorNode = ref;
149175
}
150176

151177
updateContent() {
@@ -208,31 +234,46 @@ export default class Editable extends wp.element.Component {
208234
}
209235
}
210236

211-
componentWillReceiveProps( nextProps ) {
212-
forEach( nextProps.formats, ( state, format ) => {
213-
const currentState = this.formats[ format ] || false;
237+
isFormatActive( format ) {
238+
return !! this.state.formats[ format ];
239+
}
214240

215-
if ( state !== currentState ) {
216-
this.editor.focus();
241+
toggleFormat( format ) {
242+
this.editor.focus();
217243

218-
if ( state ) {
219-
this.editor.formatter.apply( format );
220-
} else {
221-
this.editor.formatter.remove( format );
222-
}
223-
}
224-
} );
244+
if ( this.isFormatActive( format ) ) {
245+
this.editor.formatter.remove( format );
246+
} else {
247+
this.editor.formatter.apply( format );
248+
}
225249
}
226250

227251
render() {
228-
const { tagName: Tag = 'div', style, className } = this.props;
252+
const { tagName: Tag = 'div', style, focus, className } = this.props;
229253
const classes = classnames( 'blocks-editable', className );
230254

231-
return (
255+
let element = (
232256
<Tag
233-
ref={ this.bindNode }
257+
ref={ this.bindEditorNode }
234258
style={ style }
235-
className={ classes } />
259+
className={ classes }
260+
key="editor" />
236261
);
262+
263+
if ( focus ) {
264+
element = [
265+
<Fill name="Formatting.Toolbar" key="fill">
266+
<Toolbar
267+
controls={ formattingControls.map( ( control ) => ( {
268+
...control,
269+
onClick: () => this.toggleFormat( control.format ),
270+
isActive: this.isFormatActive( control.format )
271+
} ) ) } />
272+
</Fill>,
273+
element
274+
];
275+
}
276+
277+
return element;
237278
}
238279
}

blocks/library/text/index.js

+1-3
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ registerBlock( 'core/text', {
4444
}
4545
],
4646

47-
edit( { attributes, setAttributes, insertBlockAfter, focus, setFocus, onFormatChange, formats } ) {
47+
edit( { attributes, setAttributes, insertBlockAfter, focus, setFocus } ) {
4848
const { content = <p />, align } = attributes;
4949

5050
return (
@@ -64,8 +64,6 @@ registerBlock( 'core/text', {
6464
content: after
6565
} ) );
6666
} }
67-
onFormatChange={ onFormatChange }
68-
formats={ formats }
6967
/>
7068
);
7169
},

editor/index.js

+7-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
/**
22
* External dependencies
33
*/
4-
import { Provider } from 'react-redux';
4+
import { Provider as ReduxProvider } from 'react-redux';
5+
import { Provider as SlotFillProvider } from 'react-slot-fill';
56

67
/**
78
* Internal dependencies
@@ -24,9 +25,11 @@ export function createEditorInstance( id, post ) {
2425
} );
2526

2627
wp.element.render(
27-
<Provider store={ store }>
28-
<Layout />
29-
</Provider>,
28+
<ReduxProvider store={ store }>
29+
<SlotFillProvider>
30+
<Layout />
31+
</SlotFillProvider>
32+
</ReduxProvider>,
3033
document.getElementById( id )
3134
);
3235
}

editor/modes/visual-editor/block.js

+2-52
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
*/
44
import { connect } from 'react-redux';
55
import classnames from 'classnames';
6+
import { Slot } from 'react-slot-fill';
67

78
/**
89
* Internal dependencies
@@ -11,62 +12,20 @@ import Toolbar from 'components/toolbar';
1112
import BlockMover from 'components/block-mover';
1213
import BlockSwitcher from 'components/block-switcher';
1314

14-
const formattingControls = [
15-
{
16-
icon: 'editor-bold',
17-
title: wp.i18n.__( 'Bold' ),
18-
format: 'bold'
19-
},
20-
{
21-
icon: 'editor-italic',
22-
title: wp.i18n.__( 'Italic' ),
23-
format: 'italic'
24-
},
25-
{
26-
icon: 'editor-strikethrough',
27-
title: wp.i18n.__( 'Strikethrough' ),
28-
format: 'strikethrough'
29-
}
30-
];
31-
3215
class VisualEditorBlock extends wp.element.Component {
3316
constructor() {
3417
super( ...arguments );
3518
this.bindBlockNode = this.bindBlockNode.bind( this );
3619
this.setAttributes = this.setAttributes.bind( this );
3720
this.maybeDeselect = this.maybeDeselect.bind( this );
3821
this.maybeHover = this.maybeHover.bind( this );
39-
this.onFormatChange = this.onFormatChange.bind( this );
40-
this.toggleFormat = this.toggleFormat.bind( this );
4122
this.previousOffset = null;
42-
this.state = {
43-
formats: {}
44-
};
4523
}
4624

4725
bindBlockNode( node ) {
4826
this.node = node;
4927
}
5028

51-
onFormatChange( formats ) {
52-
if ( ! this.state.hasEditable ) {
53-
this.setState( { hasEditable: true } );
54-
}
55-
56-
this.setState( { formats } );
57-
}
58-
59-
toggleFormat( format ) {
60-
const { formats } = this.state;
61-
62-
this.setState( {
63-
formats: {
64-
...formats,
65-
[ format ]: ! formats[ format ]
66-
}
67-
} );
68-
}
69-
7029
componentWillReceiveProps( newProps ) {
7130
if (
7231
this.props.order !== newProps.order &&
@@ -169,14 +128,7 @@ class VisualEditorBlock extends wp.element.Component {
169128
isActive: control.isActive( block.attributes )
170129
} ) ) } />
171130
) }
172-
{ this.state.hasEditable && (
173-
<Toolbar
174-
controls={ formattingControls.map( ( control ) => ( {
175-
...control,
176-
onClick: () => this.toggleFormat( control.format ),
177-
isActive: !! this.state.formats[ control.format ]
178-
} ) ) } />
179-
) }
131+
<Slot name="Formatting.Toolbar" />
180132
</div>
181133
}
182134
<BlockEdit
@@ -185,8 +137,6 @@ class VisualEditorBlock extends wp.element.Component {
185137
setAttributes={ this.setAttributes }
186138
insertBlockAfter={ onInsertAfter }
187139
setFocus={ onFocus }
188-
onFormatChange={ this.onFormatChange }
189-
formats={ this.state.formats }
190140
/>
191141
</div>
192142
);

languages/gutenberg.pot

+12-12
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,18 @@ msgstr ""
33
"Content-Type: text/plain; charset=utf-8\n"
44
"X-Generator: babel-plugin-wp-i18n\n"
55

6+
#: blocks/components/editable/index.js:28
7+
msgid "Bold"
8+
msgstr ""
9+
10+
#: blocks/components/editable/index.js:33
11+
msgid "Italic"
12+
msgstr ""
13+
14+
#: blocks/components/editable/index.js:38
15+
msgid "Strikethrough"
16+
msgstr ""
17+
618
#: blocks/library/embed/index.js:10
719
msgid "Embed"
820
msgstr ""
@@ -102,18 +114,6 @@ msgstr ""
102114
msgid "Publish"
103115
msgstr ""
104116

105-
#: editor/modes/visual-editor/block.js:17
106-
msgid "Bold"
107-
msgstr ""
108-
109-
#: editor/modes/visual-editor/block.js:22
110-
msgid "Italic"
111-
msgstr ""
112-
113-
#: editor/modes/visual-editor/block.js:27
114-
msgid "Strikethrough"
115-
msgstr ""
116-
117117
#: editor/header/mode-switcher/index.js:24
118118
msgctxt "Name for the Text editor tab (formerly HTML)"
119119
msgid "Text"

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
"react-autosize-textarea": "^0.4.2",
6868
"react-dom": "^15.5.4",
6969
"react-redux": "^5.0.4",
70+
"react-slot-fill": "^1.0.0-alpha.11",
7071
"redux": "^3.6.0",
7172
"uuid": "^3.0.1"
7273
}

webpack.config.js

+7
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,13 @@ const config = {
2525
'react-dom': 'ReactDOM',
2626
'react-dom/server': 'ReactDOMServer'
2727
},
28+
resolve: {
29+
alias: {
30+
// There are currently resolution errors on RSF's "mitt" dependency
31+
// when imported as native ES module
32+
'react-slot-fill': 'react-slot-fill/lib/rsf.js'
33+
}
34+
},
2835
module: {
2936
rules: [
3037
{

0 commit comments

Comments
 (0)