1
0
mirror of https://github.com/BoostIo/Boostnote synced 2025-12-13 17:56:25 +00:00

add FolderSelect

This commit is contained in:
Dick Choi
2016-05-31 14:29:01 +09:00
parent 549930b48c
commit 4fdb72f93c
7 changed files with 376 additions and 29 deletions

View File

@@ -0,0 +1,267 @@
import React, { PropTypes } from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './FolderSelect.styl'
import _ from 'lodash'
class FolderSelect extends React.Component {
constructor (props) {
super(props)
this.state = {
status: 'IDLE',
search: '',
optionIndex: -1
}
}
componentDidMount () {
this.value = this.props.value
}
componentDidUpdate () {
this.value = this.props.value
}
handleClick (e) {
this.setState({
status: 'SEARCH',
optionIndex: -1
}, () => {
this.refs.search.focus()
})
}
handleFocus (e) {
if (this.state.status === 'IDLE') {
this.setState({
status: 'FOCUS'
})
}
}
handleBlur (e) {
if (this.state.status === 'FOCUS') {
this.setState({
status: 'IDLE'
})
}
}
handleKeyDown (e) {
switch (e.keyCode) {
case 13:
if (this.state.status === 'FOCUS') {
this.setState({
status: 'SEARCH',
optionIndex: -1
}, () => {
this.refs.search.focus()
})
}
break
case 40:
case 38:
if (this.state.status === 'FOCUS') {
this.setState({
status: 'SEARCH',
optionIndex: 0
}, () => {
this.refs.search.focus()
})
}
break
case 9:
if (e.shiftKey) {
e.preventDefault()
let tabbable = document.querySelectorAll('a:not([disabled]), button:not([disabled]), input[type=text]:not([disabled]), [tabindex]:not([disabled]):not([tabindex="-1"])')
let previousEl = tabbable[Array.prototype.indexOf.call(tabbable, this.refs.root) - 1]
if (previousEl != null) previousEl.focus()
}
}
}
handleSearchInputBlur (e) {
if (e.relatedTarget !== this.refs.root) {
this.setState({
status: 'IDLE'
})
}
}
handleSearchInputChange (e) {
let { folders } = this.props
let search = this.refs.search.value
let optionIndex = search.length > 0
? _.findIndex(folders, (folder) => {
return folder.name.match(new RegExp('^' + _.escapeRegExp(search), 'i'))
})
: -1
this.setState({
search: this.refs.search.value,
optionIndex: optionIndex
})
}
handleSearchInputKeyDown (e) {
switch (e.keyCode) {
case 40:
e.stopPropagation()
this.nextOption()
break
case 38:
e.stopPropagation()
this.previousOption()
break
case 13:
e.stopPropagation()
this.selectOption()
break
case 27:
e.stopPropagation()
this.setState({
status: 'FOCUS'
}, () => {
this.refs.root.focus()
})
}
}
nextOption () {
let { folders } = this.props
let { optionIndex } = this.state
optionIndex++
if (optionIndex >= folders.length) optionIndex = 0
this.setState({
optionIndex
})
}
previousOption () {
let { folders } = this.props
let { optionIndex } = this.state
optionIndex--
if (optionIndex < 0) optionIndex = folders.length - 1
this.setState({
optionIndex
})
}
selectOption () {
let { folders } = this.props
let optionIndex = this.state.optionIndex
let folder = folders[optionIndex]
if (folder != null) {
this.setState({
status: 'FOCUS'
}, () => {
this.setValue(folder.key)
this.refs.root.focus()
})
}
}
handleOptionClick (folderKey) {
return (e) => {
e.stopPropagation()
this.setState({
status: 'FOCUS'
}, () => {
this.setValue(folderKey)
this.refs.root.focus()
})
}
}
setValue (folderKey) {
this.value = folderKey
this.props.onChange()
}
render () {
let { className, folders, value } = this.props
let currentFolder = _.find(folders, {key: value})
let optionList = folders.map((folder, index) => {
return (
<div styleName={index === this.state.optionIndex
? 'search-optionList-item--active'
: 'search-optionList-item'
}
key={folder.key}
onClick={(e) => this.handleOptionClick(folder.key)(e)}
>
<i style={{color: folder.color}}
className='fa fa-fw fa-cube'
/>&nbsp;
{folder.name}
</div>
)
})
return (
<div className={_.isString(className)
? 'FolderSelect ' + className
: 'FolderSelect'
}
styleName={this.state.status === 'SEARCH'
? 'root--search'
: this.state.status === 'FOCUS'
? 'root--focus'
: 'root'
}
ref='root'
tabIndex='0'
onClick={(e) => this.handleClick(e)}
onFocus={(e) => this.handleFocus(e)}
onBlur={(e) => this.handleBlur(e)}
onKeyDown={(e) => this.handleKeyDown(e)}
>
{this.state.status === 'SEARCH'
? <div styleName='search'>
<input styleName='search-input'
ref='search'
value={this.state.search}
placeholder='Folder...'
onChange={(e) => this.handleSearchInputChange(e)}
onBlur={(e) => this.handleSearchInputBlur(e)}
onKeyDown={(e) => this.handleSearchInputKeyDown(e)}
/>
<div styleName='search-optionList'
ref='optionList'
>
{optionList}
</div>
</div>
: <div styleName='idle'>
<div styleName='idle-label'>
<i style={{color: currentFolder.color}}
className='fa fa-fw fa-cube'
/>&nbsp;
{currentFolder.name}
</div>
<i styleName='idle-caret' className='fa fa-fw fa-caret-down'/>
</div>
}
</div>
)
}
}
FolderSelect.propTypes = {
className: PropTypes.string,
onChange: PropTypes.func,
value: PropTypes.string,
folders: PropTypes.arrayOf(PropTypes.shape({
key: PropTypes.string,
name: PropTypes.string,
color: PropTypes.string
}))
}
export default CSSModules(FolderSelect, styles)

