1
0
mirror of https://github.com/BoostIo/Boostnote synced 2025-12-16 19:21:52 +00:00

blueprint articles are available in planet list

This commit is contained in:
Rokt33r
2015-07-18 16:52:17 +09:00
parent b30511eb51
commit 4fee2586e4
7 changed files with 244 additions and 50 deletions

View File

@@ -5,6 +5,7 @@ var Catalyst = require('../Mixins/Catalyst')
var Markdown = require('../Mixins/Markdown') var Markdown = require('../Mixins/Markdown')
var Select = require('react-select') var Select = require('react-select')
var request = require('superagent') var request = require('superagent')
var PlanetActions = require('../Actions/PlanetActions')
var getOptions = function (input, callback) { var getOptions = function (input, callback) {
request request
@@ -30,13 +31,14 @@ var getOptions = function (input, callback) {
var BlueprintForm = React.createClass({ var BlueprintForm = React.createClass({
mixins: [Catalyst.LinkedStateMixin, ReactRouter.State, Markdown], mixins: [Catalyst.LinkedStateMixin, ReactRouter.State, Markdown],
propTypes: {
close: React.PropTypes.func,
blueprint: React.PropTypes.object
},
statics: { statics: {
EDIT_MODE: 0, EDIT_MODE: 0,
PREVIEW_MODE: 1 PREVIEW_MODE: 1
}, },
propTypes: {
close: React.PropTypes.func
},
getInitialState: function () { getInitialState: function () {
return { return {
blueprint: { blueprint: {
@@ -65,6 +67,22 @@ var BlueprintForm = React.createClass({
}, },
submit: function () { submit: function () {
console.log(this.state.blueprint) console.log(this.state.blueprint)
var blueprint = Object.assign({}, this.state.blueprint)
blueprint.Tags = blueprint.Tags.map(function (tag) {
return tag.value
})
if (this.props.blueprint == null) {
var params = this.getParams()
var userName = params.userName
var planetName = params.planetName
PlanetActions.createBlueprint(userName + '/' + planetName, blueprint)
} else {
var blueprintId = blueprint.id
delete blueprint.id
PlanetActions.updateBlueprint(blueprintId, blueprint)
}
}, },
render: function () { render: function () {
var content = this.state.mode === BlueprintForm.EDIT_MODE ? ( var content = this.state.mode === BlueprintForm.EDIT_MODE ? (

View File

@@ -28,6 +28,9 @@ var LaunchModal = React.createClass({
case 'snippetCreated': case 'snippetCreated':
this.props.close() this.props.close()
break break
case 'blueprintCreated':
this.props.close()
break
} }
}, },
stopPropagation: function (e) { stopPropagation: function (e) {

View File

@@ -13,6 +13,8 @@ var PlanetStore = require('../Stores/PlanetStore')
var PlanetActions = require('../Actions/PlanetActions') var PlanetActions = require('../Actions/PlanetActions')
var Markdown = require('../Mixins/Markdown')
var PlanetHeader = React.createClass({ var PlanetHeader = React.createClass({
propTypes: { propTypes: {
currentPlanet: React.PropTypes.object, currentPlanet: React.PropTypes.object,
@@ -111,16 +113,17 @@ var PlanetNavigator = React.createClass({
}) })
var PlanetArticleList = React.createClass({ var PlanetArticleList = React.createClass({
mixins: [ReactRouter.Navigation, ReactRouter.State, ForceUpdate(60000)], mixins: [ReactRouter.Navigation, ReactRouter.State, ForceUpdate(60000), Markdown],
propTypes: { propTypes: {
planet: React.PropTypes.shape({ planet: React.PropTypes.shape({
Snippets: React.PropTypes.array, Snippets: React.PropTypes.array,
Blueprints: React.PropTypes.array Blueprints: React.PropTypes.array,
Articles: React.PropTypes.array
}) })
}, },
render: function () { render: function () {
var articles = this.props.planet.Snippets.map(function (snippet) { var articles = this.props.planet.Articles.map(function (article) {
var tags = snippet.Tags.length > 0 ? snippet.Tags.map(function (tag) { var tags = article.Tags.length > 0 ? article.Tags.map(function (tag) {
return ( return (
<a key={tag.id} href>#{tag.name}</a> <a key={tag.id} href>#{tag.name}</a>
) )
@@ -128,30 +131,57 @@ var PlanetArticleList = React.createClass({
<a className='noTag'>Not tagged yet</a> <a className='noTag'>Not tagged yet</a>
) )
var params = this.getParams() var params = this.getParams()
var isActive = article.type === 'snippet' ? this.isActive('snippets') && parseInt(params.localId, 10) === article.localId : this.isActive('blueprints') && parseInt(params.localId, 10) === article.localId
var isActive = parseInt(params.localId, 10) === snippet.localId var handleClick
var handleClick = function () { if (article.type === 'snippet') {
handleClick = function () {
this.transitionTo('snippets', { this.transitionTo('snippets', {
userName: params.userName, userName: params.userName,
planetName: params.planetName, planetName: params.planetName,
localId: snippet.localId localId: article.localId
}) })
}.bind(this) }.bind(this)
return ( return (
<li onClick={handleClick} key={snippet.id}> <li onClick={handleClick} key={'snippet-' + article.id}>
<div className={isActive ? 'snippetItem active' : 'snippetItem'}> <div className={'articleItem snippetItem' + (isActive ? ' active' : '')}>
<div className='itemHeader'> <div className='itemHeader'>
<div className='callSign'><i className='fa fa-code'></i> {snippet.callSign}</div> <div className='callSign'><i className='fa fa-code'></i> {article.callSign}</div>
<div className='updatedAt'>{moment(snippet.updatedAt).fromNow()}</div> <div className='updatedAt'>{moment(article.updatedAt).fromNow()}</div>
</div> </div>
<div className='description'>{snippet.description.length > 50 ? snippet.description.substring(0, 50) + ' …' : snippet.description}</div> <div className='description'>{article.description.length > 50 ? article.description.substring(0, 50) + ' …' : article.description}</div>
<div className='tags'><i className='fa fa-tags'/>{tags}</div> <div className='tags'><i className='fa fa-tags'/>{tags}</div>
</div> </div>
<div className='divider'></div> <div className='divider'></div>
</li> </li>
) )
}
handleClick = function () {
this.transitionTo('blueprints', {
userName: params.userName,
planetName: params.planetName,
localId: article.localId
})
}.bind(this)
return (
<li onClick={handleClick} key={'blueprint-' + article.id}>
<div className={'articleItem blueprintItem' + (isActive ? ' active' : '')}>
<div className='itemHeader'>
<div className='callSign'><i className='fa fa-file-text-o'></i> {article.title}</div>
<div className='updatedAt'>{moment(article.updatedAt).fromNow()}</div>
</div>
<div className='content'>{this.markdown(article.content.substring(0, 150)).replace(/(<([^>]+)>)/ig, '').substring(0, 75)}</div>
<div className='tags'><i className='fa fa-tags'/>{tags}</div>
</div>
<div className='divider'></div>
</li>
)
}.bind(this)) }.bind(this))
return ( return (
@@ -165,9 +195,9 @@ var PlanetArticleList = React.createClass({
}) })
var PlanetArticleDetail = React.createClass({ var PlanetArticleDetail = React.createClass({
mixins: [ForceUpdate(60000)], mixins: [ForceUpdate(60000), Markdown],
propTypes: { propTypes: {
snippet: React.PropTypes.object article: React.PropTypes.object
}, },
getInitialState: function () { getInitialState: function () {
return { return {
@@ -193,45 +223,70 @@ var PlanetArticleDetail = React.createClass({
this.setState({isDeleteModalOpen: false}) this.setState({isDeleteModalOpen: false})
}, },
render: function () { render: function () {
var snippet = this.props.snippet var article = this.props.article
var tags = snippet.Tags.length > 0 ? snippet.Tags.map(function (tag) { var tags = article.Tags.length > 0 ? article.Tags.map(function (tag) {
return ( return (
<a key={tag.id} href>#{tag.name}</a> <a key={tag.id} href>#{tag.name}</a>
) )
}) : ( }) : (
<a className='noTag'>Not tagged yet</a> <a className='noTag'>Not tagged yet</a>
) )
if (article.type === 'snippet') {
return ( return (
<div className='PlanetArticleDetail'> <div className='PlanetArticleDetail snippetDetail'>
<div className='viewer-header'> <div className='viewer-header'>
<i className='fa fa-code'></i> {snippet.callSign} <small className='updatedAt'>{moment(snippet.updatedAt).fromNow()}</small> <i className='fa fa-code'></i> {article.callSign} <small className='updatedAt'>{moment(article.updatedAt).fromNow()}</small>
<span className='control-group'> <span className='control-group'>
<button onClick={this.openEditModal} className='btn-default btn-square btn-sm'><i className='fa fa-edit fa-fw'></i></button> <button onClick={this.openEditModal} className='btn-default btn-square btn-sm'><i className='fa fa-edit fa-fw'></i></button>
<button onClick={this.openDeleteModal} className='btn-default btn-square btn-sm'><i className='fa fa-trash fa-fw'></i></button> <button onClick={this.openDeleteModal} className='btn-default btn-square btn-sm'><i className='fa fa-trash fa-fw'></i></button>
</span> </span>
<ModalBase isOpen={this.state.isEditModalOpen} close={this.closeEditModal}> <ModalBase isOpen={this.state.isEditModalOpen} close={this.closeEditModal}>
<SnippetEditModal snippet={snippet} submit={this.submitEditModal} close={this.closeEditModal}/> <SnippetEditModal snippet={article} submit={this.submitEditModal} close={this.closeEditModal}/>
</ModalBase> </ModalBase>
<ModalBase isOpen={this.state.isDeleteModalOpen} close={this.closeDeleteModal}> <ModalBase isOpen={this.state.isDeleteModalOpen} close={this.closeDeleteModal}>
<SnippetDeleteModal snippet={snippet} submit={this.submitDeleteModal} close={this.closeDeleteModal}/> <SnippetDeleteModal snippet={article} submit={this.submitDeleteModal} close={this.closeDeleteModal}/>
</ModalBase> </ModalBase>
</div> </div>
<div className='viewer-body'> <div className='viewer-body'>
<div className='viewer-detail'> <div className='viewer-detail'>
<div className='description'>{snippet.description}</div> <div className='description'>{article.description}</div>
<div className='tags'><i className='fa fa-tags'/>{tags}</div> <div className='tags'><i className='fa fa-tags'/>{tags}</div>
</div> </div>
<div className='content'> <div className='content'>
<CodeViewer code={snippet.content} mode={snippet.mode}/> <CodeViewer code={article.content} mode={article.mode}/>
</div> </div>
</div> </div>
</div> </div>
) )
} }
return (
<div className='PlanetArticleDetail blueprintDetail'>
<div className='viewer-header'>
<i className='fa fa-file-text-o'></i> {article.title} <small className='updatedAt'>{moment(article.updatedAt).fromNow()}</small>
<span className='control-group'>
<button onClick={this.openEditModal} className='btn-default btn-square btn-sm'><i className='fa fa-edit fa-fw'></i></button>
<button onClick={this.openDeleteModal} className='btn-default btn-square btn-sm'><i className='fa fa-trash fa-fw'></i></button>
</span>
<ModalBase isOpen={this.state.isEditModalOpen} close={this.closeEditModal}>
<SnippetEditModal snippet={article} submit={this.submitEditModal} close={this.closeEditModal}/>
</ModalBase>
<ModalBase isOpen={this.state.isDeleteModalOpen} close={this.closeDeleteModal}>
<SnippetDeleteModal snippet={article} submit={this.submitDeleteModal} close={this.closeDeleteModal}/>
</ModalBase>
</div>
<div className='viewer-body'>
<div className='tags'><i className='fa fa-tags'/>{tags}</div>
<div className='content' dangerouslySetInnerHTML={{__html: ' ' + this.markdown(article.content)}}></div>
</div>
</div>
)
}
}) })
module.exports = React.createClass({ module.exports = React.createClass({
@@ -261,11 +316,20 @@ module.exports = React.createClass({
case 'planetFetched': case 'planetFetched':
var planet = res.data var planet = res.data
this.setState({currentPlanet: planet}, function () { this.setState({currentPlanet: planet}, function () {
if (planet.Snippets.length > 0) { if (planet.Articles.length > 0) {
if (this.isActive('snippets')) {
this.transitionTo('snippets', { this.transitionTo('snippets', {
userName: this.props.params.userName, userName: this.props.params.userName,
planetName: this.props.params.planetName, planetName: this.props.params.planetName,
localId: this.props.params.localId == null ? planet.Snippets[0].localId : this.props.params.localId}) localId: this.props.params.localId == null ? planet.Articles[0].localId : this.props.params.localId
})
} else if (this.isActive('blueprints')) {
this.transitionTo('blueprints', {
userName: this.props.params.userName,
planetName: this.props.params.planetName,
localId: this.props.params.localId == null ? planet.Articles[0].localId : this.props.params.localId
})
}
} }
}) })
break break
@@ -326,14 +390,26 @@ module.exports = React.createClass({
if (user == null) return (<div/>) if (user == null) return (<div/>)
if (this.state.currentPlanet == null) return (<div/>) if (this.state.currentPlanet == null) return (<div/>)
var content = (<div>No selected</div>) var content = (<div>Nothing selected</div>)
if (this.isActive('snippets')) { if (this.isActive('snippets')) {
var localId = parseInt(this.props.params.localId, 10) var localId = parseInt(this.props.params.localId, 10)
this.state.currentPlanet.Snippets.some(function (_snippet) { this.state.currentPlanet.Articles.some(function (article) {
if (localId === _snippet.localId) { if (article.type === 'snippet' && localId === article.localId) {
content = ( content = (
<PlanetArticleDetail snippet={_snippet}/> <PlanetArticleDetail article={article}/>
)
return true
}
return false
})
} else if (this.isActive('blueprints')) {
var localId = parseInt(this.props.params.localId, 10)
this.state.currentPlanet.Articles.some(function (article) {
if (article.type === 'blueprint' && localId === article.localId) {
content = (
<PlanetArticleDetail article={article}/>
) )
return true return true
} }

View File

@@ -1,6 +1,7 @@
var markdownit = require('markdown-it') var markdownit = require('markdown-it')
var md = markdownit({ var md = markdownit({
typographer: true typographer: true,
linkify: true
}) })
var Markdown = { var Markdown = {

View File

@@ -4,16 +4,21 @@ var request = require('superagent')
var PlanetActions = require('../Actions/PlanetActions') var PlanetActions = require('../Actions/PlanetActions')
var apiUrl = 'http://localhost:8000/'
var PlanetStore = Reflux.createStore({ var PlanetStore = Reflux.createStore({
init: function () { init: function () {
this.listenTo(PlanetActions.fetchPlanet, this.fetchPlanet) this.listenTo(PlanetActions.fetchPlanet, this.fetchPlanet)
this.listenTo(PlanetActions.createSnippet, this.createSnippet) this.listenTo(PlanetActions.createSnippet, this.createSnippet)
this.listenTo(PlanetActions.updateSnippet, this.updateSnippet) this.listenTo(PlanetActions.updateSnippet, this.updateSnippet)
this.listenTo(PlanetActions.deleteSnippet, this.deleteSnippet) this.listenTo(PlanetActions.deleteSnippet, this.deleteSnippet)
this.listenTo(PlanetActions.createBlueprint, this.createBlueprint)
this.listenTo(PlanetActions.updateBlueprint, this.updateBlueprint)
this.listenTo(PlanetActions.deleteBlueprint, this.deleteBlueprint)
}, },
fetchPlanet: function (planetName) { fetchPlanet: function (planetName) {
request request
.get('http://localhost:8000/' + planetName) .get(apiUrl + planetName)
.send() .send()
.end(function (err, res) { .end(function (err, res) {
if (err) { if (err) {
@@ -23,7 +28,17 @@ var PlanetStore = Reflux.createStore({
} }
var planet = res.body var planet = res.body
planet.Snippets = planet.Snippets.sort(function (a, b) { planet.Snippets = planet.Snippets.map(function (snippet) {
snippet.type = 'snippet'
return snippet
})
planet.Blueprints = planet.Blueprints.map(function (blueprint) {
blueprint.type = 'blueprint'
return blueprint
})
planet.Articles = planet.Snippets.concat(planet.Blueprints).sort(function (a, b) {
a = new Date(a.updatedAt) a = new Date(a.updatedAt)
b = new Date(b.updatedAt) b = new Date(b.updatedAt)
return a < b ? 1 : a > b ? -1 : 0 return a < b ? 1 : a > b ? -1 : 0
@@ -38,13 +53,12 @@ var PlanetStore = Reflux.createStore({
createSnippet: function (planetName, input) { createSnippet: function (planetName, input) {
input.description = input.description.substring(0, 255) input.description = input.description.substring(0, 255)
request request
.post('http://localhost:8000/' + planetName + '/snippets') .post(apiUrl + planetName + '/snippets')
.set({ .set({
Authorization: 'Bearer ' + localStorage.getItem('token') Authorization: 'Bearer ' + localStorage.getItem('token')
}) })
.send(input) .send(input)
.end(function (req, res) { .end(function (req, res) {
console.log('snippet created', res.body)
this.trigger({ this.trigger({
status: 'snippetCreated', status: 'snippetCreated',
data: res.body data: res.body
@@ -54,7 +68,7 @@ var PlanetStore = Reflux.createStore({
updateSnippet: function (id, input) { updateSnippet: function (id, input) {
input.description = input.description.substring(0, 255) input.description = input.description.substring(0, 255)
request request
.put('http://localhost:8000/snippets/id/' + id) .put(apiUrl + 'snippets/id/' + id)
.set({ .set({
Authorization: 'Bearer ' + localStorage.getItem('token') Authorization: 'Bearer ' + localStorage.getItem('token')
}) })
@@ -75,7 +89,7 @@ var PlanetStore = Reflux.createStore({
}, },
deleteSnippet: function (id) { deleteSnippet: function (id) {
request request
.del('http://localhost:8000/snippets/id/' + id) .del(apiUrl + 'snippets/id/' + id)
.set({ .set({
Authorization: 'Bearer ' + localStorage.getItem('token') Authorization: 'Bearer ' + localStorage.getItem('token')
}) })
@@ -93,6 +107,64 @@ var PlanetStore = Reflux.createStore({
data: snippet data: snippet
}) })
}.bind(this)) }.bind(this))
},
createBlueprint: function (planetName, input) {
input.title = input.title.substring(0, 255)
request
.post(apiUrl + planetName + '/blueprints')
.set({
Authorization: 'Bearer ' + localStorage.getItem('token')
})
.send(input)
.end(function (req, res) {
this.trigger({
status: 'blueprintCreated',
data: res.body
})
}.bind(this))
},
updateBlueprint: function (id, input) {
input.description = input.description.substring(0, 255)
request
.put(apiUrl + 'blueprints/id/' + id)
.set({
Authorization: 'Bearer ' + localStorage.getItem('token')
})
.send(input)
.end(function (err, res) {
if (err) {
console.error(err)
this.trigger(null)
return
}
var blueprint = res.body
this.trigger({
status: 'blueprintUpdated',
data: blueprint
})
}.bind(this))
},
deleteBlueprint: function (id) {
request
.del(apiUrl + 'blueprints/id/' + id)
.set({
Authorization: 'Bearer ' + localStorage.getItem('token')
})
.send()
.end(function (err, res) {
if (err) {
console.error(err)
this.trigger(null)
return
}
var blueprint = res.body
this.trigger({
status: 'blueprintDeleted',
data: blueprint
})
}.bind(this))
} }
}) })

View File

@@ -133,7 +133,7 @@
absolute top bottom left right absolute top bottom left right
overflow-y auto overflow-y auto
li li
.snippetItem .articleItem
user-select none user-select none
border solid 2px transparent border solid 2px transparent
padding 10px padding 10px
@@ -142,7 +142,7 @@
.itemHeader .itemHeader
clearfix() clearfix()
margin-bottom 5px margin-bottom 5px
.callSign .callSign, .title
float left float left
font-weight 600 font-weight 600
font-size 1.1em font-size 1.1em
@@ -151,7 +151,7 @@
line-height 16px line-height 16px
color lighten(textColor, 25%) color lighten(textColor, 25%)
font-size 0.8em font-size 0.8em
.description .description, .content
margin 10px 0 15px margin 10px 0 15px
&:hover, &.hover &:hover, &.hover
background-color hoverBackgroundColor background-color hoverBackgroundColor
@@ -183,6 +183,7 @@
absolute bottom right absolute bottom right
left 1px left 1px
top 44px top 44px
&.snippetDetail>.viewer-body
.viewer-detail .viewer-detail
border-bottom solid 1px borderColor border-bottom solid 1px borderColor
height 150px height 150px
@@ -202,3 +203,20 @@
absolute left right absolute left right
top 155px top 155px
bottom 5px bottom 5px
&.blueprintDetail>.viewer-body
.tags
absolute top
left 15px
right 15px
height 24px
line-height 24px
.content
absolute left right bottom
top 30px
box-sizing border-box
padding 5px
border-top solid 1px borderColor
padding 10px
overflow-x hidden
overflow-y auto
marked()

View File

@@ -1,5 +1,9 @@
marked() marked()
line-height 1.2em line-height 1.2em
hr
border-top none
border-bottom solid 1px borderColor
margin 15px 0
h1 h1
font-size 2em font-size 2em
margin 0.67em auto margin 0.67em auto
@@ -64,6 +68,8 @@ marked()
border none border none
border-radius 0 border-radius 0
table table
width 100%
margin 15px 0
thead thead
tr tr
background-color tableHeadBgColor background-color tableHeadBgColor