diff --git a/browser/main/HomePage.js b/browser/main/HomePage.js
index beb312ee..c40b4dfb 100644
--- a/browser/main/HomePage.js
+++ b/browser/main/HomePage.js
@@ -99,7 +99,7 @@ class HomePage extends React.Component {
}
render () {
- let { dispatch, status, articles, allArticles, activeArticle, folders, filters } = this.props
+ let { dispatch, status, articles, allArticles, activeArticle, folders, tags, filters } = this.props
return (
@@ -129,6 +129,7 @@ class HomePage extends React.Component {
activeArticle={activeArticle}
folders={folders}
status={status}
+ tags={tags}
filters={filters}
/>
@@ -164,6 +165,11 @@ function remap (state) {
})
let allArticles = articles.slice()
+ let tags = _.uniq(allArticles.reduce((sum, article) => {
+ if (!_.isArray(article.tags)) return sum
+ return sum.concat(article.tags)
+ }, []))
+
// Filter articles
let filters = status.search.split(' ')
.map(key => key.trim())
@@ -254,6 +260,7 @@ function remap (state) {
allArticles,
articles,
activeArticle,
+ tags,
filters: {
folder: folderFilters,
tag: tagFilters,
diff --git a/browser/main/HomePage/ArticleDetail.js b/browser/main/HomePage/ArticleDetail.js
index 10c702c5..6ae52e7d 100644
--- a/browser/main/HomePage/ArticleDetail.js
+++ b/browser/main/HomePage/ArticleDetail.js
@@ -303,7 +303,7 @@ export default class ArticleDetail extends React.Component {
}
renderEdit () {
- let { folders, status } = this.props
+ let { folders, status, tags } = this.props
let folderOptions = folders.map(folder => {
return (
@@ -326,6 +326,7 @@ export default class ArticleDetail extends React.Component {
this.handleTagsChange(tags, tag)}
+ suggestTags={tags}
/>
{status.isTutorialOpen ? tagSelectTutorialElement : null}
diff --git a/browser/styles/main/HomeContainer/components/ArticleDetail.styl b/browser/styles/main/HomeContainer/components/ArticleDetail.styl
index 0a3182f4..2f4ae10c 100644
--- a/browser/styles/main/HomeContainer/components/ArticleDetail.styl
+++ b/browser/styles/main/HomeContainer/components/ArticleDetail.styl
@@ -98,44 +98,66 @@ iptFocusBorderColor = #369DCD
&:hover
background-color white
.TagSelect
- white-space nowrap
- overflow-x auto
- position relative
- margin-top 5px
- noSelect()
- z-index 30
- background-color #E6E6E6
- .tagItem
- background-color brandColor
- border-radius 2px
- color white
- margin 0 2px
- padding 0
- border 1px solid darken(brandColor, 10%)
- button.tagRemoveBtn
+ .tags
+ white-space nowrap
+ overflow-x auto
+ position relative
+ max-width 350px
+ margin-top 5px
+ noSelect()
+ z-index 30
+ background-color #E6E6E6
+ .tagItem
+ background-color brandColor
+ border-radius 2px
color white
+ margin 0 2px
+ padding 0
+ border 1px solid darken(brandColor, 10%)
+ button.tagRemoveBtn
+ color white
+ border-radius 2px
+ border none
+ background-color transparent
+ padding 4px 2px
+ border-right 1px solid #E6E6E6
+ font-size 8px
+ line-height 12px
+ transition 0.1s
+ &:hover
+ background-color lighten(brandColor, 10%)
+ .tagLabel
+ padding 4px 4px
+ font-size 12px
+ line-height 12px
+ input.tagInput
+ background-color transparent
+ outline none
+ margin 0 2px
border-radius 2px
border none
- background-color transparent
- padding 4px 2px
- border-right 1px solid #E6E6E6
- font-size 8px
- line-height 12px
transition 0.1s
+ height 18px
+ .suggestTags
+ position fixed
+ width 150px
+ max-height 150px
+ background-color white
+ z-index 5
+ border 1px solid borderColor
+ border-radius 5px
+ button
+ width 100%
+ display block
+ padding 0 15px
+ height 33px
+ line-height 33px
+ background-color transparent
+ border none
+ text-align left
+ font-size 14px
&:hover
- background-color lighten(brandColor, 10%)
- .tagLabel
- padding 4px 4px
- font-size 12px
- line-height 12px
- input.tagInput
- background-color transparent
- outline none
- margin 0 2px
- border-radius 2px
- border none
- transition 0.1s
- height 18px
+ background-color darken(white, 10%)
.right
button
cursor pointer
@@ -222,9 +244,6 @@ iptFocusBorderColor = #369DCD
display inline-block
&:hover
background-color darken(white, 10%)
-
-
-
.title
absolute left top bottom
right 150px
diff --git a/lib/components/ModeSelect.js b/lib/components/ModeSelect.js
index 554e361c..28404d20 100644
--- a/lib/components/ModeSelect.js
+++ b/lib/components/ModeSelect.js
@@ -18,7 +18,7 @@ export default class ModeSelect extends React.Component {
}
}
- componentDidMount (e) {
+ componentDidMount () {
this.blurHandler = e => {
let searchElement = ReactDOM.findDOMNode(this.refs.search)
if (this.state.mode === EDIT_MODE && document.activeElement !== searchElement) {
@@ -28,7 +28,7 @@ export default class ModeSelect extends React.Component {
window.addEventListener('click', this.blurHandler)
}
- componentWillUnmount (e) {
+ componentWillUnmount () {
window.removeEventListener('click', this.blurHandler)
let searchElement = ReactDOM.findDOMNode(this.refs.search)
if (searchElement != null && this.searchKeyDownListener != null) {
diff --git a/lib/components/TagSelect.js b/lib/components/TagSelect.js
index b7fbead8..f000d32d 100644
--- a/lib/components/TagSelect.js
+++ b/lib/components/TagSelect.js
@@ -3,23 +3,54 @@ import ReactDOM from 'react-dom'
import _ from 'lodash'
import linkState from 'boost/linkState'
+function isNotEmptyString (str) {
+ return _.isString(str) && str.length > 0
+}
+
export default class TagSelect extends React.Component {
constructor (props) {
super(props)
this.state = {
- input: ''
+ input: '',
+ isInputFocused: false
}
}
- handleKeyDown (e) {
- if (e.keyCode !== 13) return false
- e.preventDefault()
+ componentDidMount () {
+ this.blurInputBlurHandler = e => {
+ if (ReactDOM.findDOMNode(this.refs.tagInput) !== document.activeElement) {
+ this.setState({isInputFocused: false})
+ }
+ }
+ window.addEventListener('click', this.blurInputBlurHandler)
+ }
+ componentWillUnmount (e) {
+ window.removeEventListener('click', this.blurInputBlurHandler)
+ }
+
+ // Suggestは必ずInputの下に位置するようにする
+ componentDidUpdate () {
+ if (this.shouldShowSuggest()) {
+ let inputRect = ReactDOM.findDOMNode(this.refs.tagInput).getBoundingClientRect()
+ let suggestElement = ReactDOM.findDOMNode(this.refs.suggestTags)
+ if (suggestElement != null) {
+ suggestElement.style.top = inputRect.top + 20 + 'px'
+ suggestElement.style.left = inputRect.left + 'px'
+ }
+ }
+ }
+
+ shouldShowSuggest () {
+ return this.state.isInputFocused && isNotEmptyString(this.state.input)
+ }
+
+ addTag (tag, clearInput = true) {
let tags = this.props.tags.slice(0)
- let newTag = this.state.input.trim()
+ let newTag = tag.trim()
- if (newTag.length === 0) {
+ if (newTag.length === 0 && clearInput) {
this.setState({input: ''})
return
}
@@ -30,13 +61,38 @@ export default class TagSelect extends React.Component {
if (_.isFunction(this.props.onChange)) {
this.props.onChange(newTag, tags)
}
- this.setState({input: ''})
+ if (clearInput) this.setState({input: ''})
+ }
+
+ handleKeyDown (e) {
+ switch (e.keyCode) {
+ case 8:
+ {
+ if (this.state.input.length > 0) break
+ e.preventDefault()
+
+ let tags = this.props.tags.slice(0)
+ tags.pop()
+
+ this.props.onChange(null, tags)
+ }
+ break
+ case 13:
+ {
+ e.preventDefault()
+ this.addTag(this.state.input)
+ }
+ }
}
handleThisClick (e) {
ReactDOM.findDOMNode(this.refs.tagInput).focus()
}
+ handleInputFocus (e) {
+ this.setState({isInputFocused: true})
+ }
+
handleItemRemoveButton (tag) {
return e => {
e.stopPropagation()
@@ -50,8 +106,16 @@ export default class TagSelect extends React.Component {
}
}
+ handleSuggestClick (tag) {
+ return e => {
+ this.addTag(tag)
+ }
+ }
+
render () {
- var tagElements = _.isArray(this.props.tags)
+ let { tags, suggestTags } = this.props
+
+ let tagElements = _.isArray(tags)
? this.props.tags.map(tag => (
@@ -59,16 +123,37 @@ export default class TagSelect extends React.Component {
))
: null
+ let suggestElements = this.shouldShowSuggest() ? suggestTags
+ .filter(tag => {
+ return tag.match(this.state.input)
+ })
+ .map(tag => {
+ return
+ })
+ : null
+
return (
this.handleThisClick(e)}>
- {tagElements}
-
this.handleKeyDown(e)}
- ref='tagInput'
- valueLink={this.linkState('input')}
- placeholder='Click here to add tags'
- className='tagInput'/>
+
+ {tagElements}
+ this.handleKeyDown(e)}
+ ref='tagInput'
+ valueLink={this.linkState('input')}
+ placeholder='Click here to add tags'
+ className='tagInput'
+ onFocus={e => this.handleInputFocus(e)}
+ />
+
+ {suggestElements != null && suggestElements.length > 0
+ ? (
+
+ {suggestElements}
+
+ )
+ : null
+ }
)
}
@@ -76,7 +161,8 @@ export default class TagSelect extends React.Component {
TagSelect.propTypes = {
tags: PropTypes.arrayOf(PropTypes.string),
- onChange: PropTypes.func
+ onChange: PropTypes.func,
+ suggestTags: PropTypes.array
}
TagSelect.prototype.linkState = linkState