-
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
Chrome: Allow adding, removing tags from posts #970
Changes from all commits
3bddad6
4042170
deed57f
197c19b
0fc2c3e
8f20d1d
bbb9eaf
0905da4
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 |
---|---|---|
|
@@ -8,6 +8,7 @@ | |
], | ||
"env": { | ||
"browser": false, | ||
"es6": true, | ||
"node": true, | ||
"mocha": true | ||
}, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
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. Do we ever worry the 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. Not at this moment, because we ensure all the tagNames are loaded before calling this. |
||
); | ||
}; | ||
|
||
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 ) ) | ||
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. Do we actually need or want to save these tags when they're added? AFAIK the post endpoint is happy to create these for us when the post is saved if they don't yet exist, and may actually be the preferred behavior. 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 tried but failed, which shape should these tags have to do this? The endpoint was giving me an error like 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. Dug around a bit and it appears intentional the endpoint only accepts IDs: |
||
.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 ( | ||
<div className="editor-post-taxonomies__tags-selector"> | ||
<FormTokenField | ||
value={ this.state.tokens } | ||
suggestions={ suggestions } | ||
onChange={ this.onTokensChange } | ||
value={ selectedTags } | ||
displayTransform={ unescape } | ||
suggestions={ tagNames } | ||
onChange={ this.onTagsChange } | ||
maxSuggestions={ MAX_TERMS_SUGGESTIONS } | ||
disabled={ loading } | ||
/> | ||
</div> | ||
); | ||
} | ||
} | ||
|
||
export default TagsSelector; | ||
export default connect( | ||
( state ) => { | ||
return { | ||
tags: getEditedPostAttribute( state, 'tags' ), | ||
}; | ||
}, | ||
( dispatch ) => { | ||
return { | ||
onUpdateTags( tags ) { | ||
dispatch( editPost( { tags } ) ); | ||
}, | ||
}; | ||
} | ||
)( TagsSelector ); | ||
|
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.
Why don't we need to
updateSelectedTags
on initial mount? Wouldn't a post loaded from save already havetags
assigned?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.
but the component won't have the availableTags yet