-
Notifications
You must be signed in to change notification settings - Fork 4.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Text formatting UI #460
Text formatting UI #460
Changes from all commits
395ad1b
c48a996
6546b7c
4e00fec
76e3da6
6b61f8c
a8c964d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,7 +2,7 @@ | |
* External dependencies | ||
*/ | ||
import classnames from 'classnames'; | ||
import { last } from 'lodash'; | ||
import { forEach, last } from 'lodash'; | ||
import { Parser as HtmlToReactParser } from 'html-to-react'; | ||
|
||
/** | ||
|
@@ -11,6 +11,11 @@ import { Parser as HtmlToReactParser } from 'html-to-react'; | |
import './style.scss'; | ||
|
||
const htmlToReactParser = new HtmlToReactParser(); | ||
const formatMap = { | ||
strong: 'bold', | ||
em: 'italic', | ||
del: 'strikethrough' | ||
}; | ||
|
||
export default class Editable extends wp.element.Component { | ||
constructor() { | ||
|
@@ -21,6 +26,8 @@ export default class Editable extends wp.element.Component { | |
this.onNewBlock = this.onNewBlock.bind( this ); | ||
this.bindNode = this.bindNode.bind( this ); | ||
this.onFocus = this.onFocus.bind( this ); | ||
this.onNodeChange = this.onNodeChange.bind( this ); | ||
this.formats = {}; | ||
} | ||
|
||
componentDidMount() { | ||
|
@@ -50,6 +57,10 @@ export default class Editable extends wp.element.Component { | |
editor.on( 'focusout', this.onChange ); | ||
editor.on( 'NewBlock', this.onNewBlock ); | ||
editor.on( 'focusin', this.onFocus ); | ||
|
||
if ( this.props.onFormatChange ) { | ||
editor.on( 'nodechange', this.onNodeChange ); | ||
} | ||
} | ||
|
||
onInit() { | ||
|
@@ -119,6 +130,20 @@ export default class Editable extends wp.element.Component { | |
} ); | ||
} | ||
|
||
onNodeChange( { parents } ) { | ||
this.formats = parents.reduce( ( result, node ) => { | ||
const tag = node.nodeName.toLowerCase(); | ||
|
||
if ( formatMap.hasOwnProperty( tag ) ) { | ||
result[ formatMap[ tag ] ] = true; | ||
} | ||
|
||
return result; | ||
}, {} ); | ||
|
||
this.props.onFormatChange( this.formats ); | ||
} | ||
|
||
bindNode( ref ) { | ||
this.node = ref; | ||
} | ||
|
@@ -183,6 +208,22 @@ export default class Editable extends wp.element.Component { | |
} | ||
} | ||
|
||
componentWillReceiveProps( nextProps ) { | ||
forEach( nextProps.formats, ( state, format ) => { | ||
const currentState = this.formats[ format ] || false; | ||
|
||
if ( state !== currentState ) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd suggest an early return to avoid tabbing the rest of the callback: if ( state === currentState ) {
return;
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure how that helps? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
It's more a style consideration of avoiding pyramid of doom (and in other cases, callback hell), where the problems become:
All of which are easily addressed by an emphasis on keeping code as shallow as possible by terminating early or deferring remainder of logic to separate function(s). In this case, the code is not particularly difficult to make sense of. I only make the suggestion as I find it's almost always a better style to default to, especially if we expect to add more to these functions in the future (becomes less easy or obvious to flatten in the future). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It definitely makes sense to make a habit of this. Thanks for elaborating @aduth! |
||
this.editor.focus(); | ||
|
||
if ( state ) { | ||
this.editor.formatter.apply( format ); | ||
} else { | ||
this.editor.formatter.remove( format ); | ||
} | ||
} | ||
} ); | ||
} | ||
|
||
render() { | ||
const { tagName: Tag = 'div', style, className } = this.props; | ||
const classes = classnames( 'blocks-editable', className ); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,20 +11,62 @@ import Toolbar from 'components/toolbar'; | |
import BlockMover from 'components/block-mover'; | ||
import BlockSwitcher from 'components/block-switcher'; | ||
|
||
const formattingControls = [ | ||
{ | ||
icon: 'editor-bold', | ||
title: wp.i18n.__( 'Bold' ), | ||
format: 'bold' | ||
}, | ||
{ | ||
icon: 'editor-italic', | ||
title: wp.i18n.__( 'Italic' ), | ||
format: 'italic' | ||
}, | ||
{ | ||
icon: 'editor-strikethrough', | ||
title: wp.i18n.__( 'Strikethrough' ), | ||
format: 'strikethrough' | ||
} | ||
]; | ||
|
||
class VisualEditorBlock extends wp.element.Component { | ||
constructor() { | ||
super( ...arguments ); | ||
this.bindBlockNode = this.bindBlockNode.bind( this ); | ||
this.setAttributes = this.setAttributes.bind( this ); | ||
this.maybeDeselect = this.maybeDeselect.bind( this ); | ||
this.maybeHover = this.maybeHover.bind( this ); | ||
this.onFormatChange = this.onFormatChange.bind( this ); | ||
this.toggleFormat = this.toggleFormat.bind( this ); | ||
this.previousOffset = null; | ||
this.state = { | ||
formats: {} | ||
}; | ||
} | ||
|
||
bindBlockNode( node ) { | ||
this.node = node; | ||
} | ||
|
||
onFormatChange( formats ) { | ||
if ( ! this.state.hasEditable ) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This feels a bit like a hack :) but in the same time I can't find a better alternative There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It does :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. An alternative would be an extra setting for it, but not sure if that's any better. |
||
this.setState( { hasEditable: true } ); | ||
} | ||
|
||
this.setState( { formats } ); | ||
} | ||
|
||
toggleFormat( format ) { | ||
const { formats } = this.state; | ||
|
||
this.setState( { | ||
formats: { | ||
...formats, | ||
[ format ]: ! formats[ format ] | ||
} | ||
} ); | ||
} | ||
|
||
componentWillReceiveProps( newProps ) { | ||
if ( | ||
this.props.order !== newProps.order && | ||
|
@@ -119,6 +161,14 @@ class VisualEditorBlock extends wp.element.Component { | |
isActive: () => control.isActive( block.attributes ) | ||
} ) ) } /> | ||
) } | ||
{ this.state.hasEditable && ( | ||
<Toolbar | ||
controls={ formattingControls.map( ( control ) => ( { | ||
...control, | ||
onClick: () => this.toggleFormat( control.format ), | ||
isActive: () => !! this.state.formats[ control.format ] | ||
} ) ) } /> | ||
) } | ||
</div> | ||
} | ||
<BlockEdit | ||
|
@@ -127,6 +177,8 @@ class VisualEditorBlock extends wp.element.Component { | |
setAttributes={ this.setAttributes } | ||
insertBlockAfter={ onInsertAfter } | ||
setFocus={ onFocus } | ||
onFormatChange={ this.onFormatChange } | ||
formats={ this.state.formats } | ||
/> | ||
</div> | ||
); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer if we find a way to avoid the cache
this.formats
but it's ok for now.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is
this.formats
here any different thanthis.props.formats
? Like Riad, I also instinctively cringe at the sight of instance variables, though they're sometimes justified.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, It can be different than
this.props.formats
right after we trigger theonFormatChange
callback.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any suggestions here are welcome.