diff --git a/browser/main/Detail/FolderSelect.js b/browser/main/Detail/FolderSelect.js new file mode 100644 index 00000000..ec38d63f --- /dev/null +++ b/browser/main/Detail/FolderSelect.js @@ -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 ( +
this.handleOptionClick(folder.key)(e)} + > +   + {folder.name} +
+ ) + }) + + return ( +
this.handleClick(e)} + onFocus={(e) => this.handleFocus(e)} + onBlur={(e) => this.handleBlur(e)} + onKeyDown={(e) => this.handleKeyDown(e)} + > + {this.state.status === 'SEARCH' + ?
+ this.handleSearchInputChange(e)} + onBlur={(e) => this.handleSearchInputBlur(e)} + onKeyDown={(e) => this.handleSearchInputKeyDown(e)} + /> +
+ {optionList} +
+
+ :
+
+   + {currentFolder.name} +
+ +
+ } + +
+ ) + } +} + +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) diff --git a/browser/main/Detail/FolderSelect.styl b/browser/main/Detail/FolderSelect.styl new file mode 100644 index 00000000..7fb7b2ca --- /dev/null +++ b/browser/main/Detail/FolderSelect.styl @@ -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 diff --git a/browser/main/Detail/NoteDetail.js b/browser/main/Detail/NoteDetail.js index ebce7262..ef3d23dd 100644 --- a/browser/main/Detail/NoteDetail.js +++ b/browser/main/Detail/NoteDetail.js @@ -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 (
this.handleStarButtonClick(e)} isActive={isStarred} /> -
FolderSelect
+ this.handleChange()} + />
{ - 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) { diff --git a/browser/main/lib/queue.js b/browser/main/lib/queue.js index c0554b48..8e70ae0c 100644 --- a/browser/main/lib/queue.js +++ b/browser/main/lib/queue.js @@ -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) => { diff --git a/browser/main/store.js b/browser/main/store.js index 3f41ad5c..8050466d 100644 --- a/browser/main/store.js +++ b/browser/main/store.js @@ -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 {