View File

@@ -0,0 +1,74 @@
.root
position relative
border solid 1px transparent
line-height 34px
vertical-align middle
border-radius 5px
transition 0.15s
user-select none
&:hover
background-color white
border-color $ui-borderColor
.root--search, .root--focus
@extend .root
background-color white
border-color $ui-input--focus-borderColor
&:hover
background-color white
border-color $ui-input--focus-borderColor
.idle
position relative
cursor pointer
.idle-label
absolute left top
padding 0 0 0 5px
right 20px
overflow ellipsis
.idle-caret
absolute right top
height 34px
width 20px
line-height 34px
.search
absolute top left right bottom
line-height 34px
.search-input
vertical-align middle
position relative
top -2px
outline none
border none
height 20px
line-height 20px
background-color transparent
padding 0 10px
.search-optionList
position fixed
border $ui-border
z-index 10
background-color white
border-radius 5px
.search-optionList-item
height 34px
width 120px
box-sizing border-box
padding 0 5px
overflow ellipsis
cursor pointer
&:hover
background-color $ui-button--hover-backgroundColor
.search-optionList-item--active
@extend .search-optionList-item
background-color $ui-button--active-backgroundColor
color $ui-button--active-color
&:hover
background-color $ui-button--active-backgroundColor
color $ui-button--active-color

View File

