mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-13 09:46:22 +00:00
add FolderSelect
This commit is contained in:
267
browser/main/Detail/FolderSelect.js
Normal file
267
browser/main/Detail/FolderSelect.js
Normal 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'
|
||||
/>
|
||||
{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'
|
||||
/>
|
||||
{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)
|
||||
74
browser/main/Detail/FolderSelect.styl
Normal file
74
browser/main/Detail/FolderSelect.styl
Normal 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
|
||||
@@ -5,6 +5,7 @@ import MarkdownEditor from 'browser/components/MarkdownEditor'
|
||||
import queue from 'browser/main/lib/queue'
|
||||
import StarButton from './StarButton'
|
||||
import TagSelect from './TagSelect'
|
||||
import FolderSelect from './FolderSelect'
|
||||
import Repository from 'browser/lib/Repository'
|
||||
|
||||
class NoteDetail extends React.Component {
|
||||
@@ -66,6 +67,7 @@ class NoteDetail extends React.Component {
|
||||
|
||||
note.content = this.refs.content.value
|
||||
note.tags = this.refs.tags.value
|
||||
note.folder = this.refs.folder.value
|
||||
|
||||
this.setState({
|
||||
note,
|
||||
@@ -111,6 +113,7 @@ class NoteDetail extends React.Component {
|
||||
handleStarButtonClick (e) {
|
||||
let { note } = this.state
|
||||
let { dispatch } = this.props
|
||||
|
||||
let isStarred = note._repository.starred.some((starredKey) => starredKey === note.key)
|
||||
|
||||
if (isStarred) {
|
||||
@@ -143,6 +146,7 @@ class NoteDetail extends React.Component {
|
||||
render () {
|
||||
let { note } = this.state
|
||||
let isStarred = note._repository.starred.some((starredKey) => starredKey === note.key)
|
||||
let folders = note._repository.folders
|
||||
|
||||
return (
|
||||
<div className='NoteDetail'
|
||||
@@ -157,7 +161,12 @@ class NoteDetail extends React.Component {
|
||||
onClick={(e) => this.handleStarButtonClick(e)}
|
||||
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 styleName='info-left-bottom'>
|
||||
<TagSelect
|
||||
@@ -197,6 +206,9 @@ class NoteDetail extends React.Component {
|
||||
NoteDetail.propTypes = {
|
||||
dispatch: PropTypes.func,
|
||||
repositories: PropTypes.array,
|
||||
note: PropTypes.shape({
|
||||
|
||||
}),
|
||||
style: PropTypes.shape({
|
||||
left: PropTypes.number
|
||||
}),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
$info-height = 80px
|
||||
$info-height = 75px
|
||||
|
||||
.root
|
||||
absolute top bottom right
|
||||
@@ -17,6 +17,7 @@ $info-height = 80px
|
||||
|
||||
.info-left-top
|
||||
height 40px
|
||||
line-height 40px
|
||||
|
||||
.info-left-top-starButton
|
||||
display inline-block
|
||||
@@ -27,15 +28,16 @@ $info-height = 80px
|
||||
|
||||
.info-left-top-folderSelect
|
||||
display inline-block
|
||||
height 40px
|
||||
width 100px
|
||||
vertical-align top
|
||||
height 34px
|
||||
width 120px
|
||||
vertical-align middle
|
||||
|
||||
.info-left-bottom
|
||||
height 40px
|
||||
height 30px
|
||||
|
||||
.info-left-bottom-tagSelect
|
||||
height 40px
|
||||
height 30px
|
||||
line-height 30px
|
||||
|
||||
.info-right
|
||||
float right
|
||||
|
||||
@@ -36,10 +36,10 @@ class NoteList extends React.Component {
|
||||
}
|
||||
|
||||
// Auto scroll
|
||||
let splitted = location.query.key.split('-')
|
||||
let repoKey = splitted[0]
|
||||
let noteKey = splitted[1]
|
||||
let targetIndex = _.findIndex(this.notes, (note) => {
|
||||
let splitted = location.query.key.split('-')
|
||||
let repoKey = splitted[0]
|
||||
let noteKey = splitted[1]
|
||||
return repoKey === note._repository.key && noteKey === note.key
|
||||
})
|
||||
if (targetIndex > -1) {
|
||||
@@ -133,11 +133,7 @@ class NoteList extends React.Component {
|
||||
if (location.pathname.match(/\/home/)) {
|
||||
return repositories
|
||||
.reduce((sum, repository) => {
|
||||
return sum.concat(repository.notes
|
||||
.map((note) => {
|
||||
note._repository = repository
|
||||
return note
|
||||
}))
|
||||
return sum.concat(repository.notes)
|
||||
}, [])
|
||||
}
|
||||
|
||||
@@ -148,11 +144,7 @@ class NoteList extends React.Component {
|
||||
.map((starredKey) => {
|
||||
return _.find(repository.notes, {key: starredKey})
|
||||
})
|
||||
.filter((note) => _.isObject(note))
|
||||
.map((note) => {
|
||||
note._repository = repository
|
||||
return note
|
||||
}))
|
||||
.filter((note) => _.isObject(note)))
|
||||
}, [])
|
||||
}
|
||||
|
||||
@@ -162,18 +154,10 @@ class NoteList extends React.Component {
|
||||
let folder = _.find(repository.folders, {key: folderKey})
|
||||
if (folder == null) {
|
||||
return repository.notes
|
||||
.map((note) => {
|
||||
note._repository = repository
|
||||
return note
|
||||
})
|
||||
}
|
||||
|
||||
return repository.notes
|
||||
.filter((note) => note.folder === folderKey)
|
||||
.map((note) => {
|
||||
note._repository = repository
|
||||
return note
|
||||
})
|
||||
}
|
||||
|
||||
handleNoteClick (key) {
|
||||
|
||||
@@ -4,6 +4,7 @@ import _ from 'lodash'
|
||||
let tasks = []
|
||||
|
||||
function _save (task, repoKey, note) {
|
||||
note = Object.assign({}, note)
|
||||
delete note._repository
|
||||
|
||||
task.status = 'process'
|
||||
@@ -15,7 +16,6 @@ function _save (task, repoKey, note) {
|
||||
})
|
||||
.then((note) => {
|
||||
tasks.splice(tasks.indexOf(task), 1)
|
||||
console.log(tasks)
|
||||
console.info('Note saved', note)
|
||||
})
|
||||
.catch((err) => {
|
||||
|
||||
@@ -33,6 +33,11 @@ function repositories (state = initialRepositories, action) {
|
||||
console.info('REDUX >> ', action)
|
||||
switch (action.type) {
|
||||
case 'INIT_ALL':
|
||||
action.data.forEach((repo) => {
|
||||
repo.notes.forEach((note) => {
|
||||
note._repository = repo
|
||||
})
|
||||
})
|
||||
return action.data.slice()
|
||||
case 'ADD_REPOSITORY':
|
||||
{
|
||||
@@ -113,6 +118,7 @@ function repositories (state = initialRepositories, action) {
|
||||
let targetRepo = _.find(repos, {key: action.repository})
|
||||
|
||||
if (targetRepo == null) return state
|
||||
action.note._repository = targetRepo
|
||||
targetRepo.notes.push(action.note)
|
||||
|
||||
return repos
|
||||
@@ -126,6 +132,8 @@ function repositories (state = initialRepositories, action) {
|
||||
|
||||
let targetNoteIndex = _.findIndex(targetRepo.notes, {key: action.note.key})
|
||||
action.note.updatedAt = Date.now()
|
||||
action.note._repository = targetRepo
|
||||
|
||||
if (targetNoteIndex > -1) {
|
||||
targetRepo.notes.splice(targetNoteIndex, 1, action.note)
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user