diff --git a/packages/editor/src/components/post-taxonomies/flat-term-selector.js b/packages/editor/src/components/post-taxonomies/flat-term-selector.js index decc8953ec9c55..d7a7290df1d8be 100644 --- a/packages/editor/src/components/post-taxonomies/flat-term-selector.js +++ b/packages/editor/src/components/post-taxonomies/flat-term-selector.js @@ -18,7 +18,7 @@ import { */ import { __, _x, sprintf } from '@wordpress/i18n'; import { Component } from '@wordpress/element'; -import { FormTokenField, withFilters } from '@wordpress/components'; +import { FormTokenField, withFilters, Button } from '@wordpress/components'; import { withSelect, withDispatch } from '@wordpress/data'; import { compose } from '@wordpress/compose'; import apiFetch from '@wordpress/api-fetch'; @@ -28,11 +28,12 @@ import { addQueryArgs } from '@wordpress/url'; * Module constants */ const MAX_TERMS_SUGGESTIONS = 20; +const MAX_RELATED_TERMS_SUGGESTIONS = 20; const DEFAULT_QUERY = { per_page: MAX_TERMS_SUGGESTIONS, orderby: 'count', order: 'desc', - _fields: 'id,name', + _fields: 'id,name,count', }; // Lodash unescape function handles ' but not ' which may be return in some API requests. @@ -76,10 +77,15 @@ class FlatTermSelector extends Component { this.onChange = this.onChange.bind( this ); this.searchTerms = throttle( this.searchTerms.bind( this ), 500 ); this.findOrCreateTerm = this.findOrCreateTerm.bind( this ); + this.appendTerm = this.appendTerm.bind( this ); + this.toggleRelatedTerms = this.toggleRelatedTerms.bind( this ); + this.fetchRelatedTerms = this.fetchRelatedTerms.bind( this ); this.state = { loading: ! isEmpty( this.props.terms ), availableTerms: [], selectedTerms: [], + relatedTerms: [], + areRelatedTermsHidden: true, }; } @@ -89,20 +95,31 @@ class FlatTermSelector extends Component { include: this.props.terms.join( ',' ), per_page: -1, } ); - this.initRequest.then( - () => { + } else { + this.initRequest = Promise.resolve( [] ); + } + + this.initRequest.then( + ( response ) => { + this.relatedTermsRequest = this.fetchRelatedTerms( { + order: 'desc', + orderby: 'count', + per_page: MAX_RELATED_TERMS_SUGGESTIONS + response.length, + } ); + + this.relatedTermsRequest.then( () => { this.setState( { loading: false } ); - }, - ( xhr ) => { - if ( xhr.statusText === 'abort' ) { - return; - } - this.setState( { - loading: false, - } ); + } ); + }, + ( xhr ) => { + if ( xhr.statusText === 'abort' ) { + return; } - ); - } + this.setState( { + loading: false, + } ); + } + ); } componentWillUnmount() { @@ -141,6 +158,37 @@ class FlatTermSelector extends Component { return request; } + fetchRelatedTerms( params = {} ) { + const { taxonomy } = this.props; + const query = { ...DEFAULT_QUERY, ...params }; + const request = apiFetch( { + path: addQueryArgs( `/wp/v2/${ taxonomy.rest_base }`, query ), + } ); + request.then( unescapeTerms ).then( ( terms ) => { + let newRelatedTerms = terms.filter( + ( term ) => + ! find( + this.state.selectedTerms, + ( updatedTerm ) => updatedTerm === term.name + ) + ); + const newAvailableTerms = this.state.availableTerms.concat( + newRelatedTerms + ); + + newRelatedTerms = newRelatedTerms.splice( + 0, + MAX_RELATED_TERMS_SUGGESTIONS + ); + this.setState( { + relatedTerms: newRelatedTerms, + availableTerms: newAvailableTerms, + } ); + } ); + + return request; + } + updateSelectedTerms( terms = [] ) { const selectedTerms = terms.reduce( ( accumulator, termId ) => { const termObject = find( @@ -233,6 +281,32 @@ class FlatTermSelector extends Component { } } + appendTerm( newTerm ) { + if ( + find( this.state.selectedTerms, ( selectedTerm ) => { + isSameTermName( selectedTerm, newTerm.name ); + } ) + ) + return; + + const newSelectedTerms = this.state.selectedTerms.concat( [ + newTerm.name, + ] ); + + this.onChange( newSelectedTerms ); + } + + toggleRelatedTerms() { + this.setState( ( state ) => ( { + areRelatedTermsHidden: ! state.areRelatedTermsHidden, + } ) ); + } + + // Screen Reader is an extension for modern browsers which helps visually handicapped users to interact with websites. + getTermScreenReaderString( tag, count ) { + return `${ tag } (${ count } items)`; + } + render() { const { slug, taxonomy, hasAssignAction } = this.props; @@ -252,6 +326,7 @@ class FlatTermSelector extends Component { [ 'labels', 'singular_name' ], slug === 'post_tag' ? __( 'Tag' ) : __( 'Term' ) ); + const name = get( taxonomy, [ 'labels', 'name' ] ); const termAddedLabel = sprintf( /* translators: %s: term name. */ _x( '%s added', 'term' ), @@ -267,22 +342,66 @@ class FlatTermSelector extends Component { _x( 'Remove %s', 'term' ), singularName ); + const tagsButtonLabel = sprintf( + /* translators: %s: term name. */ + _x( 'Choose from the most used %s', 'terms' ), + name + ); return ( - +
+ +
+ + { ! this.state.areRelatedTermsHidden ? ( + /* eslint-disable jsx-a11y/no-redundant-roles */ +
    + { this.state.relatedTerms.map( ( term, index ) => ( +
  • + +
  • + ) ) } +
+ ) : ( +
    + /* eslint-enable jsx-a11y/no-redundant-roles */ + ) } +
    +
    ); } } diff --git a/packages/editor/src/components/post-taxonomies/style.scss b/packages/editor/src/components/post-taxonomies/style.scss index 232ac770597695..c20b1401408cb6 100644 --- a/packages/editor/src/components/post-taxonomies/style.scss +++ b/packages/editor/src/components/post-taxonomies/style.scss @@ -32,7 +32,29 @@ margin-top: 8px; width: 100%; } + .editor-post-taxonomies__hierarchical-terms-filter { margin-bottom: 8px; width: 100%; } + +.editor-post-taxonomies__flat-terms-related-list { + column-count: 2; +} + +.editor-post-taxonomies__flat-terms-related-list-wrapper { + position: relative; + top: -13px; +} + +.editor-post-taxonomies__flat-terms-related-list-wrapper > .components-button { + font-style: italic; +} + +.editor-post-taxonomies__flat-terms-related-list .components-button { + text-overflow: ellipsis; + max-width: 108px; + overflow: hidden; + white-space: nowrap; + display: inline-block; +}