Skip to content

Commit 832ca33

Browse files
committed
CRUD done
1 parent 9d2b64e commit 832ca33

8 files changed

Lines changed: 317 additions & 46 deletions

File tree

browser/main/HomePage.js

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import ArticleNavigator from './HomePage/ArticleNavigator'
66
import ArticleTopBar from './HomePage/ArticleTopBar'
77
import ArticleList from './HomePage/ArticleList'
88
import ArticleDetail from './HomePage/ArticleDetail'
9-
import { findWhere, pick } from 'lodash'
9+
import { findWhere, findIndex, pick } from 'lodash'
1010
import keygen from 'boost/keygen'
1111
import { NEW } from './actions'
1212

@@ -50,9 +50,20 @@ function remap (state) {
5050
let activeUser = findWhere(users, {id: parseInt(status.userId, 10)})
5151
if (activeUser == null) activeUser = users[0]
5252
let articles = state.articles['team-' + activeUser.id]
53-
let activeArticle = findWhere(users, {id: status.articleId})
53+
let activeArticle = findWhere(articles, {id: status.articleId})
5454
if (activeArticle == null) activeArticle = articles[0]
5555

56+
// remove Unsaved new article if user is not CREATE_MODE
57+
if (status.mode !== CREATE_MODE) {
58+
let targetIndex = findIndex(articles, article => article.status === NEW)
59+
60+
if (targetIndex >= 0) articles.splice(targetIndex, 1)
61+
}
62+
63+
// switching CREATE_MODE
64+
// restrict
65+
// 1. team have one folder at least
66+
// or Change IDLE MODE
5667
if (status.mode === CREATE_MODE && activeUser.Folders.length > 0) {
5768
var newArticle = findWhere(articles, {status: 'NEW'})
5869
if (newArticle == null) {
@@ -72,10 +83,6 @@ function remap (state) {
7283
} else if (status.mode === CREATE_MODE) {
7384
status.mode = IDLE_MODE
7485
}
75-
if (status.mode !== CREATE_MODE && activeArticle != null && activeArticle.status === NEW) {
76-
articles.splice(articles.indexOf(activeArticle), 1)
77-
activeArticle = articles[0]
78-
}
7986

8087
return {
8188
users,

browser/main/HomePage/ArticleDetail.js

Lines changed: 143 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@ import { findWhere, uniq } from 'lodash'
44
import ModeIcon from 'boost/components/ModeIcon'
55
import MarkdownPreview from 'boost/components/MarkdownPreview'
66
import CodeEditor from 'boost/components/CodeEditor'
7-
import { NEW, IDLE_MODE, CREATE_MODE, EDIT_MODE, switchMode } from '../actions'
7+
import { UNSYNCED, IDLE_MODE, CREATE_MODE, EDIT_MODE, switchMode, switchArticle, updateArticle, destroyArticle } from '../actions'
88
import aceModes from 'boost/ace-modes'
99
import Select from 'react-select'
1010
import linkState from 'boost/linkState'
11+
import api from 'boost/api'
1112

1213
var modeOptions = aceModes.map(function (mode) {
1314
return {
@@ -16,16 +17,27 @@ var modeOptions = aceModes.map(function (mode) {
1617
}
1718
})
1819

20+
function makeInstantArticle (article) {
21+
let instantArticle = Object.assign({}, article)
22+
instantArticle.Tags = instantArticle.Tags.map(tag => tag.name)
23+
return instantArticle
24+
}
25+
1926
export default class ArticleDetail extends React.Component {
2027
constructor (props) {
2128
super(props)
29+
2230
this.state = {
23-
article: Object.assign({}, props.activeArticle)
31+
article: makeInstantArticle(props.activeArticle)
2432
}
2533
}
2634

2735
componentWillReceiveProps (nextProps) {
28-
this.setState({article: nextProps.activeArticle})
36+
if (nextProps.activeArticle != null && nextProps.activeArticle.id !== this.state.article.id) {
37+
this.setState({article: makeInstantArticle(nextProps.activeArticle)}, function () {
38+
console.log('receive props')
39+
})
40+
}
2941
}
3042

3143
renderEmpty () {
@@ -36,12 +48,46 @@ export default class ArticleDetail extends React.Component {
3648
)
3749
}
3850

51+
handleEditButtonClick (e) {
52+
let { dispatch } = this.props
53+
dispatch(switchMode(EDIT_MODE))
54+
}
55+
56+
handleDeleteButtonClick (e) {
57+
this.setState({openDeleteConfirmMenu: true})
58+
}
59+
60+
handleDeleteConfirmButtonClick (e) {
61+
let { dispatch, activeUser, activeArticle } = this.props
62+
63+
api.destroyArticle(activeArticle.id)
64+
.then(res => {
65+
console.log(res.body)
66+
})
67+
.catch(err => {
68+
// connect failed need to queue data
69+
if (err.code === 'ECONNREFUSED') {
70+
return
71+
}
72+
73+
if (err.status != null) throw err
74+
else console.log(err)
75+
})
76+
77+
dispatch(destroyArticle(activeUser.id, activeArticle.id))
78+
this.setState({openDeleteConfirmMenu: false})
79+
}
80+
81+
handleDeleteCancleButtonClick (e) {
82+
this.setState({openDeleteConfirmMenu: false})
83+
}
84+
3985
renderIdle () {
40-
let { status, activeArticle, activeUser } = this.props
86+
let { activeArticle, activeUser } = this.props
4187

4288
let tags = activeArticle.Tags.length > 0 ? activeArticle.Tags.map(tag => {
4389
return (
44-
<a key={tag.id}>{tag.name}</a>
90+
<a key={tag.name}>{tag.name}</a>
4591
)
4692
}) : (
4793
<span className='noTags'>Not tagged yet</span>
@@ -50,24 +96,37 @@ export default class ArticleDetail extends React.Component {
5096
let folderName = folder != null ? folder.name : '(unknown)'
5197

5298
return (
53-
<div className='ArticleDetail show'>
54-
<div className='detailInfo'>
55-
<div className='left'>
56-
<div className='info'>
57-
<i className='fa fa-fw fa-square'/> {folderName}&nbsp;
58-
by {activeArticle.User.profileName}&nbsp;
59-
Created {moment(activeArticle.createdAt).format('YYYY/MM/DD')}&nbsp;
60-
Updated {moment(activeArticle.updatedAt).format('YYYY/MM/DD')}
99+
<div className='ArticleDetail idle'>
100+
{this.state.openDeleteConfirmMenu
101+
? (
102+
<div className='deleteConfirm'>
103+
<div className='right'>
104+
Are you sure to delete this article?
105+
<button onClick={e => this.handleDeleteConfirmButtonClick(e)} className='primary'><i className='fa fa-fw fa-check'/> Sure</button>
106+
<button onClick={e => this.handleDeleteCancleButtonClick(e)}><i className='fa fa-fw fa-times'/> Cancle</button>
107+
</div>
61108
</div>
62-
<div className='tags'><i className='fa fa-fw fa-tags'/>{tags}</div>
63-
</div>
109+
)
110+
: (
111+
<div className='detailInfo'>
112+
<div className='left'>
113+
<div className='info'>
114+
<i className='fa fa-fw fa-square'/> {folderName}&nbsp;
115+
by {activeArticle.User.profileName}&nbsp;
116+
Created {moment(activeArticle.createdAt).format('YYYY/MM/DD')}&nbsp;
117+
Updated {moment(activeArticle.updatedAt).format('YYYY/MM/DD')}
118+
</div>
119+
<div className='tags'><i className='fa fa-fw fa-tags'/>{tags}</div>
120+
</div>
121+
<div className='right'>
122+
<button onClick={e => this.handleEditButtonClick(e)}><i className='fa fa-fw fa-edit'/></button>
123+
<button onClick={e => this.handleDeleteButtonClick(e)}><i className='fa fa-fw fa-trash'/></button>
124+
<button><i className='fa fa-fw fa-share-alt'/></button>
125+
</div>
126+
</div>
127+
)
128+
}
64129

65-
<div className='right'>
66-
<button><i className='fa fa-fw fa-edit'/></button>
67-
<button><i className='fa fa-fw fa-trash'/></button>
68-
<button><i className='fa fa-fw fa-share-alt'/></button>
69-
</div>
70-
</div>
71130
<div className='detailBody'>
72131
<div className='detailPanel'>
73132
<div className='header'>
@@ -86,7 +145,67 @@ export default class ArticleDetail extends React.Component {
86145
}
87146

88147
handleSaveButtonClick (e) {
89-
console.log(this.state.article)
148+
let { activeArticle } = this.props
149+
150+
if (typeof activeArticle.id === 'string') this.saveAsNew()
151+
else this.save()
152+
}
153+
154+
saveAsNew () {
155+
let { dispatch, activeUser } = this.props
156+
let article = this.state.article
157+
let newArticle = Object.assign({}, article)
158+
article.tags = article.Tags
159+
160+
api.createArticle(article)
161+
.then(res => {
162+
console.log(res.body)
163+
})
164+
.catch(err => {
165+
// connect failed need to queue data
166+
if (err.code === 'ECONNREFUSED') {
167+
return
168+
}
169+
170+
if (err.status != null) throw err
171+
else console.log(err)
172+
})
173+
174+
newArticle.status = UNSYNCED
175+
newArticle.Tags = newArticle.Tags.map(tag => { return {name: tag} })
176+
177+
dispatch(updateArticle(activeUser.id, newArticle))
178+
dispatch(switchMode(IDLE_MODE))
179+
dispatch(switchArticle(article.id))
180+
}
181+
182+
save () {
183+
let { dispatch, activeUser } = this.props
184+
let article = this.state.article
185+
let newArticle = Object.assign({}, article)
186+
187+
article.tags = article.Tags
188+
189+
api.saveArticle(article)
190+
.then(res => {
191+
console.log(res.body)
192+
})
193+
.catch(err => {
194+
// connect failed need to queue data
195+
if (err.code === 'ECONNREFUSED') {
196+
return
197+
}
198+
199+
if (err.status != null) throw err
200+
else console.log(err)
201+
})
202+
203+
newArticle.status = UNSYNCED
204+
newArticle.Tags = newArticle.Tags.map(tag => { return {name: tag} })
205+
206+
dispatch(updateArticle(activeUser.id, newArticle))
207+
dispatch(switchMode(IDLE_MODE))
208+
dispatch(switchArticle(article.id))
90209
}
91210

92211
handleFolderIdChange (value) {
@@ -121,7 +240,7 @@ export default class ArticleDetail extends React.Component {
121240
}
122241

123242
renderEdit () {
124-
let { status, activeUser } = this.props
243+
let { activeUser } = this.props
125244

126245
let folderOptions = activeUser.Folders.map(folder => {
127246
return {
@@ -146,7 +265,7 @@ export default class ArticleDetail extends React.Component {
146265
<div className='detailPanel'>
147266
<div className='header'>
148267
<div className='title'>
149-
<input ref='title' valueLink={this.linkState('article.title')}/>
268+
<input placeholder='Title' ref='title' valueLink={this.linkState('article.title')}/>
150269
</div>
151270
<Select ref='mode' onChange={value => this.handleModeChange(value)} clearable={false} options={modeOptions}placeholder='select mode...' value={this.state.article.mode} className='mode'/>
152271
</div>

browser/main/HomePage/ArticleList.js

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,31 @@ import React, { PropTypes } from 'react'
22
import ProfileImage from 'boost/components/ProfileImage'
33
import ModeIcon from 'boost/components/ModeIcon'
44
import moment from 'moment'
5-
import { IDLE_MODE, CREATE_MODE, EDIT_MODE, NEW } from '../actions'
5+
import { IDLE_MODE, CREATE_MODE, EDIT_MODE, switchArticle, NEW } from '../actions'
66

77
export default class ArticleList extends React.Component {
8+
handleArticleClick (id) {
9+
let { dispatch } = this.props
10+
return function (e) {
11+
dispatch(switchArticle(id))
12+
}
13+
}
14+
815
render () {
916
let { status, articles, activeArticle } = this.props
1017

1118
let articlesEl = articles.map(article => {
1219
let tags = Array.isArray(article.Tags) && article.Tags.length > 0 ? article.Tags.map(tag => {
1320
return (
14-
<a key={tag.id}>#{tag.name}</a>
21+
<a key={tag.name}>{tag.name}</a>
1522
)
1623
}) : (
1724
<span>Not tagged yet</span>
1825
)
1926

2027
return (
2128
<div key={'article-' + article.id}>
22-
<div className={'articleItem' + (activeArticle.id === article.id ? ' active' : '')}>
29+
<div onClick={e => this.handleArticleClick(article.id)(e)} className={'articleItem' + (activeArticle.id === article.id ? ' active' : '')}>
2330
<div className='top'>
2431
<i className='fa fa-fw fa-square'/>
2532
by <ProfileImage className='profileImage' size='20' email={article.User.email}/> {article.User.profileName}
@@ -48,5 +55,6 @@ export default class ArticleList extends React.Component {
4855
ArticleList.propTypes = {
4956
status: PropTypes.shape(),
5057
articles: PropTypes.array,
51-
activeArticle: PropTypes.shape()
58+
activeArticle: PropTypes.shape(),
59+
dispatch: PropTypes.func
5260
}

browser/main/actions.js

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,54 @@
1+
// Action types
12
export const USER_UPDATE = 'USER_UPDATE'
3+
export const ARTICLE_REFRESH = 'ARTICLE_REFRESH'
24
export const ARTICLE_UPDATE = 'ARTICLE_UPDATE'
5+
export const ARTICLE_DESTROY = 'ARTICLE_DESTROY'
6+
37
export const SWITCH_USER = 'SWITCH_USER'
48
export const SWITCH_FOLDER = 'SWITCH_FOLDER'
59
export const SWITCH_MODE = 'SWITCH_MODE'
10+
export const SWITCH_ARTICLE = 'SWITCH_ARTICLE'
611

12+
// Status - mode
713
export const IDLE_MODE = 'IDLE_MODE'
814
export const CREATE_MODE = 'CREATE_MODE'
915
export const EDIT_MODE = 'EDIT_MODE'
1016

17+
// Article status
1118
export const NEW = 'NEW'
1219
export const SYNCING = 'SYNCING'
1320
export const UNSYNCED = 'UNSYNCED'
1421

22+
// DB
1523
export function updateUser (user) {
1624
return {
1725
type: USER_UPDATE,
1826
data: user
1927
}
2028
}
2129

22-
export function updateArticles (userId, articles) {
30+
export function refreshArticles (userId, articles) {
2331
return {
24-
type: ARTICLE_UPDATE,
32+
type: ARTICLE_REFRESH,
2533
data: {userId, articles}
2634
}
2735
}
2836

37+
export function updateArticle (userId, article) {
38+
return {
39+
type: ARTICLE_UPDATE,
40+
data: {userId, article}
41+
}
42+
}
43+
44+
export function destroyArticle (userId, articleId) {
45+
return {
46+
type: ARTICLE_DESTROY,
47+
data: { userId, articleId }
48+
}
49+
}
50+
51+
// Nav
2952
export function switchUser (userId) {
3053
return {
3154
type: SWITCH_USER,
@@ -46,3 +69,10 @@ export function switchMode (mode) {
4669
data: mode
4770
}
4871
}
72+
73+
export function switchArticle (articleId) {
74+
return {
75+
type: SWITCH_ARTICLE,
76+
data: articleId
77+
}
78+
}

0 commit comments

Comments
 (0)