import React, { PropTypes} from 'react'
import { connect } from 'react-redux'
import ReactDOM from 'react-dom'
import { toggleTutorial } from '../actions'
import ArticleNavigator from './ArticleNavigator'
import ArticleTopBar from './ArticleTopBar'
import ArticleList from './ArticleList'
import ArticleDetail from './ArticleDetail'
import _ from 'lodash'
import { isModalOpen, closeModal } from 'browser/lib/modal'
const TEXT_FILTER = 'TEXT_FILTER'
const FOLDER_FILTER = 'FOLDER_FILTER'
const FOLDER_EXACT_FILTER = 'FOLDER_EXACT_FILTER'
const TAG_FILTER = 'TAG_FILTER'
class HomePage extends React.Component {
componentDidMount () {
// React自体のKey入力はfocusされていないElementからは動かないため、
// `window`に直接かける
this.keyHandler = e => this.handleKeyDown(e)
window.addEventListener('keydown', this.keyHandler)
}
componentWillUnmount () {
window.removeEventListener('keydown', this.keyHandler)
}
handleKeyDown (e) {
if (isModalOpen()) {
if (e.keyCode === 27) closeModal()
return
}
let { status, dispatch } = this.props
let { top, list } = this.refs
let listElement = ReactDOM.findDOMNode(list)
if (status.isTutorialOpen) {
dispatch(toggleTutorial())
e.preventDefault()
return
}
if (e.keyCode === 13 && top.isInputFocused()) {
listElement.focus()
return
}
if (e.keyCode === 27 && top.isInputFocused()) {
if (status.search.length > 0) top.escape()
else listElement.focus()
return
}
// Search inputがfocusされていたら大体のキー入力は無視される。
if (e.keyCode === 27) {
if (document.activeElement !== listElement) {
listElement.focus()
} else {
top.focusInput()
}
return
}
}
render () {
let { dispatch, status, user, articles, allArticles, modified, activeArticle, folders, tags } = this.props
return (
)
}
}
// Ignore invalid key
function ignoreInvalidKey (key) {
return key.length > 0 && !key.match(/^\/\/$/) && !key.match(/^\/$/) && !key.match(/^#$/) && !key.match(/^--/)
}
// Build filter object by key
function buildFilter (key) {
if (key.match(/^\/\/.+/)) {
return {type: FOLDER_EXACT_FILTER, value: key.match(/^\/\/(.+)$/)[1]}
}
if (key.match(/^\/.+/)) {
return {type: FOLDER_FILTER, value: key.match(/^\/(.+)$/)[1]}
}
if (key.match(/^#(.+)/)) {
return {type: TAG_FILTER, value: key.match(/^#(.+)$/)[1]}
}
return {type: TEXT_FILTER, value: key}
}
function isContaining (target, needle) {
return target.match(new RegExp(_.escapeRegExp(needle)))
}
function startsWith (target, needle) {
return target.match(new RegExp('^' + _.escapeRegExp(needle)))
}
function remap (state) {
let { user, folders, status } = state
let _articles = state.articles
let articles = _articles != null ? _articles.data : []
let modified = _articles != null ? _articles.modified : []
articles.sort((a, b) => {
return new Date(b.updatedAt) - new Date(a.updatedAt)
})
let allArticles = articles.slice()
let tags = _.uniq(allArticles.reduce((sum, article) => {
if (!_.isArray(article.tags)) return sum
return sum.concat(article.tags)
}, []))
if (status.search.split(' ').some(key => key === '--unsaved')) articles = articles.filter(article => _.findWhere(modified, {key: article.key}))
// Filter articles
let filters = status.search.split(' ')
.map(key => key.trim())
.filter(ignoreInvalidKey)
.map(buildFilter)
let folderExactFilters = filters.filter(filter => filter.type === FOLDER_EXACT_FILTER)
let folderFilters = filters.filter(filter => filter.type === FOLDER_FILTER)
let textFilters = filters.filter(filter => filter.type === TEXT_FILTER)
let tagFilters = filters.filter(filter => filter.type === TAG_FILTER)
let targetFolders
if (folders != null) {
let exactTargetFolders = folders.filter(folder => {
return _.findWhere(folderExactFilters, {value: folder.name})
})
let fuzzyTargetFolders = folders.filter(folder => {
return _.find(folderFilters, filter => startsWith(folder.name, filter.value))
})
targetFolders = status.targetFolders = exactTargetFolders.concat(fuzzyTargetFolders)
if (targetFolders.length > 0) {
articles = articles.filter(article => {
return _.findWhere(targetFolders, {key: article.FolderKey})
})
}
if (textFilters.length > 0) {
articles = textFilters.reduce((articles, textFilter) => {
return articles.filter(article => {
return isContaining(article.title, textFilter.value) || isContaining(article.content, textFilter.value)
})
}, articles)
}
if (tagFilters.length > 0) {
articles = tagFilters.reduce((articles, tagFilter) => {
return articles.filter(article => {
return _.find(article.tags, tag => isContaining(tag, tagFilter.value))
})
}, articles)
}
}
// Grab active article
let activeArticle = _.findWhere(articles, {key: status.articleKey})
if (activeArticle == null) activeArticle = articles[0]
return {
user,
folders,
status,
articles,
allArticles,
modified,
activeArticle,
tags
}
}
HomePage.propTypes = {
status: PropTypes.shape(),
user: PropTypes.shape({
name: PropTypes.string
}),
articles: PropTypes.array,
allArticles: PropTypes.array,
modified: PropTypes.array,
activeArticle: PropTypes.shape(),
dispatch: PropTypes.func,
folders: PropTypes.array,
tags: PropTypes.array
}
export default connect(remap)(HomePage)