mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-13 17:56:25 +00:00
No edit mode
This commit is contained in:
@@ -133,7 +133,7 @@ export default class ModeSelect extends React.Component {
|
||||
|
||||
return (
|
||||
<div className={className + ' idle'} onClick={e => this.handleIdleSelectClick(e)}>
|
||||
<ModeIcon mode={modeName}/>
|
||||
<ModeIcon mode={modeName}/>{mode.label}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,25 +2,16 @@ import React, { PropTypes } from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import moment from 'moment'
|
||||
import _ from 'lodash'
|
||||
import ModeIcon from 'browser/components/ModeIcon'
|
||||
import MarkdownPreview from 'browser/components/MarkdownPreview'
|
||||
import CodeEditor from 'browser/components/CodeEditor'
|
||||
import {
|
||||
IDLE_MODE,
|
||||
EDIT_MODE,
|
||||
switchMode,
|
||||
switchArticle,
|
||||
switchFolder,
|
||||
clearSearch,
|
||||
lockStatus,
|
||||
unlockStatus,
|
||||
updateArticle,
|
||||
destroyArticle,
|
||||
NEW
|
||||
cacheArticle,
|
||||
saveArticle,
|
||||
destroyArticle
|
||||
} from '../../actions'
|
||||
import linkState from 'browser/lib/linkState'
|
||||
import FolderMark from 'browser/components/FolderMark'
|
||||
import TagLink from '../TagLink'
|
||||
import TagSelect from 'browser/components/TagSelect'
|
||||
import ModeSelect from 'browser/components/ModeSelect'
|
||||
import activityRecord from 'browser/lib/activityRecord'
|
||||
@@ -123,45 +114,31 @@ export default class ArticleDetail extends React.Component {
|
||||
clearInterval(this.refreshTimer)
|
||||
}
|
||||
|
||||
componentDidUpdate (prevProps) {
|
||||
let isModeChanged = prevProps.status.mode !== this.props.status.mode
|
||||
if (isModeChanged && this.props.status.mode === EDIT_MODE) {
|
||||
ReactDOM.findDOMNode(this.refs.title).focus()
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
let nextState = {}
|
||||
if (nextProps.activeArticle == null || this.props.activeArticle == null || nextProps.activeArticle.key !== this.props.activeArticle.key) {
|
||||
let nextArticle = nextProps.activeArticle
|
||||
let nextModified = nextArticle != null ? _.findWhere(nextProps.modified, {key: nextArticle.key}) : null
|
||||
|
||||
let isArticleChanged = nextProps.activeArticle != null && (nextProps.activeArticle.key !== this.state.article.key)
|
||||
let isModeChanged = nextProps.status.mode !== this.props.status.mode
|
||||
let article = Object.assign({}, nextProps.activeArticle, nextModified)
|
||||
|
||||
// Reset article input
|
||||
if (isArticleChanged || (isModeChanged && nextProps.status.mode !== IDLE_MODE)) {
|
||||
Object.assign(nextState, {
|
||||
article: makeInstantArticle(nextProps.activeArticle)
|
||||
this.setState({
|
||||
article
|
||||
})
|
||||
}
|
||||
|
||||
// Clean state
|
||||
if (isModeChanged) {
|
||||
Object.assign(nextState, {
|
||||
openDeleteConfirmMenu: false,
|
||||
previewMode: false,
|
||||
isArticleEdited: false,
|
||||
isTagChanged: false,
|
||||
isTitleChanged: false,
|
||||
isContentChanged: false
|
||||
})
|
||||
}
|
||||
|
||||
this.setState(nextState)
|
||||
cacheArticle () {
|
||||
let { dispatch } = this.props
|
||||
|
||||
dispatch(cacheArticle(this.props.activeArticle.key, this.state.article))
|
||||
}
|
||||
|
||||
renderEmpty () {
|
||||
return (
|
||||
<div className='ArticleDetail empty'>
|
||||
Command(⌘) + Enter to create a new post
|
||||
<div className='ArticleDetail-empty-box'>
|
||||
<div className='ArticleDetail-empty-box-message'>Command(⌘) + N<br/>to create a new post</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -176,123 +153,46 @@ export default class ArticleDetail extends React.Component {
|
||||
|
||||
handleSaveButtonClick (e) {
|
||||
let { dispatch, folders, status } = this.props
|
||||
let article = this.state.article
|
||||
let newArticle = Object.assign({}, article)
|
||||
|
||||
let folder = _.findWhere(folders, {key: article.FolderKey})
|
||||
if (folder == null) return false
|
||||
|
||||
dispatch(unlockStatus())
|
||||
|
||||
newArticle.status = null
|
||||
newArticle.updatedAt = new Date()
|
||||
newArticle.title = newArticle.title.trim()
|
||||
if (newArticle.createdAt == null) {
|
||||
newArticle.createdAt = new Date()
|
||||
if (newArticle.title.length === 0) {
|
||||
newArticle.title = `Created at ${moment(newArticle.createdAt).format('YYYY/MM/DD HH:mm')}`
|
||||
let targetFolderKey = this.state.article.FolderKey
|
||||
dispatch(saveArticle(this.props.activeArticle.key, this.state.article), true)
|
||||
if (status.targetFolders.length > 0) {
|
||||
let targetFolder = _.findWhere(folders, {key: targetFolderKey})
|
||||
dispatch(switchFolder(targetFolder.name))
|
||||
}
|
||||
activityRecord.emit('ARTICLE_CREATE', {mode: newArticle.mode})
|
||||
} else {
|
||||
activityRecord.emit('ARTICLE_UPDATE', {mode: newArticle.mode})
|
||||
}
|
||||
|
||||
dispatch(updateArticle(newArticle))
|
||||
dispatch(switchMode(IDLE_MODE))
|
||||
// Folder filterがかかっている時に、
|
||||
// Searchを初期化し、更新先のFolder filterをかける
|
||||
// かかれていない時に
|
||||
// Searchを初期化する
|
||||
if (status.targetFolders.length > 0) dispatch(switchFolder(folder.name))
|
||||
else dispatch(clearSearch())
|
||||
dispatch(switchArticle(newArticle.key))
|
||||
}
|
||||
|
||||
handleFolderKeyChange (e) {
|
||||
let article = this.state.article
|
||||
article.FolderKey = e.target.value
|
||||
|
||||
this.setState({article: article})
|
||||
this.setState({article: article}, () => this.cacheArticle())
|
||||
}
|
||||
|
||||
handleTitleChange (e) {
|
||||
let { article } = this.state
|
||||
article.title = e.target.value
|
||||
let _isTitleChanged = article.title !== this.props.activeArticle.title
|
||||
|
||||
let { isTagChanged, isContentChanged, isArticleEdited, isModeChanged } = this.state
|
||||
let _isArticleEdited = _isTitleChanged || isTagChanged || isContentChanged || isModeChanged
|
||||
|
||||
this.setState({
|
||||
article,
|
||||
isTitleChanged: _isTitleChanged,
|
||||
isArticleEdited: _isArticleEdited
|
||||
}, () => {
|
||||
if (isArticleEdited !== _isArticleEdited) {
|
||||
let { dispatch } = this.props
|
||||
if (_isArticleEdited) {
|
||||
console.log('lockit')
|
||||
dispatch(lockStatus())
|
||||
} else {
|
||||
console.log('unlockit')
|
||||
dispatch(unlockStatus())
|
||||
}
|
||||
}
|
||||
})
|
||||
article
|
||||
}, () => this.cacheArticle())
|
||||
}
|
||||
|
||||
handleTagsChange (newTag, tags) {
|
||||
let article = this.state.article
|
||||
article.tags = tags
|
||||
|
||||
let _isTagChanged = _.difference(article.tags, this.props.activeArticle.tags).length > 0 || _.difference(this.props.activeArticle.tags, article.tags).length > 0
|
||||
|
||||
let { isTitleChanged, isContentChanged, isArticleEdited, isModeChanged } = this.state
|
||||
let _isArticleEdited = _isTagChanged || isTitleChanged || isContentChanged || isModeChanged
|
||||
|
||||
this.setState({
|
||||
article,
|
||||
isTagChanged: _isTagChanged,
|
||||
isArticleEdited: _isArticleEdited
|
||||
}, () => {
|
||||
if (isArticleEdited !== _isArticleEdited) {
|
||||
let { dispatch } = this.props
|
||||
if (_isArticleEdited) {
|
||||
console.log('lockit')
|
||||
dispatch(lockStatus())
|
||||
} else {
|
||||
console.log('unlockit')
|
||||
dispatch(unlockStatus())
|
||||
}
|
||||
}
|
||||
})
|
||||
article
|
||||
}, () => this.cacheArticle())
|
||||
}
|
||||
|
||||
handleModeChange (value) {
|
||||
let { article } = this.state
|
||||
article.mode = value
|
||||
let _isModeChanged = article.mode !== this.props.activeArticle.mode
|
||||
|
||||
let { isTagChanged, isContentChanged, isArticleEdited, isTitleChanged } = this.state
|
||||
let _isArticleEdited = _isModeChanged || isTagChanged || isContentChanged || isTitleChanged
|
||||
|
||||
this.setState({
|
||||
article,
|
||||
previewMode: false,
|
||||
isModeChanged: _isModeChanged,
|
||||
isArticleEdited: _isArticleEdited
|
||||
}, () => {
|
||||
if (isArticleEdited !== _isArticleEdited) {
|
||||
let { dispatch } = this.props
|
||||
if (_isArticleEdited) {
|
||||
console.log('lockit')
|
||||
dispatch(lockStatus())
|
||||
} else {
|
||||
console.log('unlockit')
|
||||
dispatch(unlockStatus())
|
||||
}
|
||||
}
|
||||
})
|
||||
article
|
||||
}, () => this.cacheArticle())
|
||||
}
|
||||
|
||||
handleModeSelectBlur () {
|
||||
@@ -302,34 +202,12 @@ export default class ArticleDetail extends React.Component {
|
||||
}
|
||||
|
||||
handleContentChange (e, value) {
|
||||
let { status } = this.props
|
||||
if (status.mode === IDLE_MODE) {
|
||||
return
|
||||
}
|
||||
|
||||
let { article } = this.state
|
||||
article.content = value
|
||||
let _isContentChanged = article.content !== this.props.activeArticle.content
|
||||
|
||||
let { isTagChanged, isModeChanged, isArticleEdited, isTitleChanged } = this.state
|
||||
let _isArticleEdited = _isContentChanged || isTagChanged || isModeChanged || isTitleChanged
|
||||
|
||||
this.setState({
|
||||
article,
|
||||
isContentChanged: _isContentChanged,
|
||||
isArticleEdited: _isArticleEdited
|
||||
}, () => {
|
||||
if (isArticleEdited !== _isArticleEdited) {
|
||||
let { dispatch } = this.props
|
||||
if (_isArticleEdited) {
|
||||
console.log('lockit')
|
||||
dispatch(lockStatus())
|
||||
} else {
|
||||
console.log('unlockit')
|
||||
dispatch(unlockStatus())
|
||||
}
|
||||
}
|
||||
})
|
||||
article
|
||||
}, () => this.cacheArticle())
|
||||
}
|
||||
|
||||
handleTogglePreviewButtonClick (e) {
|
||||
@@ -373,14 +251,16 @@ export default class ArticleDetail extends React.Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
let { folders, status, tags, activeArticle, user } = this.props
|
||||
|
||||
let { folders, status, tags, activeArticle, modified, user } = this.props
|
||||
if (activeArticle == null) return this.renderEmpty()
|
||||
let folderOptions = folders.map(folder => {
|
||||
return (
|
||||
<option key={folder.key} value={folder.key}>{folder.name}</option>
|
||||
)
|
||||
})
|
||||
|
||||
let isUnsaved = !!_.findWhere(modified, {key: activeArticle.key})
|
||||
|
||||
return (
|
||||
<div className='ArticleDetail'>
|
||||
<div className='ArticleDetail-info'>
|
||||
@@ -392,7 +272,13 @@ export default class ArticleDetail extends React.Component {
|
||||
>
|
||||
{folderOptions}
|
||||
</select>
|
||||
{this.state.isArticleEdited ? ' (edited)' : ''}
|
||||
<span className='ArticleDetail-info-status'
|
||||
children={
|
||||
isUnsaved
|
||||
? <span> <span className='unsaved-mark'>●</span> Unsaved</span>
|
||||
: `Created : ${moment(this.state.article.createdAt).format('YYYY/MM/DD')} Updated : ${moment(this.state.article.updatedAt).format('YYYY/MM/DD')}`
|
||||
}
|
||||
/>
|
||||
|
||||
<div className='ArticleDetail-info-control'>
|
||||
<ShareButton
|
||||
@@ -404,8 +290,8 @@ export default class ArticleDetail extends React.Component {
|
||||
<i className='fa fa-fw fa-clipboard'/><span className='tooltip'>Copy to clipboard</span>
|
||||
</button>
|
||||
|
||||
<button onClick={e => this.handleEditButtonClick(e)}>
|
||||
<i className='fa fa-fw fa-edit'/><span className='tooltip'>Save (⌘ + s)</span>
|
||||
<button onClick={e => this.handleSaveButtonClick(e)}>
|
||||
<i className='fa fa-fw fa-save'/><span className='tooltip'>Save (⌘ + s)</span>
|
||||
</button>
|
||||
<button onClick={e => this.handleDeleteButtonClick(e)}>
|
||||
<i className='fa fa-fw fa-trash'/><span className='tooltip'>Delete</span>
|
||||
@@ -426,6 +312,15 @@ export default class ArticleDetail extends React.Component {
|
||||
|
||||
<div className='ArticleDetail-panel'>
|
||||
<div className='ArticleDetail-panel-header'>
|
||||
<div className='ArticleDetail-panel-header-title'>
|
||||
<input
|
||||
onKeyDown={e => this.handleTitleKeyDown(e)}
|
||||
placeholder='(Untitled)'
|
||||
ref='title'
|
||||
value={this.state.article.title}
|
||||
onChange={e => this.handleTitleChange(e)}
|
||||
/>
|
||||
</div>
|
||||
<ModeSelect
|
||||
ref='mode'
|
||||
onChange={e => this.handleModeChange(e)}
|
||||
@@ -433,17 +328,6 @@ export default class ArticleDetail extends React.Component {
|
||||
className='ArticleDetail-panel-header-mode'
|
||||
onBlur={() => this.handleModeSelectBlur()}
|
||||
/>
|
||||
<div className='ArticleDetail-panel-header-title'>
|
||||
<input
|
||||
onKeyDown={e => this.handleTitleKeyDown(e)}
|
||||
placeholder={this.state.article.createdAt == null
|
||||
? `Created at ${moment().format('YYYY/MM/DD HH:mm')}`
|
||||
: 'Title'}
|
||||
ref='title'
|
||||
value={this.state.article.title}
|
||||
onChange={e => this.handleTitleChange(e)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{status.isTutorialOpen ? modeSelectTutorialElement : null}
|
||||
|
||||
@@ -466,6 +350,7 @@ export default class ArticleDetail extends React.Component {
|
||||
ArticleDetail.propTypes = {
|
||||
status: PropTypes.shape(),
|
||||
activeArticle: PropTypes.shape(),
|
||||
modified: PropTypes.array,
|
||||
user: PropTypes.shape(),
|
||||
folders: PropTypes.array,
|
||||
tags: PropTypes.array,
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { PropTypes } from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import ModeIcon from 'browser/components/ModeIcon'
|
||||
import moment from 'moment'
|
||||
import { switchArticle, NEW } from '../actions'
|
||||
import { switchArticle } from '../actions'
|
||||
import FolderMark from 'browser/components/FolderMark'
|
||||
import TagLink from './TagLink'
|
||||
import _ from 'lodash'
|
||||
@@ -64,40 +64,58 @@ export default class ArticleList extends React.Component {
|
||||
handleArticleClick (article) {
|
||||
let { dispatch } = this.props
|
||||
return function (e) {
|
||||
if (article.status === NEW) return null
|
||||
dispatch(switchArticle(article.key))
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
let { articles, activeArticle, folders } = this.props
|
||||
let { articles, modified, activeArticle, folders } = this.props
|
||||
|
||||
let articleElements = articles.map(article => {
|
||||
let modifiedArticle = _.findWhere(modified, {key: article.key})
|
||||
let originalArticle = article
|
||||
if (modifiedArticle) {
|
||||
article = Object.assign({}, article, modifiedArticle)
|
||||
}
|
||||
let tagElements = Array.isArray(article.tags) && article.tags.length > 0
|
||||
? article.tags.map(tag => {
|
||||
return (<TagLink key={tag} tag={tag}/>)
|
||||
})
|
||||
: (<span>Not tagged yet</span>)
|
||||
let folder = _.findWhere(folders, {key: article.FolderKey})
|
||||
let folderChanged = originalArticle.FolderKey !== article.FolderKey
|
||||
let originalFolder = folderChanged ? _.findWhere(folders, {key: originalArticle.FolderKey}) : null
|
||||
|
||||
let title = article.status !== NEW
|
||||
? article.title.trim().length === 0
|
||||
let title = article.title.trim().length === 0
|
||||
? <small>(Untitled)</small>
|
||||
: article.title
|
||||
: '(New article)'
|
||||
|
||||
return (
|
||||
<div key={'article-' + article.key}>
|
||||
<div onClick={e => this.handleArticleClick(article)(e)} className={'articleItem' + (activeArticle.key === article.key ? ' active' : '')}>
|
||||
<div className='top'>
|
||||
{folder != null
|
||||
? <span className='folderName'><FolderMark color={folder.color}/>{folder.name}</span>
|
||||
? folderChanged
|
||||
? <span className='folderName'>
|
||||
<FolderMark color={originalFolder.color}/>{originalFolder.name}
|
||||
->
|
||||
<FolderMark color={folder.color}/>{folder.name}
|
||||
</span>
|
||||
: <span className='folderName'>
|
||||
<FolderMark color={folder.color}/>{folder.name}
|
||||
</span>
|
||||
: <span><FolderMark color={-1}/>Unknown</span>
|
||||
}
|
||||
<span className='updatedAt'>{article.status != null ? article.status : moment(article.updatedAt).fromNow()}</span>
|
||||
<span className='updatedAt'
|
||||
children={
|
||||
modifiedArticle != null
|
||||
? <span><span className='unsaved-mark'>●</span> Unsaved</span>
|
||||
: moment(article.updatedAt).fromNow()
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className='middle'>
|
||||
<ModeIcon className='mode' mode={article.mode}/> <div className='title'>{title}</div>
|
||||
<ModeIcon className='mode' mode={article.mode}/> <div className='title' children={title}/>
|
||||
</div>
|
||||
<div className='bottom'>
|
||||
<div className='tags'><i className='fa fa-fw fa-tags'/>{tagElements}</div>
|
||||
@@ -119,6 +137,7 @@ export default class ArticleList extends React.Component {
|
||||
ArticleList.propTypes = {
|
||||
folders: PropTypes.array,
|
||||
articles: PropTypes.array,
|
||||
modified: PropTypes.array,
|
||||
activeArticle: PropTypes.shape(),
|
||||
dispatch: PropTypes.func
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import { findWhere } from 'lodash'
|
||||
import { setSearchFilter, switchFolder, switchMode, switchArticle, updateArticle, clearNewArticle, EDIT_MODE } from '../actions'
|
||||
import { setSearchFilter, switchFolder, saveArticle } from '../actions'
|
||||
import { openModal } from 'browser/lib/modal'
|
||||
import FolderMark from 'browser/components/FolderMark'
|
||||
import Preferences from '../modal/Preferences'
|
||||
@@ -71,20 +71,17 @@ export default class ArticleNavigator extends React.Component {
|
||||
: folders[0].key
|
||||
|
||||
let newArticle = {
|
||||
id: null,
|
||||
key: keygen(),
|
||||
title: '',
|
||||
content: '',
|
||||
mode: 'markdown',
|
||||
tags: [],
|
||||
FolderKey: FolderKey,
|
||||
status: 'NEW'
|
||||
craetedAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
}
|
||||
|
||||
dispatch(clearNewArticle())
|
||||
dispatch(updateArticle(newArticle))
|
||||
dispatch(switchArticle(newArticle.key, true))
|
||||
dispatch(switchMode(EDIT_MODE))
|
||||
dispatch(saveArticle(newArticle.key, newArticle, true))
|
||||
}
|
||||
|
||||
handleNewFolderButton (e) {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React, { PropTypes} from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { EDIT_MODE, IDLE_MODE, toggleTutorial } from './actions'
|
||||
import ArticleNavigator from './HomePage/ArticleNavigator'
|
||||
import ArticleTopBar from './HomePage/ArticleTopBar'
|
||||
import ArticleList from './HomePage/ArticleList'
|
||||
import ArticleDetail from './HomePage/ArticleDetail'
|
||||
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'
|
||||
|
||||
@@ -48,43 +48,6 @@ class HomePage extends React.Component {
|
||||
return
|
||||
}
|
||||
|
||||
switch (status.mode) {
|
||||
|
||||
case EDIT_MODE:
|
||||
if (e.keyCode === 27) {
|
||||
detail.handleCancelButtonClick()
|
||||
}
|
||||
if ((e.keyCode === 13 && (cmdOrCtrl)) || (e.keyCode === 83 && (cmdOrCtrl))) {
|
||||
detail.handleSaveButtonClick()
|
||||
}
|
||||
if (e.keyCode === 80 && cmdOrCtrl) {
|
||||
detail.handleTogglePreviewButtonClick()
|
||||
}
|
||||
if (e.keyCode === 78 && cmdOrCtrl) {
|
||||
nav.handleNewPostButtonClick()
|
||||
e.preventDefault()
|
||||
}
|
||||
break
|
||||
case IDLE_MODE:
|
||||
if (e.keyCode === 69) {
|
||||
detail.handleEditButtonClick()
|
||||
e.preventDefault()
|
||||
}
|
||||
if (e.keyCode === 68) {
|
||||
detail.handleDeleteButtonClick()
|
||||
}
|
||||
|
||||
// `detail`の`openDeleteConfirmMenu`の時。
|
||||
if (detail.state.openDeleteConfirmMenu) {
|
||||
if (e.keyCode === 27) {
|
||||
detail.handleDeleteCancelButtonClick()
|
||||
}
|
||||
if (e.keyCode === 13 && cmdOrCtrl) {
|
||||
detail.handleDeleteConfirmButtonClick()
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// `detail`の`openDeleteConfirmMenu`が`true`なら呼ばれない。
|
||||
if (e.keyCode === 27 || (e.keyCode === 70 && cmdOrCtrl)) {
|
||||
top.focusInput()
|
||||
@@ -98,15 +61,14 @@ class HomePage extends React.Component {
|
||||
list.selectNextArticle()
|
||||
}
|
||||
|
||||
if ((e.keyCode === 65 && !e.metaKey && !e.ctrlKey) || (e.keyCode === 13 && cmdOrCtrl) || (e.keyCode === 78 && cmdOrCtrl)) {
|
||||
if (e.keyCode === 78 && cmdOrCtrl) {
|
||||
nav.handleNewPostButtonClick()
|
||||
e.preventDefault()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
let { dispatch, status, user, articles, allArticles, activeArticle, folders, tags, filters } = this.props
|
||||
let { dispatch, status, user, articles, allArticles, modified, activeArticle, folders, tags, filters } = this.props
|
||||
|
||||
return (
|
||||
<div className='HomePage'>
|
||||
@@ -128,6 +90,7 @@ class HomePage extends React.Component {
|
||||
dispatch={dispatch}
|
||||
folders={folders}
|
||||
articles={articles}
|
||||
modified={modified}
|
||||
status={status}
|
||||
activeArticle={activeArticle}
|
||||
/>
|
||||
@@ -136,6 +99,7 @@ class HomePage extends React.Component {
|
||||
dispatch={dispatch}
|
||||
user={user}
|
||||
activeArticle={activeArticle}
|
||||
modified={modified}
|
||||
folders={folders}
|
||||
status={status}
|
||||
tags={tags}
|
||||
@@ -174,9 +138,12 @@ function startsWith (target, needle) {
|
||||
}
|
||||
|
||||
function remap (state) {
|
||||
let { user, folders, articles, status } = state
|
||||
let { user, folders, status } = state
|
||||
let _articles = state.articles
|
||||
|
||||
let articles = _articles != null ? _articles.data : []
|
||||
let modified = _articles != null ? _articles.modified : []
|
||||
|
||||
if (articles == null) articles = []
|
||||
articles.sort((a, b) => {
|
||||
return new Date(b.updatedAt) - new Date(a.updatedAt)
|
||||
})
|
||||
@@ -239,8 +206,9 @@ function remap (state) {
|
||||
user,
|
||||
folders,
|
||||
status,
|
||||
allArticles,
|
||||
articles,
|
||||
allArticles,
|
||||
modified,
|
||||
activeArticle,
|
||||
tags,
|
||||
filters: {
|
||||
@@ -258,6 +226,7 @@ HomePage.propTypes = {
|
||||
}),
|
||||
articles: PropTypes.array,
|
||||
allArticles: PropTypes.array,
|
||||
modified: PropTypes.array,
|
||||
activeArticle: PropTypes.shape(),
|
||||
dispatch: PropTypes.func,
|
||||
folders: PropTypes.array,
|
||||
@@ -4,24 +4,20 @@ export const USER_UPDATE = 'USER_UPDATE'
|
||||
export const CLEAR_NEW_ARTICLE = 'CLEAR_NEW_ARTICLE'
|
||||
export const ARTICLE_UPDATE = 'ARTICLE_UPDATE'
|
||||
export const ARTICLE_DESTROY = 'ARTICLE_DESTROY'
|
||||
export const ARTICLE_SAVE = 'ARTICLE_SAVE'
|
||||
export const ARTICLE_CACHE = 'ARTICLE_CACHE'
|
||||
export const FOLDER_CREATE = 'FOLDER_CREATE'
|
||||
export const FOLDER_UPDATE = 'FOLDER_UPDATE'
|
||||
export const FOLDER_DESTROY = 'FOLDER_DESTROY'
|
||||
export const FOLDER_REPLACE = 'FOLDER_REPLACE'
|
||||
|
||||
export const SWITCH_FOLDER = 'SWITCH_FOLDER'
|
||||
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'
|
||||
export const LOCK_STATUS = 'LOCK_STATUS'
|
||||
export const UNLOCK_STATUS = 'UNLOCK_STATUS'
|
||||
export const TOGGLE_TUTORIAL = 'TOGGLE_TUTORIAL'
|
||||
|
||||
// Status - mode
|
||||
export const IDLE_MODE = 'IDLE_MODE'
|
||||
export const EDIT_MODE = 'EDIT_MODE'
|
||||
export const TOGGLE_TUTORIAL = 'TOGGLE_TUTORIAL'
|
||||
|
||||
// Article status
|
||||
export const NEW = 'NEW'
|
||||
@@ -40,6 +36,20 @@ export function clearNewArticle () {
|
||||
}
|
||||
}
|
||||
|
||||
export function cacheArticle (key, article) {
|
||||
return {
|
||||
type: ARTICLE_CACHE,
|
||||
data: { key, article }
|
||||
}
|
||||
}
|
||||
|
||||
export function saveArticle (key, article, forceSwitch) {
|
||||
return {
|
||||
type: ARTICLE_SAVE,
|
||||
data: { key, article, forceSwitch }
|
||||
}
|
||||
}
|
||||
|
||||
export function updateArticle (article) {
|
||||
return {
|
||||
type: ARTICLE_UPDATE,
|
||||
@@ -92,19 +102,11 @@ export function switchFolder (folderName) {
|
||||
}
|
||||
}
|
||||
|
||||
export function switchMode (mode) {
|
||||
return {
|
||||
type: SWITCH_MODE,
|
||||
data: mode
|
||||
}
|
||||
}
|
||||
|
||||
export function switchArticle (articleKey, isNew) {
|
||||
export function switchArticle (articleKey) {
|
||||
return {
|
||||
type: SWITCH_ARTICLE,
|
||||
data: {
|
||||
key: articleKey,
|
||||
isNew: isNew
|
||||
key: articleKey
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -129,18 +131,6 @@ export function clearSearch () {
|
||||
}
|
||||
}
|
||||
|
||||
export function lockStatus () {
|
||||
return {
|
||||
type: LOCK_STATUS
|
||||
}
|
||||
}
|
||||
|
||||
export function unlockStatus () {
|
||||
return {
|
||||
type: UNLOCK_STATUS
|
||||
}
|
||||
}
|
||||
|
||||
export function toggleTutorial () {
|
||||
return {
|
||||
type: TOGGLE_TUTORIAL
|
||||
@@ -152,17 +142,16 @@ export default {
|
||||
clearNewArticle,
|
||||
updateArticle,
|
||||
destroyArticle,
|
||||
cacheArticle,
|
||||
saveArticle,
|
||||
createFolder,
|
||||
updateFolder,
|
||||
destroyFolder,
|
||||
replaceFolder,
|
||||
switchFolder,
|
||||
switchMode,
|
||||
switchArticle,
|
||||
setSearchFilter,
|
||||
setTagFilter,
|
||||
clearSearch,
|
||||
lockStatus,
|
||||
unlockStatus,
|
||||
toggleTutorial
|
||||
}
|
||||
|
||||
@@ -3,13 +3,10 @@ import _ from 'lodash'
|
||||
import {
|
||||
// Status action type
|
||||
SWITCH_FOLDER,
|
||||
SWITCH_MODE,
|
||||
SWITCH_ARTICLE,
|
||||
SET_SEARCH_FILTER,
|
||||
SET_TAG_FILTER,
|
||||
CLEAR_SEARCH,
|
||||
LOCK_STATUS,
|
||||
UNLOCK_STATUS,
|
||||
TOGGLE_TUTORIAL,
|
||||
|
||||
// user
|
||||
@@ -18,38 +15,33 @@ import {
|
||||
// Article action type
|
||||
ARTICLE_UPDATE,
|
||||
ARTICLE_DESTROY,
|
||||
CLEAR_NEW_ARTICLE,
|
||||
ARTICLE_CACHE,
|
||||
ARTICLE_SAVE,
|
||||
|
||||
// Folder action type
|
||||
FOLDER_CREATE,
|
||||
FOLDER_UPDATE,
|
||||
FOLDER_DESTROY,
|
||||
FOLDER_REPLACE,
|
||||
|
||||
// view mode
|
||||
IDLE_MODE
|
||||
FOLDER_REPLACE
|
||||
} from './actions'
|
||||
import dataStore from 'browser/lib/dataStore'
|
||||
import keygen from 'browser/lib/keygen'
|
||||
import activityRecord from 'browser/lib/activityRecord'
|
||||
import { openModal } from 'browser/lib/modal'
|
||||
import EditedAlert from './modal/EditedAlert'
|
||||
|
||||
const initialStatus = {
|
||||
mode: IDLE_MODE,
|
||||
search: '',
|
||||
isTutorialOpen: false,
|
||||
isStatusLocked: false
|
||||
}
|
||||
|
||||
let data = dataStore.getData()
|
||||
let initialArticles = data.articles
|
||||
let initialArticles = {
|
||||
data: data.articles,
|
||||
modified: []
|
||||
}
|
||||
let initialFolders = data.folders
|
||||
let initialUser = dataStore.getUser().user
|
||||
|
||||
let isStatusLocked = false
|
||||
let isCreatingNew = false
|
||||
|
||||
function user (state = initialUser, action) {
|
||||
switch (action.type) {
|
||||
case USER_UPDATE:
|
||||
@@ -140,58 +132,84 @@ function folders (state = initialFolders, action) {
|
||||
return state
|
||||
}
|
||||
}
|
||||
let isCleaned = true
|
||||
function articles (state = initialArticles, action) {
|
||||
state = state.slice()
|
||||
|
||||
if (!isCreatingNew && !isCleaned) {
|
||||
state = state.filter(article => article.status !== 'NEW')
|
||||
isCleaned = true
|
||||
function compareArticle (original, modified) {
|
||||
var keys = _.keys(_.pick(modified, ['mode', 'title', 'tags', 'content', 'FolderKey']))
|
||||
|
||||
return keys.reduce((sum, key) => {
|
||||
if (original[key] !== modified[key]) {
|
||||
if (sum == null) {
|
||||
sum = {
|
||||
key: original.key
|
||||
}
|
||||
}
|
||||
sum[key] = modified[key]
|
||||
}
|
||||
return sum
|
||||
}, null)
|
||||
}
|
||||
|
||||
function articles (state = initialArticles, action) {
|
||||
switch (action.type) {
|
||||
case SWITCH_ARTICLE:
|
||||
if (action.data.isNew) {
|
||||
isCleaned = false
|
||||
}
|
||||
if (!isStatusLocked && !action.data.isNew) {
|
||||
isCreatingNew = false
|
||||
if (!isCleaned) {
|
||||
state = state.filter(article => article.status !== 'NEW')
|
||||
isCleaned = true
|
||||
}
|
||||
}
|
||||
case ARTICLE_CACHE:
|
||||
{
|
||||
let modified = action.data.article
|
||||
let targetKey = action.data.key
|
||||
let originalIndex = _.findIndex(state.data, _article => targetKey === _article.key)
|
||||
if (originalIndex === -1) return state
|
||||
let modifiedIndex = _.findIndex(state.modified, _article => targetKey === _article.key)
|
||||
|
||||
modified = compareArticle(state.data[originalIndex], modified)
|
||||
if (modified == null) {
|
||||
if (modifiedIndex !== -1) state.modified.splice(modifiedIndex, 1)
|
||||
return state
|
||||
case SWITCH_FOLDER:
|
||||
case SET_SEARCH_FILTER:
|
||||
case SET_TAG_FILTER:
|
||||
case CLEAR_SEARCH:
|
||||
if (!isStatusLocked) {
|
||||
isCreatingNew = false
|
||||
if (!isCleaned) {
|
||||
state = state.filter(article => article.status !== 'NEW')
|
||||
isCleaned = true
|
||||
}
|
||||
}
|
||||
|
||||
if (modifiedIndex === -1) state.modified.push(modified)
|
||||
else Object.assign(state.modified[modifiedIndex], modified)
|
||||
return state
|
||||
case CLEAR_NEW_ARTICLE:
|
||||
return state.filter(article => article.status !== 'NEW')
|
||||
}
|
||||
case ARTICLE_SAVE:
|
||||
{
|
||||
let targetKey = action.data.key
|
||||
let override = action.data.article
|
||||
let modifiedIndex = _.findIndex(state.modified, _article => targetKey === _article.key)
|
||||
let modified = modifiedIndex !== -1 ? state.modified.splice(modifiedIndex, 1)[0] : null
|
||||
|
||||
let targetIndex = _.findIndex(state.data, _article => targetKey === _article.key)
|
||||
// Make a new if target article is not found.
|
||||
if (targetIndex === -1) {
|
||||
state.data.push(Object.assign({
|
||||
title: '',
|
||||
content: '',
|
||||
mode: 'markdown',
|
||||
tags: [],
|
||||
craetedAt: new Date()
|
||||
}, modified, override, {key: targetKey, updatedAt: new Date()}))
|
||||
return state
|
||||
}
|
||||
|
||||
Object.assign(state.data[targetIndex], modified, override, {key: targetKey, updatedAt: new Date()})
|
||||
|
||||
dataStore.setArticles(state.data)
|
||||
return state
|
||||
}
|
||||
case ARTICLE_UPDATE:
|
||||
{
|
||||
let article = action.data.article
|
||||
|
||||
let targetIndex = _.findIndex(state, _article => article.key === _article.key)
|
||||
if (targetIndex < 0) state.unshift(article)
|
||||
else Object.assign(state[targetIndex], article)
|
||||
let targetIndex = _.findIndex(state.data, _article => article.key === _article.key)
|
||||
if (targetIndex < 0) state.data.unshift(article)
|
||||
else Object.assign(state.data[targetIndex], article)
|
||||
|
||||
if (article.status !== 'NEW') dataStore.setArticles(state)
|
||||
else isCreatingNew = true
|
||||
dataStore.setArticles(state.data)
|
||||
return state
|
||||
}
|
||||
case ARTICLE_DESTROY:
|
||||
{
|
||||
let articleKey = action.data.key
|
||||
|
||||
let targetIndex = _.findIndex(state, _article => articleKey === _article.key)
|
||||
let targetIndex = _.findIndex(state.data, _article => articleKey === _article.key)
|
||||
if (targetIndex >= 0) state.splice(targetIndex, 1)
|
||||
|
||||
dataStore.setArticles(state)
|
||||
@@ -217,47 +235,34 @@ function status (state = initialStatus, action) {
|
||||
case TOGGLE_TUTORIAL:
|
||||
state.isTutorialOpen = !state.isTutorialOpen
|
||||
return state
|
||||
case LOCK_STATUS:
|
||||
isStatusLocked = state.isStatusLocked = true
|
||||
return state
|
||||
case UNLOCK_STATUS:
|
||||
isStatusLocked = state.isStatusLocked = false
|
||||
return state
|
||||
}
|
||||
|
||||
// if status locked, status become unmutable
|
||||
if (state.isStatusLocked) {
|
||||
openModal(EditedAlert, {action})
|
||||
return state
|
||||
}
|
||||
switch (action.type) {
|
||||
case SWITCH_FOLDER:
|
||||
state.mode = IDLE_MODE
|
||||
state.search = `//${action.data} `
|
||||
|
||||
case ARTICLE_SAVE:
|
||||
if (action.data.forceSwitch) {
|
||||
let article = action.data.article
|
||||
state.articleKey = article.key
|
||||
state.search = ''
|
||||
}
|
||||
return state
|
||||
case SWITCH_MODE:
|
||||
state.mode = action.data
|
||||
case SWITCH_FOLDER:
|
||||
state.search = `//${action.data} `
|
||||
|
||||
return state
|
||||
case SWITCH_ARTICLE:
|
||||
state.articleKey = action.data.key
|
||||
state.mode = IDLE_MODE
|
||||
|
||||
return state
|
||||
case SET_SEARCH_FILTER:
|
||||
state.search = action.data
|
||||
state.mode = IDLE_MODE
|
||||
|
||||
return state
|
||||
case SET_TAG_FILTER:
|
||||
state.search = `#${action.data}`
|
||||
state.mode = IDLE_MODE
|
||||
|
||||
return state
|
||||
case CLEAR_SEARCH:
|
||||
state.search = ''
|
||||
state.mode = IDLE_MODE
|
||||
|
||||
return state
|
||||
default:
|
||||
|
||||
@@ -15,7 +15,6 @@ infoButton()
|
||||
padding 0
|
||||
&:hover
|
||||
color inherit
|
||||
|
||||
.ArticleDetail
|
||||
absolute right bottom
|
||||
top 60px
|
||||
@@ -24,6 +23,17 @@ infoButton()
|
||||
background-color #E6E6E6
|
||||
border-top 1px solid borderColor
|
||||
border-left 1px solid borderColor
|
||||
&.empty
|
||||
.ArticleDetail-empty-box
|
||||
line-height 72px
|
||||
font-size 42px
|
||||
height 320px
|
||||
display flex
|
||||
align-items center
|
||||
.ArticleDetail-empty-box-message
|
||||
text-align center
|
||||
width 100%
|
||||
color inactiveTextColor
|
||||
*
|
||||
-webkit-user-select none
|
||||
.ArticleDetail-info
|
||||
@@ -50,6 +60,10 @@ infoButton()
|
||||
&>.tutorial
|
||||
position fixed
|
||||
z-index 35
|
||||
.ArticleDetail-info-status
|
||||
padding 0 5px
|
||||
.unsaved-mark
|
||||
color brandColor
|
||||
.ArticleDetail-info-control
|
||||
float right
|
||||
.ShareButton
|
||||
@@ -174,30 +188,26 @@ infoButton()
|
||||
z-index 30
|
||||
background-color white
|
||||
absolute top bottom
|
||||
left 10px
|
||||
right 10px
|
||||
display block
|
||||
height 33px
|
||||
margin-top 14px
|
||||
width 33px
|
||||
width 120px
|
||||
margin-right 15px
|
||||
border solid 1px transparent
|
||||
border solid 1px borderColor
|
||||
border-radius 5px
|
||||
transition width 0.15s
|
||||
&.idle
|
||||
cursor pointer
|
||||
&:hover
|
||||
background-color darken(white, 5%)
|
||||
border-color borderColor
|
||||
.ModeIcon
|
||||
float left
|
||||
width 100%
|
||||
padding 0 5px
|
||||
line-height 33px
|
||||
text-align center
|
||||
&.edit
|
||||
width 150px
|
||||
border-color iptFocusBorderColor
|
||||
input
|
||||
width 150px
|
||||
width 120px
|
||||
line-height 31px
|
||||
padding 0 10px
|
||||
border none
|
||||
@@ -206,7 +216,7 @@ infoButton()
|
||||
font-size 14px
|
||||
.ModeSelect-options
|
||||
position fixed
|
||||
width 150px
|
||||
width 120px
|
||||
z-index 10
|
||||
border 1px solid borderColor
|
||||
border-radius 5px
|
||||
@@ -228,8 +238,8 @@ infoButton()
|
||||
&:hover
|
||||
background-color darken(white, 10%)
|
||||
.ArticleDetail-panel-header-title
|
||||
absolute left top right
|
||||
left 33px
|
||||
absolute left top
|
||||
right 120px
|
||||
padding 0 15px
|
||||
background-color transparent
|
||||
input
|
||||
|
||||
@@ -33,6 +33,8 @@ articleItemColor = #777
|
||||
.updatedAt
|
||||
float right
|
||||
line-height 20px
|
||||
.unsaved-mark
|
||||
color brandColor
|
||||
.middle
|
||||
padding 3px 0 7px
|
||||
font-size 16px
|
||||
|
||||
Reference in New Issue
Block a user