diff --git a/browser/main/HomePage.js b/browser/main/HomePage.js index ffd3b790..4ed0f81b 100644 --- a/browser/main/HomePage.js +++ b/browser/main/HomePage.js @@ -1,6 +1,6 @@ import React, { PropTypes} from 'react' import { connect } from 'react-redux' -import { CREATE_MODE, IDLE_MODE, NEW } from 'boost/actions' +import { CREATE_MODE, EDIT_MODE, IDLE_MODE, NEW } from 'boost/actions' // import UserNavigator from './HomePage/UserNavigator' import ArticleNavigator from './HomePage/ArticleNavigator' import ArticleTopBar from './HomePage/ArticleTopBar' @@ -8,24 +8,102 @@ import ArticleList from './HomePage/ArticleList' import ArticleDetail from './HomePage/ArticleDetail' import _ from 'lodash' import keygen from 'boost/keygen' +import { isModalOpen, closeModal } from 'boost/modal' const TEXT_FILTER = 'TEXT_FILTER' const FOLDER_FILTER = 'FOLDER_FILTER' const TAG_FILTER = 'TAG_FILTER' class HomePage extends React.Component { + componentDidMount () { + this.listener = (e) => this.handleKeyDown(e) + window.addEventListener('keydown', this.listener) + } + + componentWillUnmount () { + window.removeEventListener('keydown', this.listener) + } + + handleKeyDown (e) { + if (isModalOpen() && e.keyCode === 27) { + closeModal() + return + } + + let { status } = this.props + let { nav, top, list, detail } = this.refs + + if (top.isInputFocused() && !e.metaKey) { + if (e.keyCode === 13 || e.keyCode === 27) top.blurInput() + return + } + + switch (status.mode) { + case CREATE_MODE: + case EDIT_MODE: + if (e.keyCode === 27) { + detail.handleCancelButtonClick() + } + if (e.keyCode === 13 && e.metaKey) { + detail.handleSaveButtonClick() + } + break + case IDLE_MODE: + if (e.keyCode === 69) { + detail.handleEditButtonClick() + } + if (e.keyCode === 68) { + detail.handleDeleteButtonClick() + } + + // `detail`の`openDeleteConfirmMenu`の時。 + if (detail.state.openDeleteConfirmMenu) { + if (e.keyCode === 27) { + detail.handleDeleteCancleButtonClick() + } + if (e.keyCode === 13 && e.metaKey) { + detail.handleDeleteConfirmButtonClick() + } + break + } + + // `detail`の`openDeleteConfirmMenu`が`true`なら呼ばれない。 + if (e.keyCode === 27) { + top.focusInput() + } + + if (e.keyCode === 38) { + list.selectPriorArticle() + } + + if (e.keyCode === 40) { + list.selectNextArticle() + } + + if (e.keyCode === 13 && e.metaKey) { + nav.handleNewPostButtonClick() + } + } + } + render () { - let { dispatch, status, articles, activeArticle, folders } = this.props + let { dispatch, status, articles, activeArticle, folders, filters } = this.props return (
- +
) @@ -139,7 +219,12 @@ function remap (state) { folders, status, articles, - activeArticle + activeArticle, + filters: { + folder: folderFilters, + tag: tagFilters, + text: textFilters + } } } @@ -153,7 +238,12 @@ HomePage.propTypes = { articles: PropTypes.array, activeArticle: PropTypes.shape(), dispatch: PropTypes.func, - folders: PropTypes.array + folders: PropTypes.array, + filters: PropTypes.shape({ + folder: PropTypes.array, + tag: PropTypes.array, + text: PropTypes.array + }) } export default connect(remap)(HomePage) diff --git a/browser/main/HomePage/ArticleDetail.js b/browser/main/HomePage/ArticleDetail.js index 9a33af2a..a8b1c72c 100644 --- a/browser/main/HomePage/ArticleDetail.js +++ b/browser/main/HomePage/ArticleDetail.js @@ -4,7 +4,7 @@ import _ from 'lodash' import ModeIcon from 'boost/components/ModeIcon' import MarkdownPreview from 'boost/components/MarkdownPreview' import CodeEditor from 'boost/components/CodeEditor' -import { IDLE_MODE, CREATE_MODE, EDIT_MODE, switchMode, updateArticle, destroyArticle } from 'boost/actions' +import { IDLE_MODE, CREATE_MODE, EDIT_MODE, switchMode, switchArticle, switchFolder, updateArticle, destroyArticle } from 'boost/actions' import aceModes from 'boost/ace-modes' import Select from 'react-select' import linkState from 'boost/linkState' @@ -38,6 +38,11 @@ export default class ArticleDetail extends React.Component { console.log('receive props') }) } + + let isEdit = nextProps.status.mode === EDIT_MODE || nextProps.status.mode === CREATE_MODE + if (isEdit && this.state.openDeleteConfirmMenu) { + this.setState({openDeleteConfirmMenu: false}) + } } renderEmpty () { @@ -135,7 +140,7 @@ export default class ArticleDetail extends React.Component { } handleSaveButtonClick (e) { - let { dispatch, folders } = this.props + let { dispatch, folders, filters } = this.props let article = this.state.article let newArticle = Object.assign({}, article) @@ -147,6 +152,8 @@ export default class ArticleDetail extends React.Component { dispatch(updateArticle(newArticle)) dispatch(switchMode(IDLE_MODE)) + if (filters.folder.length !== 0) dispatch(switchFolder(folder.name)) + dispatch(switchArticle(newArticle.key)) } handleFolderKeyChange (e) { diff --git a/browser/main/HomePage/ArticleList.js b/browser/main/HomePage/ArticleList.js index 67442513..3fc307b8 100644 --- a/browser/main/HomePage/ArticleList.js +++ b/browser/main/HomePage/ArticleList.js @@ -15,6 +15,26 @@ export default class ArticleList extends React.Component { clearInterval(this.refreshTimer) } + selectPriorArticle () { + let { articles, activeArticle, dispatch } = this.props + let targetIndex = articles.indexOf(activeArticle) - 1 + let targetArticle = articles[targetIndex] + + if (targetArticle != null) { + dispatch(switchArticle(targetArticle.key)) + } + } + + selectNextArticle () { + let { articles, activeArticle, dispatch } = this.props + let targetIndex = articles.indexOf(activeArticle) + 1 + let targetArticle = articles[targetIndex] + + if (targetArticle != null) { + dispatch(switchArticle(targetArticle.key)) + } + } + handleArticleClick (article) { let { dispatch } = this.props return function (e) { diff --git a/browser/main/HomePage/ArticleNavigator.js b/browser/main/HomePage/ArticleNavigator.js index 5db3b832..1d6ea82c 100644 --- a/browser/main/HomePage/ArticleNavigator.js +++ b/browser/main/HomePage/ArticleNavigator.js @@ -7,6 +7,7 @@ import Preferences from 'boost/components/modal/Preferences' import CreateNewFolder from 'boost/components/modal/CreateNewFolder' import remote from 'remote' +let userName = remote.getGlobal('process').env.USER export default class ArticleNavigator extends React.Component { handlePreferencesButtonClick (e) { @@ -51,8 +52,6 @@ export default class ArticleNavigator extends React.Component { ) }) - let userName = remote.getGlobal('process').env.USER - return (
diff --git a/browser/main/HomePage/ArticleTopBar.js b/browser/main/HomePage/ArticleTopBar.js index 317d7e4a..c78ecf37 100644 --- a/browser/main/HomePage/ArticleTopBar.js +++ b/browser/main/HomePage/ArticleTopBar.js @@ -4,16 +4,29 @@ import ExternalLink from 'boost/components/ExternalLink' import { setSearchFilter } from 'boost/actions' export default class ArticleTopBar extends React.Component { + isInputFocused () { + return document.activeElement === ReactDOM.findDOMNode(this.refs.searchInput) + } + + focusInput () { + ReactDOM.findDOMNode(this.refs.searchInput).focus() + } + + blurInput () { + ReactDOM.findDOMNode(this.refs.searchInput).blur() + } + handleSearchChange (e) { let { dispatch } = this.props dispatch(setSearchFilter(e.target.value)) } + handleSearchClearButton (e) { let { dispatch } = this.props dispatch(setSearchFilter('')) - ReactDOM.findDOMNode(this.refs.searchInput).focus() + this.focusInput() } render () { diff --git a/browser/main/index.js b/browser/main/index.js index 32a4f106..33e468fb 100644 --- a/browser/main/index.js +++ b/browser/main/index.js @@ -7,6 +7,8 @@ import HomePage from './HomePage' // import auth from 'boost/auth' import store from 'boost/store' import ReactDOM from 'react-dom' +import { isModalOpen, closeModal } from 'boost/modal' +import { IDLE_MODE, CREATE_MODE, EDIT_MODE } from 'boost/actions' require('../styles/main/index.styl') let routes = ( @@ -25,21 +27,4 @@ ReactDOM.render(( ), el, function () { let loadingCover = document.getElementById('loadingCover') loadingCover.parentNode.removeChild(loadingCover) - - // Refresh user information - // if (auth.user() != null) { - // fetchCurrentUser() - // .then(function (res) { - // let user = res.body - // store.dispatch(updateUser(user)) - // }) - // .catch(function (err) { - // if (err.status === 401) { - // auth.clear() - // if (window != null) window.location.reload() - // } - // console.error(err.message) - // console.log('Fetch failed') - // }) - // } }) diff --git a/lib/actions.js b/lib/actions.js index ac0a2059..ba0f668e 100644 --- a/lib/actions.js +++ b/lib/actions.js @@ -10,6 +10,7 @@ export const SWITCH_MODE = 'SWITCH_MODE' export const SWITCH_ARTICLE = 'SWITCH_ARTICLE' export const SET_SEARCH_FILTER = 'SET_SEARCH_FILTER' export const SET_TAG_FILTER = 'SET_TAG_FILTER' +export const CLEAR_SEARCH = 'CLEAR_SEARCH' // Status - mode export const IDLE_MODE = 'IDLE_MODE' @@ -18,8 +19,6 @@ export const EDIT_MODE = 'EDIT_MODE' // Article status export const NEW = 'NEW' -export const SYNCING = 'SYNCING' -export const UNSYNCED = 'UNSYNCED' // DB export function updateArticle (article) { @@ -91,3 +90,9 @@ export function setTagFilter (tag) { data: tag } } + +export function clearSearch () { + return { + type: CLEAR_SEARCH + } +} diff --git a/lib/components/TagSelect.js b/lib/components/TagSelect.js index 91a2ed63..b7fbead8 100644 --- a/lib/components/TagSelect.js +++ b/lib/components/TagSelect.js @@ -17,7 +17,12 @@ export default class TagSelect extends React.Component { e.preventDefault() let tags = this.props.tags.slice(0) - let newTag = this.state.input + let newTag = this.state.input.trim() + + if (newTag.length === 0) { + this.setState({input: ''}) + return + } tags.push(newTag) tags = _.uniq(tags) diff --git a/lib/modal.js b/lib/modal.js index ae720490..a445ec33 100644 --- a/lib/modal.js +++ b/lib/modal.js @@ -40,3 +40,7 @@ export function closeModal () { if (modalBase == null) { return } modalBase.setState({component: null, componentProps: null, isHidden: true}) } + +export function isModalOpen () { + return !modalBase.state.isHidden +} diff --git a/lib/reducer.js b/lib/reducer.js index a51d48a8..e74f1370 100644 --- a/lib/reducer.js +++ b/lib/reducer.js @@ -1,6 +1,6 @@ import { combineReducers } from 'redux' import _ from 'lodash' -import { SWITCH_FOLDER, SWITCH_MODE, SWITCH_ARTICLE, SET_SEARCH_FILTER, SET_TAG_FILTER, ARTICLE_UPDATE, ARTICLE_DESTROY, FOLDER_CREATE, FOLDER_UPDATE, FOLDER_DESTROY, IDLE_MODE, CREATE_MODE } from './actions' +import { SWITCH_FOLDER, SWITCH_MODE, SWITCH_ARTICLE, SET_SEARCH_FILTER, SET_TAG_FILTER, CLEAR_SEARCH, ARTICLE_UPDATE, ARTICLE_DESTROY, FOLDER_CREATE, FOLDER_UPDATE, FOLDER_DESTROY, IDLE_MODE, CREATE_MODE } from './actions' import dataStore from 'boost/dataStore' import keygen from 'boost/keygen' @@ -134,6 +134,11 @@ function status (state = initialStatus, action) { state.search = `#${action.data}` state.mode = IDLE_MODE + return state + case CLEAR_SEARCH: + state.search = '' + state.mode = IDLE_MODE + return state default: return state