@@ -5,6 +5,7 @@ import MarkdownEditor from 'browser/components/MarkdownEditor'
import queue from 'browser/main/lib/queue' import queue from 'browser/main/lib/queue'
import StarButton from './StarButton' import StarButton from './StarButton'
import TagSelect from './TagSelect' import TagSelect from './TagSelect'
import FolderSelect from './FolderSelect'
import Repository from 'browser/lib/Repository' import Repository from 'browser/lib/Repository'
class NoteDetail extends React.Component { class NoteDetail extends React.Component {
@@ -66,6 +67,7 @@ class NoteDetail extends React.Component {
note.content = this.refs.content.value note.content = this.refs.content.value
note.tags = this.refs.tags.value note.tags = this.refs.tags.value
note.folder = this.refs.folder.value
this.setState({ this.setState({
note, note,
@@ -111,6 +113,7 @@ class NoteDetail extends React.Component {
handleStarButtonClick (e) { handleStarButtonClick (e) {
let { note } = this.state let { note } = this.state
let { dispatch } = this.props let { dispatch } = this.props
let isStarred = note._repository.starred.some((starredKey) => starredKey === note.key) let isStarred = note._repository.starred.some((starredKey) => starredKey === note.key)
if (isStarred) { if (isStarred) {
@@ -143,6 +146,7 @@ class NoteDetail extends React.Component {
render () { render () {
let { note } = this.state let { note } = this.state
let isStarred = note._repository.starred.some((starredKey) => starredKey === note.key) let isStarred = note._repository.starred.some((starredKey) => starredKey === note.key)
let folders = note._repository.folders
return ( return (
<div className='NoteDetail' <div className='NoteDetail'
@@ -157,7 +161,12 @@ class NoteDetail extends React.Component {
onClick={(e) => this.handleStarButtonClick(e)} onClick={(e) => this.handleStarButtonClick(e)}
isActive={isStarred} isActive={isStarred}
/> />
<div styleName='info-left-top-folderSelect'>FolderSelect</div> <FolderSelect styleName='info-left-top-folderSelect'
value={this.state.note.folder}
ref='folder'
folders={folders}
onChange={() => this.handleChange()}
/>
</div> </div>
<div styleName='info-left-bottom'> <div styleName='info-left-bottom'>
<TagSelect <TagSelect
@@ -197,6 +206,9 @@ class NoteDetail extends React.Component {
NoteDetail.propTypes = { NoteDetail.propTypes = {
dispatch: PropTypes.func, dispatch: PropTypes.func,
repositories: PropTypes.array, repositories: PropTypes.array,
note: PropTypes.shape({
}),
style: PropTypes.shape({ style: PropTypes.shape({
left: PropTypes.number left: PropTypes.number
}), }),

View File

@@ -1,4 +1,4 @@
$info-height = 80px $info-height = 75px
.root .root
absolute top bottom right absolute top bottom right
@@ -17,6 +17,7 @@ $info-height = 80px
.info-left-top .info-left-top
height 40px height 40px
line-height 40px
.info-left-top-starButton .info-left-top-starButton
display inline-block display inline-block
@@ -27,15 +28,16 @@ $info-height = 80px
.info-left-top-folderSelect .info-left-top-folderSelect
display inline-block display inline-block
height 40px height 34px
width 100px width 120px
vertical-align top vertical-align middle
.info-left-bottom .info-left-bottom
height 40px height 30px
.info-left-bottom-tagSelect .info-left-bottom-tagSelect
height 40px height 30px
line-height 30px
.info-right .info-right
float right float right

View File

@@ -36,10 +36,10 @@ class NoteList extends React.Component {
} }
// Auto scroll // Auto scroll
let targetIndex = _.findIndex(this.notes, (note) => {
let splitted = location.query.key.split('-') let splitted = location.query.key.split('-')
let repoKey = splitted[0] let repoKey = splitted[0]
let noteKey = splitted[1] let noteKey = splitted[1]
let targetIndex = _.findIndex(this.notes, (note) => {
return repoKey === note._repository.key && noteKey === note.key return repoKey === note._repository.key && noteKey === note.key
}) })
if (targetIndex > -1) { if (targetIndex > -1) {
@@ -133,11 +133,7 @@ class NoteList extends React.Component {
if (location.pathname.match(/\/home/)) { if (location.pathname.match(/\/home/)) {
return repositories return repositories
.reduce((sum, repository) => { .reduce((sum, repository) => {
return sum.concat(repository.notes return sum.concat(repository.notes)
.map((note) => {
note._repository = repository
return note
}))
}, []) }, [])
} }
@@ -148,11 +144,7 @@ class NoteList extends React.Component {
.map((starredKey) => { .map((starredKey) => {
return _.find(repository.notes, {key: starredKey}) return _.find(repository.notes, {key: starredKey})
}) })
.filter((note) => _.isObject(note)) .filter((note) => _.isObject(note)))
.map((note) => {
note._repository = repository
return note
}))
}, []) }, [])
} }
@@ -162,18 +154,10 @@ class NoteList extends React.Component {
let folder = _.find(repository.folders, {key: folderKey}) let folder = _.find(repository.folders, {key: folderKey})
if (folder == null) { if (folder == null) {
return repository.notes return repository.notes
.map((note) => {
note._repository = repository
return note
})
} }
return repository.notes return repository.notes
.filter((note) => note.folder === folderKey) .filter((note) => note.folder === folderKey)
.map((note) => {
note._repository = repository
return note
})
} }
handleNoteClick (key) { handleNoteClick (key) {

View File

@@ -4,6 +4,7 @@ import _ from 'lodash'
let tasks = [] let tasks = []
function _save (task, repoKey, note) { function _save (task, repoKey, note) {
note = Object.assign({}, note)
delete note._repository delete note._repository
task.status = 'process' task.status = 'process'
@@ -15,7 +16,6 @@ function _save (task, repoKey, note) {
}) })
.then((note) => { .then((note) => {
tasks.splice(tasks.indexOf(task), 1) tasks.splice(tasks.indexOf(task), 1)
console.log(tasks)
console.info('Note saved', note) console.info('Note saved', note)
}) })
.catch((err) => { .catch((err) => {

View File

@@ -33,6 +33,11 @@ function repositories (state = initialRepositories, action) {
console.info('REDUX >> ', action) console.info('REDUX >> ', action)
switch (action.type) { switch (action.type) {
case 'INIT_ALL': case 'INIT_ALL':
action.data.forEach((repo) => {
repo.notes.forEach((note) => {
note._repository = repo
})
})
return action.data.slice() return action.data.slice()
case 'ADD_REPOSITORY': case 'ADD_REPOSITORY':
{ {
@@ -113,6 +118,7 @@ function repositories (state = initialRepositories, action) {
let targetRepo = _.find(repos, {key: action.repository}) let targetRepo = _.find(repos, {key: action.repository})
if (targetRepo == null) return state if (targetRepo == null) return state
action.note._repository = targetRepo
targetRepo.notes.push(action.note) targetRepo.notes.push(action.note)
return repos return repos
@@ -126,6 +132,8 @@ function repositories (state = initialRepositories, action) {
let targetNoteIndex = _.findIndex(targetRepo.notes, {key: action.note.key}) let targetNoteIndex = _.findIndex(targetRepo.notes, {key: action.note.key})
action.note.updatedAt = Date.now() action.note.updatedAt = Date.now()
action.note._repository = targetRepo
if (targetNoteIndex > -1) { if (targetNoteIndex > -1) {
targetRepo.notes.splice(targetNoteIndex, 1, action.note) targetRepo.notes.splice(targetNoteIndex, 1, action.note)
} else { } else {