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