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

Implement list block in React Native #14636

Merged
merged 31 commits into from
Apr 5, 2019
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
bd83d8f
Make sure multiline property is filtered out of props on save.
SergioEstevao Mar 5, 2019
8ed9cc0
Send block edit parameters using the context.
SergioEstevao Mar 5, 2019
e0d7cf6
Add multiline variables to allow proper parsing and saving of propert…
SergioEstevao Mar 5, 2019
eb33f84
Add list edit toolbar options
SergioEstevao Mar 5, 2019
c64dd5a
Add multiline property.
SergioEstevao Mar 25, 2019
f0a1e21
Add list block to mobile gb.
SergioEstevao Mar 4, 2019
73aaa4f
Move list-edit.native.js to new location.
SergioEstevao Mar 20, 2019
b6938aa
Make block edit send down the onFocus property.
SergioEstevao Mar 26, 2019
74f77cf
Handle case where unstableSplit is passed has prop.
SergioEstevao Mar 26, 2019
79f408c
Pass multiline tags to serialiser.
SergioEstevao Mar 27, 2019
1f12a9f
Use the format-lib for handling "Enter" in lists
hypest Mar 28, 2019
cc8a4e8
Merge branch 'master' into rnmobile/lists2
SergioEstevao Mar 29, 2019
ca338c2
Force selection reset on split
hypest Mar 29, 2019
076936f
Add multiline wrapper tags to formatToValue.
SergioEstevao Mar 29, 2019
6ff97b8
Merge branch 'rnmobile/lists2' of https://github.com/WordPress/gutenb…
SergioEstevao Mar 29, 2019
73fbd38
Remove unnecessary code.
SergioEstevao Mar 29, 2019
e07498e
Force rich-text text update on list type change
hypest Mar 29, 2019
f40852c
Merge branch 'master' into rnmobile/lists2
SergioEstevao Apr 2, 2019
7fb36f6
Merge branch 'master' into rnmobile/lists2
SergioEstevao Apr 3, 2019
9145a6d
Merge branch 'master' into rnmobile/lists2
SergioEstevao Apr 4, 2019
58eae26
Disable indent and outdent.
SergioEstevao Apr 4, 2019
08318ca
Enable toggling list type of nested lists
hypest Apr 4, 2019
9dcd23b
Update list type toolbar button on native mobile
hypest Apr 4, 2019
fd62f01
Include diff missed by previous commit
hypest Apr 4, 2019
66e971d
Rename to denote that it's about lines
hypest Apr 5, 2019
da0e1a7
Split into separate functions and mark unstable
hypest Apr 5, 2019
eb009e8
Add missing JSDoc param
Tug Apr 5, 2019
cc5666e
Update snapshot for BlockControls
Tug Apr 5, 2019
91f790c
Merge branch 'master' into rnmobile/lists2
SergioEstevao Apr 5, 2019
4451cb3
Move isActiveListType, isListRootSelected to rich-text package
hypest Apr 5, 2019
97ac0fb
Remove excess empty line
hypest Apr 5, 2019
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
3 changes: 2 additions & 1 deletion packages/block-editor/src/components/block-edit/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,13 @@ class BlockEdit extends Component {
}

static getDerivedStateFromProps( props ) {
const { clientId, name, isSelected } = props;
const { clientId, name, isSelected, onFocus } = props;

return {
name,
isSelected,
clientId,
onFocus,
};
}

Expand Down
123 changes: 106 additions & 17 deletions packages/block-editor/src/components/rich-text/index.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ import {
split,
toHTMLString,
insert,
insertLineSeparator,
insertLineBreak,
isEmptyLine,
isCollapsed,
} from '@wordpress/rich-text';
import { decodeEntities } from '@wordpress/html-entities';
Expand All @@ -31,6 +34,8 @@ import { isURL } from '@wordpress/url';
*/
import FormatEdit from './format-edit';
import FormatToolbar from './format-toolbar';
import { withBlockEditContext } from '../block-edit/context';
import { ListEdit } from './list-edit';

import styles from './style.scss';

Expand Down Expand Up @@ -70,14 +75,32 @@ const gutenbergFormatNamesToAztec = {
};

