Skip to content
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

Update/972 freeform block add format selector #1030

Merged
merged 19 commits into from
Jun 12, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
310cf90
Prototyping the format selector
tiny-james Jun 5, 2017
2f147d7
Merge branch 'master' into update/972-Freeform-block-add-format-selector
tiny-james Jun 5, 2017
202ca7d
Finished the format selector
tiny-james Jun 6, 2017
7b0209f
Merge branch 'master' into update/972-Freeform-block-add-format-selector
tiny-james Jun 7, 2017
4c4a694
Handle cases where selection does not have a known format
tiny-james Jun 7, 2017
eabe788
Moved formats and handleFormatChange out of state
tiny-james Jun 7, 2017
2452ede
Add onSelectionChange handler from editable to fix toolbar fade
tiny-james Jun 8, 2017
051772c
Merge branch 'master' into update/972-Freeform-block-add-format-selector
tiny-james Jun 8, 2017
db8a657
Merge branch 'master' into update/972-Freeform-block-add-format-selector
tiny-james Jun 9, 2017
6df2860
Fixed z-index problems and tweaked margins
tiny-james Jun 9, 2017
134324a
Use css from TinyMCE to style the format menu (WIP)
tiny-james Jun 9, 2017
ce4f093
Use camelCase from lodash
tiny-james Jun 11, 2017
fa2cea5
Import __ from i18n to save repeated typing of wp.i18n.__
tiny-james Jun 11, 2017
917c850
Merge branch 'master' into update/972-Freeform-block-add-format-selector
tiny-james Jun 12, 2017
227ed80
Fixed naiveCss2Jsx to remove empty style rules
tiny-james Jun 12, 2017
1669d87
Added unit tests for naiveCss2Jsx
tiny-james Jun 12, 2017
85327d6
Added missing styling
tiny-james Jun 12, 2017
aab0bcf
Set line height to match item size
tiny-james Jun 12, 2017
badf745
Merge branch 'master' into update/972-Freeform-block-add-format-selector
tiny-james Jun 12, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 113 additions & 0 deletions blocks/library/freeform/format-list.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/**
* External dependencies
*/
import clickOutside from 'react-click-outside';
import classnames from 'classnames';
import { camelCase, fromPairs, omit } from 'lodash';

/**
* WordPress dependencies
*/
import { Button, Dashicon } from 'components';

/**
* Internal dependencies
*/
import { __ } from 'i18n';
import './format-list.scss';

export function naiveCss2Jsx( styleText ) {
return fromPairs(
styleText.split( ';' ).filter( ( text ) => /\S/.test( text ) ).map(
( stylePart ) => {
const [ cssKey, cssValue ] = stylePart.split( ':', 2 );
return [ camelCase( cssKey ), cssValue ];
}
)
);
}

class FormatList extends wp.element.Component {
constructor() {
super( ...arguments );
this.switchFormat = this.switchFormat.bind( this );
this.toggleMenu = this.toggleMenu.bind( this );
this.state = {
open: false,
};
}

handleClickOutside() {
if ( ! this.state.open ) {
return;
}
this.setState( { open: false } );
}

toggleMenu() {
this.setState( {
open: ! this.state.open,
} );
}

switchFormat( newValue ) {
if ( this.props.onFormatChange ) {
this.props.onFormatChange( newValue );
}
this.setState( { open: false } );
}

render() {
const { formats } = this.props;
const selectedValue = this.props.value;
const noFormat = { text: __( 'No format' ), value: null };
const styleExclude = [ 'color', 'backgroundColor' ];
return (
formats && <div className="editor-format-list">
<Button
className="editor-format-list__toggle"
onClick={ this.toggleMenu }
aria-haspopup="true"
aria-expanded={ this.state.open }
aria-label={ __( 'Change format' ) }
>
<div className="formats">
{ [ noFormat, ...formats ].map( ( { text, value }, i ) => (
<span
key={ i }
className={ value === selectedValue ? 'active' : null }
aria-hidden={ value !== selectedValue }
>
{ text }<br />
</span>
) ) }
</div>
<Dashicon icon="arrow-down" />
</Button>
{ this.state.open &&
<div
className="editor-format-list__menu"
role="menu"
tabIndex="0"
aria-label={ __( 'Formats' ) }
>
{ formats.map( ( { text, value, textStyle } ) => (
<Button
key={ value }
onClick={ () => this.switchFormat( value ) }
className={ classnames( 'editor-format-list__menu-item', {
'is-active': value === selectedValue,
} ) }
role="menuitem"
>
<span style={ omit( naiveCss2Jsx( textStyle() ), styleExclude ) }>{ text }</span>
</Button>
) ) }
</div>
}
</div>
);
}
}

