diff --git a/.eslintrc.json b/.eslintrc.json index 4fe46964516009..763603865944f8 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -8,6 +8,7 @@ ], "env": { "browser": false, + "es6": true, "node": true, "mocha": true }, diff --git a/components/form-token-field/style.scss b/components/form-token-field/style.scss index 21dba507942310..29413b21e6b6cc 100644 --- a/components/form-token-field/style.scss +++ b/components/form-token-field/style.scss @@ -4,8 +4,9 @@ margin: 0; padding: 0; background-color: $white; + border-radius: 4px; border: 1px solid $light-gray-500; - color: $dark-gray-800; + color: $dark-gray-700; cursor: text; transition: all .15s ease-in-out; @@ -28,7 +29,7 @@ display: flex; flex-wrap: wrap; align-items: flex-start; - padding: 5px 14px 5px 0; + padding: 4px; } // Token input @@ -37,13 +38,13 @@ input[type="text"].components-form-token-field__input { width: auto; max-width: 100%; margin: 2px 0 2px 8px; - padding: 0 0 0 6px; + padding: 0; line-height: 24px; background: inherit; border: 0; outline: none; font-family: inherit; - font-size: 14px; + font-size: $default-font-size; color: $dark-gray-800; box-shadow: none; @@ -54,10 +55,10 @@ input[type="text"].components-form-token-field__input { // Tokens .components-form-token-field__token { - font-size: 14px; + font-size: $default-font-size; display: flex; - margin: 2px 0 2px 8px; - color: $white; + margin: 2px 4px 2px 0; + color: $dark-gray-700; overflow: hidden; &.is-success { @@ -127,32 +128,31 @@ input[type="text"].components-form-token-field__input { } .components-form-token-field__token-text, -.components-form-token-field__remove-token { +.components-form-token-field__remove-token.components-icon-button { display: inline-block; line-height: 24px; - background: $dark-gray-500; + background: $light-gray-500; transition: all .2s cubic-bezier( .4, 1, .4, 1 ); } .components-form-token-field__token-text { - border-radius: 4px 0 0 4px; - padding: 0 4px 0 6px; + border-radius: 12px 0 0 12px; + padding: 0 4px 0 8px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } -.components-form-token-field__remove-token { +.components-form-token-field__remove-token.components-icon-button { cursor: pointer; - border-radius: 0 4px 4px 0; + border-radius: 0 12px 12px 0; padding: 0 2px; font-size: 10px; - color: $light-gray-500; + color: $dark-gray-500; line-height: 10px; &:hover { - color: white; - background: $dark-gray-600; + color: $dark-gray-700; } } diff --git a/components/form-token-field/token.js b/components/form-token-field/token.js index f42d46cb0759d4..e16734d413435d 100644 --- a/components/form-token-field/token.js +++ b/components/form-token-field/token.js @@ -43,7 +43,7 @@ function Token( { diff --git a/components/index.js b/components/index.js index 1582340f6b380e..5a7e9876cf3f8f 100644 --- a/components/index.js +++ b/components/index.js @@ -1,6 +1,7 @@ export { default as Button } from './button'; export { default as Dashicon } from './dashicon'; export { default as FormToggle } from './form-toggle'; +export { default as FormTokenField } from './form-token-field'; export { default as HtmlEmbed } from './html-embed'; export { default as IconButton } from './icon-button'; export { default as Panel } from './panel'; diff --git a/editor/sidebar/post-taxonomies/tags-selector.js b/editor/sidebar/post-taxonomies/tags-selector.js index f0918e3ce64451..9d1af1ac29cc76 100644 --- a/editor/sidebar/post-taxonomies/tags-selector.js +++ b/editor/sidebar/post-taxonomies/tags-selector.js @@ -1,36 +1,132 @@ +/** + * External dependencies + */ +import { connect } from 'react-redux'; +import { unescape, find } from 'lodash'; + /** * WordPress dependencies */ import { Component } from 'element'; -import FormTokenField from 'components/form-token-field'; +import { FormTokenField } from 'components'; +import { getEditedPostAttribute } from '../../selectors'; +import { editPost } from '../../actions'; + +const DEFAULT_TAGS_QUERY = { + number: -1, + orderby: 'count', + order: 'DESC', +}; +const MAX_TERMS_SUGGESTIONS = 20; class TagsSelector extends Component { constructor() { super( ...arguments ); - this.onTokensChange = this.onTokensChange.bind( this ); + this.onTagsChange = this.onTagsChange.bind( this ); this.state = { - tokens: [ 'React', 'Vue' ], + loading: true, + availableTags: [], + selectedTags: [], }; } - onTokensChange( value ) { - this.setState( { tokens: value } ); + componentDidMount() { + this.fetchTagsRequest = new wp.api.collections.Tags().fetch( DEFAULT_TAGS_QUERY ) + .done( ( tags ) => { + this.setState( { + loading: false, + availableTags: tags, + } ); + this.updateSelectedTags( this.props.tags ); + } ) + .fail( ( xhr ) => { + if ( xhr.statusText === 'abort' ) { + return; + } + this.setState( { + loading: false, + } ); + } ); + } + + componentWillUnmount() { + if ( this.fetchTagsRequest ) { + this.fetchTagsRequest.abort(); + } + } + + componentWillReceiveProps( newProps ) { + if ( newProps.tags !== this.props.tags ) { + this.updateSelectedTags( newProps.tags ); + } + } + + updateSelectedTags( tags = [] ) { + const selectedTags = tags.map( ( tagId ) => { + const tagObject = find( this.state.availableTags, ( tag ) => tag.id === tagId ); + return tagObject ? tagObject.name : ''; + } ); + this.setState( { + selectedTags, + } ); + } + + onTagsChange( tagNames ) { + this.setState( { selectedTags: tagNames } ); + const newTagNames = tagNames.filter( ( tagName ) => + ! find( this.state.availableTags, ( tag ) => tag.name === tagName ) + ); + const tagNamesToIds = ( names, availableTags ) => { + return names + .map( ( tagName ) => + find( availableTags, ( tag ) => tag.name === tagName ).id + ); + }; + + if ( newTagNames.length === 0 ) { + return this.props.onUpdateTags( tagNamesToIds( tagNames, this.state.availableTags ) ); + } + const createTag = ( tagName ) => new wp.api.models.Tag( { name: tagName } ).save(); + Promise + .all( newTagNames.map( createTag ) ) + .then( ( newTags ) => { + const newAvailableTags = this.state.availableTags.concat( newTags ); + this.setState( { availableTags: newAvailableTags } ); + return this.props.onUpdateTags( tagNamesToIds( tagNames, newAvailableTags ) ); + } ); } render() { - const suggestions = [ 'React', 'Vue', 'Angular', 'Cycle', 'PReact', 'Inferno' ]; + const { loading, availableTags, selectedTags } = this.state; + const tagNames = availableTags.map( ( tag ) => tag.name ); return (
); } } -export default TagsSelector; +export default connect( + ( state ) => { + return { + tags: getEditedPostAttribute( state, 'tags' ), + }; + }, + ( dispatch ) => { + return { + onUpdateTags( tags ) { + dispatch( editPost( { tags } ) ); + }, + }; + } +)( TagsSelector );