1
0
mirror of https://github.com/BoostIo/Boostnote synced 2025-12-13 17:56:25 +00:00

No edit mode

This commit is contained in:
Rokt33r
2015-12-28 00:59:36 +09:00
parent 013a1b4f51
commit 2537b6ba09
9 changed files with 240 additions and 364 deletions

View File

@@ -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>
)
}

View File

@@ -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,

View File

@@ -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
}

View File

@@ -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) {

View File

@@ -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,

View File

@@ -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
}

View File

@@ -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:

View File

@@ -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

View File

@@ -33,6 +33,8 @@ articleItemColor = #777
.updatedAt
float right
line-height 20px
.unsaved-mark
color brandColor
.middle
padding 3px 0 7px
font-size 16px