export class RichText extends Component {
constructor() {
constructor( { multiline } ) {
super( ...arguments );

this.isMultiline = false;
if ( multiline === true || multiline === 'p' || multiline === 'li' ) {
this.multilineTag = multiline === true ? 'p' : multiline;
this.isMultiline = true;
}

if ( this.multilineTag === 'li' ) {
this.multilineWrapperTags = [ 'ul', 'ol' ];
}

if ( this.props.onSplit ) {
this.onSplit = this.props.onSplit;
} else if ( this.props.unstableOnSplit ) {
this.onSplit = this.props.unstableOnSplit;
}

this.isIOS = Platform.OS === 'ios';
this.onChange = this.onChange.bind( this );
this.onEnter = this.onEnter.bind( this );
this.onBackspace = this.onBackspace.bind( this );
this.onPaste = this.onPaste.bind( this );
this.onContentSizeChange = this.onContentSizeChange.bind( this );
this.onFormatChangeForceChild = this.onFormatChangeForceChild.bind( this );
this.onFormatChange = this.onFormatChange.bind( this );
// This prevents a bug in Aztec which triggers onSelectionChange twice on format change
this.onSelectionChange = this.onSelectionChange.bind( this );
Expand Down Expand Up @@ -120,9 +143,7 @@ export class RichText extends Component {
*
*/
splitContent( currentRecord, blocks = [], isPasted = false ) {
const { onSplit } = this.props;

if ( ! onSplit ) {
if ( ! this.onSplit ) {
return;
}

Expand Down Expand Up @@ -161,7 +182,7 @@ export class RichText extends Component {
// always update when provided with new content.
this.lastEventCount = undefined;

onSplit( before, after, ...blocks );
this.onSplit( before, after, ...blocks );
}

valueToFormat( value ) {
Expand All @@ -182,7 +203,11 @@ export class RichText extends Component {
} ).map( ( name ) => gutenbergFormatNamesToAztec[ name ] ).filter( Boolean );
}

onFormatChange( record ) {
onFormatChangeForceChild( record ) {
this.onFormatChange( record, true );
}

onFormatChange( record, doUpdateChild ) {
let newContent;
// valueToFormat might throw when converting the record to a tree structure
// let's ignore the event for now and force a render update so we're still in sync
Expand All @@ -204,9 +229,13 @@ export class RichText extends Component {
needsSelectionUpdate: record.needsSelectionUpdate,
} );
} else {
// make sure the component rerenders without refreshing the text on gutenberg
// (this can trigger other events that might update the active formats on aztec)
this.lastEventCount = 0;
if ( doUpdateChild ) {
this.lastEventCount = undefined;
} else {
// make sure the component rerenders without refreshing the text on gutenberg
// (this can trigger other events that might update the active formats on aztec)
this.lastEventCount = 0;
}
this.forceUpdate();
}
}
Expand Down Expand Up @@ -255,17 +284,31 @@ export class RichText extends Component {
// eslint-disable-next-line no-unused-vars
onEnter( event ) {
this.lastEventCount = event.nativeEvent.eventCount;
if ( ! this.props.onSplit ) {
// TODO: insert the \n char instead?
return;
}

const currentRecord = this.createRecord( {
...event.nativeEvent,
currentContent: unescapeSpaces( event.nativeEvent.text ),
} );

this.splitContent( currentRecord );
if ( this.multilineTag ) {
if ( event.shiftKey ) {
const insertedLineBreak = { needsSelectionUpdate: true, ...insertLineBreak( currentRecord ) };
this.onFormatChangeForceChild( insertedLineBreak );
} else if ( this.onSplit && isEmptyLine( currentRecord ) ) {
this.setState( {
needsSelectionUpdate: false,
} );
this.onSplit( ...split( currentRecord ).map( this.valueToFormat ) );
} else {
const insertedLineSeparator = { needsSelectionUpdate: true, ...insertLineSeparator( currentRecord ) };
this.onFormatChangeForceChild( insertedLineSeparator );
}
} else if ( event.shiftKey || ! this.onSplit ) {
const insertedLineBreak = { needsSelectionUpdate: true, ...insertLineBreak( currentRecord ) };
this.onFormatChangeForceChild( insertedLineBreak );
} else {
this.splitContent( currentRecord );
}
}

// eslint-disable-next-line no-unused-vars
Expand Down Expand Up @@ -422,7 +465,8 @@ export class RichText extends Component {
...create( {
html: innerContent,
range: null,
multilineTag: false,
multilineTag: this.multilineTag,
multilineWrapperTags: this.multilineWrapperTags,
} ),
};

Expand All @@ -435,13 +479,15 @@ export class RichText extends Component {
return create( {
html: children.toHTML( value ),
multilineTag: this.multilineTag,
multilineWrapperTags: this.multilineWrapperTags,
} );
}

if ( this.props.format === 'string' ) {
return create( {
html: value,
multilineTag: this.multilineTag,
multilineWrapperTags: this.multilineWrapperTags,
} );
}

Expand Down Expand Up @@ -501,6 +547,7 @@ export class RichText extends Component {
style,
formattingControls,
isSelected,
onTagNameChange,
} = this.props;

const record = this.getRecord();
Expand All @@ -522,6 +569,14 @@ export class RichText extends Component {

return (
<View>
{ isSelected && this.multilineTag === 'li' && (
<ListEdit
onTagNameChange={ onTagNameChange }
tagName={ tagName }
value={ record }
onChange={ this.onFormatChangeForceChild }
/>
) }
{ isSelected && (
<BlockFormatControls>
<FormatToolbar controls={ formattingControls } />
Expand Down Expand Up @@ -561,6 +616,7 @@ export class RichText extends Component {
fontWeight={ this.props.fontWeight }
fontStyle={ this.props.fontStyle }
disableEditingMenu={ this.props.disableEditingMenu }
isMultiline={ this.isMultiline }
/>
{ isSelected && <FormatEdit value={ record } onChange={ this.onFormatChange } /> }
</View>
Expand All @@ -582,13 +638,46 @@ const RichTextContainer = compose( [
formatTypes: getFormatTypes(),
};
} ),
withBlockEditContext( ( context, ownProps ) => {
// When explicitly set as not selected, do nothing.
if ( ownProps.isSelected === false ) {
return {
clientId: context.clientId,
};
}
// When explicitly set as selected, use the value stored in the context instead.
if ( ownProps.isSelected === true ) {
return {
isSelected: context.isSelected,
clientId: context.clientId,
};
}

// Ensures that only one RichText component can be focused.
return {
clientId: context.clientId,
isSelected: context.isSelected,
onFocus: context.onFocus,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I saw that but it looks we don't use the same logic on the native part on our block manager.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how is going your solution work with multiple RichText controls included in a single block?

};
} ),
] )( RichText );

RichTextContainer.Content = ( { value, format, tagName: Tag, ...props } ) => {
RichTextContainer.Content = ( { value, format, tagName: Tag, multiline, ...props } ) => {
let content;
let html = value;
let MultilineTag;

if ( multiline === true || multiline === 'p' || multiline === 'li' ) {
MultilineTag = multiline === true ? 'p' : multiline;
}

if ( ! html && MultilineTag ) {
html = `<${ MultilineTag }></${ MultilineTag }>`;
}

switch ( format ) {
case 'string':
content = <RawHTML>{ value }</RawHTML>;
content = <RawHTML>{ html }</RawHTML>;
break;
}

Expand Down
77 changes: 77 additions & 0 deletions packages/block-editor/src/components/rich-text/list-edit.native.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/**
* WordPress dependencies
*/

import { Toolbar } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import {
changeListType,
getStartNestingLevel,
} from '@wordpress/rich-text';

/**
* Internal dependencies
*/

import BlockFormatControls from '../block-format-controls';

/**
* Whether or not the root list is selected.
*
* @return {boolean} True if the root list or nothing is selected, false if an
* inner list is selected.
*/
function isListRootSelected( value ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not replace getStartListFormat with both isListRootSelected and isActiveListType? This seems weird to me.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👋 @ellatrix , not sure I follow. Do you mean to completely remove getStartListFormat and implement its pieces inside isListRootSelected and isActiveListType?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, export isListRootSelected and isActiveListType from rich-text as unstable.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done with 4451cb3.

return getStartNestingLevel( value ) < 1;
}

/**
* Wether or not the selected list has the given tag name.
*
* @param {string} tagName The tag name the list should have.
* @param {string} rootTagName The current root tag name, to compare with in
* case nothing is selected.
*
* @return {boolean} [description]
*/
function isActiveListType( tagName, rootTagName ) {
return tagName === rootTagName;
}

export const ListEdit = ( {
onTagNameChange,
tagName,
value,
onChange,
} ) => (
<BlockFormatControls>
<Toolbar
controls={ [
onTagNameChange && {
icon: 'editor-ul',
title: __( 'Convert to unordered list' ),
isActive: isActiveListType( 'ul', tagName ),
onClick() {
onChange( changeListType( value, { type: 'ul' } ) );

if ( isListRootSelected( value ) ) {
onTagNameChange( 'ul' );
}
},
},
onTagNameChange && {
icon: 'editor-ol',
title: __( 'Convert to ordered list' ),
isActive: isActiveListType( 'ol', tagName ),
onClick() {
onChange( changeListType( value, { type: 'ol' } ) );

if ( isListRootSelected( value ) ) {
onTagNameChange( 'ol' );
}
},
},
].filter( Boolean ) }
/>
</BlockFormatControls>
);
1 change: 1 addition & 0 deletions packages/block-library/src/index.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ export const registerCoreBlocks = () => {
more,
image,
nextpage,
list,
].forEach( ( { name, settings } ) => {
registerBlockType( name, settings );
} );
Expand Down
19 changes: 19 additions & 0 deletions packages/rich-text/src/get-start-nesting-level.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* Internal dependencies
*/

import { getLineIndex } from './get-line-index';

/**
* Returns the nesting level of the list at the selection start position.
*
* @param {Object} value The rich-text value
*
* @return {number} The nesting level, starting from 0.
*/
export function getStartNestingLevel( value ) {
hypest marked this conversation as resolved.
Show resolved Hide resolved
const { text, replacements, start, end } = value;
const startingLineIndex = getLineIndex( value, start );
const startLineFormats = replacements[ startingLineIndex ] || [];
return startLineFormats.length;
}
1 change: 1 addition & 0 deletions packages/rich-text/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export { getActiveObject } from './get-active-object';
export { getSelectionEnd } from './get-selection-end';
export { getSelectionStart } from './get-selection-start';
export { getTextContent } from './get-text-content';
export { getStartNestingLevel } from './get-start-nesting-level';
export { isCollapsed } from './is-collapsed';
export { isEmpty, isEmptyLine } from './is-empty';
export { join } from './join';
Expand Down