export default clickOutside( FormatList );
86 changes: 86 additions & 0 deletions blocks/library/freeform/format-list.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
.editor-format-list {
border: 1px solid $light-gray-500;
box-shadow: $shadow-popover;
background-color: $white;
margin-right: 10px;
font-family: $default-font;
font-size: $default-font-size;
line-height: $default-line-height;
display: flex;
flex-direction: row;
padding: 0;
position: relative;
}

.editor-format-list__toggle {
display: inline-flex;
flex-direction: row;
align-items: center;
padding: 0 8px;

&:focus:before {
top: -3px;
right: -3px;
bottom: -3px;
left: -3px;
}

.formats {
display: inline;
text-align: start;
line-height: 0;
visibility: hidden;

.active {
visibility: visible;
}
}
}

.editor-format-list .editor-format-list__menu {
position: absolute;
top: $block-controls-height - 2px;
left: -1px;
box-shadow: $shadow-popover;
border: 1px solid $light-gray-500;
background: $white;
padding: 3px 3px 0 3px;
display: flex;
flex-direction: column;
align-content: flex-start;
z-index: z-index( '.editor-format-list__menu' );

input {
font-size: 13px;
}
}

.editor-format-list .editor-format-list__menu-item {
width: 100%;
margin-bottom: 3px;
padding: 6px;
padding-left: 4px;
padding-right: 24px;
line-height: 1;
background: none;
border: 1px solid transparent;
outline: none;
border-radius: 0;
color: $dark-gray-500;
cursor: pointer;
text-align: start;

&:hover,
&:focus,
&:not(:disabled):hover {
box-shadow: none;
color: $dark-gray-500;
border-color: $dark-gray-500;
}

&.is-active,
&.is-active:hover {
color: $white;
background-color: $dark-gray-500;
}
}
80 changes: 69 additions & 11 deletions blocks/library/freeform/freeform-block.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,63 +2,66 @@
* External dependencies
*/
import { nodeListToReact } from 'dom-react';
import { concat, isEqual, omitBy } from 'lodash';
import { isEqual, omitBy } from 'lodash';
import { Fill } from 'react-slot-fill';

/**
* Internal dependencies
*/
import { __ } from 'i18n';
import TinyMCE from '../../editable/tinymce';
import BlockControls from '../../block-controls';
import FormatList from './format-list';

const ALIGNMENT_CONTROLS = [
{
id: 'alignleft',
icon: 'editor-alignleft',
title: wp.i18n.__( 'Align left' ),
title: __( 'Align left' ),
},
{
id: 'aligncenter',
icon: 'editor-aligncenter',
title: wp.i18n.__( 'Align center' ),
title: __( 'Align center' ),
},
{
id: 'alignright',
icon: 'editor-alignright',
title: wp.i18n.__( 'Align right' ),
title: __( 'Align right' ),
},
];

const FREEFORM_CONTROLS = [
{
id: 'blockquote',
icon: 'editor-quote',
title: wp.i18n.__( 'Quote' ),
title: __( 'Quote' ),
},
{
id: 'bullist',
icon: 'editor-ul',
title: wp.i18n.__( 'Convert to unordered' ),
title: __( 'Convert to unordered' ),
},
{
id: 'numlist',
icon: 'editor-ol',
title: wp.i18n.__( 'Convert to ordered' ),
title: __( 'Convert to ordered' ),
},
{
leftDivider: true,
id: 'bold',
icon: 'editor-bold',
title: wp.i18n.__( 'Bold' ),
title: __( 'Bold' ),
},
{
id: 'italic',
icon: 'editor-italic',
title: wp.i18n.__( 'Italic' ),
title: __( 'Italic' ),
},
{
id: 'strikethrough',
icon: 'editor-strikethrough',
title: wp.i18n.__( 'Strikethrough' ),
title: __( 'Strikethrough' ),
},
];

Expand All @@ -83,8 +86,10 @@ export default class FreeformBlock extends wp.element.Component {
super( ...arguments );
this.getSettings = this.getSettings.bind( this );
this.setButtonActive = this.setButtonActive.bind( this );
this.setFormatActive = this.setFormatActive.bind( this );
this.onSetup = this.onSetup.bind( this );
this.onInit = this.onInit.bind( this );
this.onSelectionChange = this.onSelectionChange.bind( this );
this.onChange = this.onChange.bind( this );
this.onFocus = this.onFocus.bind( this );
this.updateFocus = this.updateFocus.bind( this );
Expand All @@ -94,9 +99,12 @@ export default class FreeformBlock extends wp.element.Component {
this.controls = this.mapControls.bind( this );
this.editor = null;
this.savedContent = null;
this.formats = null;
this.handleFormatChange = null;
this.state = {
empty: ! props.value || ! props.value.length,
activeButtons: { },
activeFormat: null,
};
}

Expand All @@ -116,15 +124,28 @@ export default class FreeformBlock extends wp.element.Component {
} ) );
}

setFormatActive( newActiveFormat ) {
this.setState( { activeFormat: newActiveFormat } );
}

onSetup( editor ) {
this.editor = editor;
editor.on( 'init', this.onInit );
editor.on( 'focusout', this.onChange );
editor.on( 'focusin', this.onFocus );
editor.on( 'selectionChange', this.onSelectionChange );
}

onInit() {
concat( ALIGNMENT_CONTROLS, FREEFORM_CONTROLS ).forEach( ( control ) => {
const formatselect = this.editor.buttons.formatselect();
formatselect.onPostRender.call( {
value: this.setFormatActive,
} );
this.formats = formatselect.values;
this.handleFormatChange = formatselect.onselect;
this.forceUpdate();

[ ...ALIGNMENT_CONTROLS, ...FREEFORM_CONTROLS ].forEach( ( control ) => {
if ( control.id ) {
const button = this.editor.buttons[ control.id ];
button.onPostRender.call( {
Expand All @@ -135,6 +156,34 @@ export default class FreeformBlock extends wp.element.Component {
this.updateFocus();
}

isActive() {
return document.activeElement === this.editor.getBody();
}

onSelectionChange() {
// We must check this because selectionChange is a global event.
if ( ! this.isActive() ) {
return;
}

const content = this.getContent();
const collapsed = this.editor.selection.isCollapsed();

this.setState( {
empty: ! content || ! content.length,
} );

if (
this.props.focus && this.props.onFocus &&
this.props.focus.collapsed !== collapsed
) {
this.props.onFocus( {
...this.props.focus,
collapsed,
} );
}
}

onChange() {
if ( ! this.editor.isDirty() ) {
return;
Expand Down Expand Up @@ -220,6 +269,15 @@ export default class FreeformBlock extends wp.element.Component {
render() {
const { content, focus } = this.props;
return [
focus && (
<Fill name="Formatting.Toolbar">
<FormatList
onFormatChange={ this.handleFormatChange }
formats={ this.formats }
value={ this.state.activeFormat }
/>
</Fill>
),
focus && (
<BlockControls
key="aligns"
Expand Down
Loading