diff --git a/browser/main/Components/BlueprintForm.jsx b/browser/main/Components/BlueprintForm.jsx index 85307a8e..854754a8 100644 --- a/browser/main/Components/BlueprintForm.jsx +++ b/browser/main/Components/BlueprintForm.jsx @@ -5,6 +5,7 @@ var Catalyst = require('../Mixins/Catalyst') var Markdown = require('../Mixins/Markdown') var Select = require('react-select') var request = require('superagent') +var PlanetActions = require('../Actions/PlanetActions') var getOptions = function (input, callback) { request @@ -30,13 +31,14 @@ var getOptions = function (input, callback) { var BlueprintForm = React.createClass({ mixins: [Catalyst.LinkedStateMixin, ReactRouter.State, Markdown], + propTypes: { + close: React.PropTypes.func, + blueprint: React.PropTypes.object + }, statics: { EDIT_MODE: 0, PREVIEW_MODE: 1 }, - propTypes: { - close: React.PropTypes.func - }, getInitialState: function () { return { blueprint: { @@ -65,6 +67,22 @@ var BlueprintForm = React.createClass({ }, submit: function () { 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 () { var content = this.state.mode === BlueprintForm.EDIT_MODE ? ( diff --git a/browser/main/Components/LaunchModal.jsx b/browser/main/Components/LaunchModal.jsx index 152963dc..53c2fd85 100644 --- a/browser/main/Components/LaunchModal.jsx +++ b/browser/main/Components/LaunchModal.jsx @@ -28,6 +28,9 @@ var LaunchModal = React.createClass({ case 'snippetCreated': this.props.close() break + case 'blueprintCreated': + this.props.close() + break } }, stopPropagation: function (e) { diff --git a/browser/main/Containers/PlanetContainer.jsx b/browser/main/Containers/PlanetContainer.jsx index 0763ef8c..fa1eaa7e 100644 --- a/browser/main/Containers/PlanetContainer.jsx +++ b/browser/main/Containers/PlanetContainer.jsx @@ -13,6 +13,8 @@ var PlanetStore = require('../Stores/PlanetStore') var PlanetActions = require('../Actions/PlanetActions') +var Markdown = require('../Mixins/Markdown') + var PlanetHeader = React.createClass({ propTypes: { currentPlanet: React.PropTypes.object, @@ -111,16 +113,17 @@ var PlanetNavigator = React.createClass({ }) var PlanetArticleList = React.createClass({ - mixins: [ReactRouter.Navigation, ReactRouter.State, ForceUpdate(60000)], + mixins: [ReactRouter.Navigation, ReactRouter.State, ForceUpdate(60000), Markdown], propTypes: { planet: React.PropTypes.shape({ Snippets: React.PropTypes.array, - Blueprints: React.PropTypes.array + Blueprints: React.PropTypes.array, + Articles: React.PropTypes.array }) }, render: function () { - var articles = this.props.planet.Snippets.map(function (snippet) { - var tags = snippet.Tags.length > 0 ? snippet.Tags.map(function (tag) { + var articles = this.props.planet.Articles.map(function (article) { + var tags = article.Tags.length > 0 ? article.Tags.map(function (tag) { return ( #{tag.name} ) @@ -128,30 +131,57 @@ var PlanetArticleList = React.createClass({ Not tagged yet ) 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 () { - this.transitionTo('snippets', { + if (article.type === 'snippet') { + + handleClick = function () { + this.transitionTo('snippets', { + userName: params.userName, + planetName: params.planetName, + localId: article.localId + }) + }.bind(this) + + return ( +
  • +
    +
    +
    {article.callSign}
    +
    {moment(article.updatedAt).fromNow()}
    +
    +
    {article.description.length > 50 ? article.description.substring(0, 50) + ' …' : article.description}
    +
    {tags}
    +
    +
    +
  • + ) + } + + handleClick = function () { + this.transitionTo('blueprints', { userName: params.userName, planetName: params.planetName, - localId: snippet.localId + localId: article.localId }) }.bind(this) return ( -
  • -
    +
  • +
    -
    {snippet.callSign}
    -
    {moment(snippet.updatedAt).fromNow()}
    +
    {article.title}
    +
    {moment(article.updatedAt).fromNow()}
    -
    {snippet.description.length > 50 ? snippet.description.substring(0, 50) + ' …' : snippet.description}
    +
    {this.markdown(article.content.substring(0, 150)).replace(/(<([^>]+)>)/ig, '').substring(0, 75)}
    {tags}
  • ) + }.bind(this)) return ( @@ -165,9 +195,9 @@ var PlanetArticleList = React.createClass({ }) var PlanetArticleDetail = React.createClass({ - mixins: [ForceUpdate(60000)], + mixins: [ForceUpdate(60000), Markdown], propTypes: { - snippet: React.PropTypes.object + article: React.PropTypes.object }, getInitialState: function () { return { @@ -193,44 +223,69 @@ var PlanetArticleDetail = React.createClass({ this.setState({isDeleteModalOpen: false}) }, 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 ( #{tag.name} ) }) : ( Not tagged yet ) + if (article.type === 'snippet') { + return ( +
    +
    + {article.callSign} {moment(article.updatedAt).fromNow()} + + + + + + + + + + + +
    +
    +
    +
    {article.description}
    +
    {tags}
    +
    +
    + +
    +
    +
    + ) + } return ( -
    +
    - {snippet.callSign} {moment(snippet.updatedAt).fromNow()} + {article.title} {moment(article.updatedAt).fromNow()} - + - +
    -
    -
    {snippet.description}
    -
    {tags}
    -
    -
    - -
    +
    {tags}
    +
    ) + } }) @@ -261,11 +316,20 @@ module.exports = React.createClass({ case 'planetFetched': var planet = res.data this.setState({currentPlanet: planet}, function () { - if (planet.Snippets.length > 0) { - this.transitionTo('snippets', { - userName: this.props.params.userName, - planetName: this.props.params.planetName, - localId: this.props.params.localId == null ? planet.Snippets[0].localId : this.props.params.localId}) + if (planet.Articles.length > 0) { + if (this.isActive('snippets')) { + this.transitionTo('snippets', { + userName: this.props.params.userName, + planetName: this.props.params.planetName, + 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 @@ -326,14 +390,26 @@ module.exports = React.createClass({ if (user == null) return (
    ) if (this.state.currentPlanet == null) return (
    ) - var content = (
    No selected
    ) + var content = (
    Nothing selected
    ) if (this.isActive('snippets')) { var localId = parseInt(this.props.params.localId, 10) - this.state.currentPlanet.Snippets.some(function (_snippet) { - if (localId === _snippet.localId) { + this.state.currentPlanet.Articles.some(function (article) { + if (article.type === 'snippet' && localId === article.localId) { content = ( - + + ) + 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 = ( + ) return true } diff --git a/browser/main/Mixins/Markdown.js b/browser/main/Mixins/Markdown.js index f2cccf91..6d26080e 100644 --- a/browser/main/Mixins/Markdown.js +++ b/browser/main/Mixins/Markdown.js @@ -1,6 +1,7 @@ var markdownit = require('markdown-it') var md = markdownit({ - typographer: true + typographer: true, + linkify: true }) var Markdown = { diff --git a/browser/main/Stores/PlanetStore.js b/browser/main/Stores/PlanetStore.js index 121ea87e..a33b52e2 100644 --- a/browser/main/Stores/PlanetStore.js +++ b/browser/main/Stores/PlanetStore.js @@ -4,16 +4,21 @@ var request = require('superagent') var PlanetActions = require('../Actions/PlanetActions') +var apiUrl = 'http://localhost:8000/' + var PlanetStore = Reflux.createStore({ init: function () { this.listenTo(PlanetActions.fetchPlanet, this.fetchPlanet) this.listenTo(PlanetActions.createSnippet, this.createSnippet) this.listenTo(PlanetActions.updateSnippet, this.updateSnippet) 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) { request - .get('http://localhost:8000/' + planetName) + .get(apiUrl + planetName) .send() .end(function (err, res) { if (err) { @@ -23,7 +28,17 @@ var PlanetStore = Reflux.createStore({ } 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) b = new Date(b.updatedAt) return a < b ? 1 : a > b ? -1 : 0 @@ -38,13 +53,12 @@ var PlanetStore = Reflux.createStore({ createSnippet: function (planetName, input) { input.description = input.description.substring(0, 255) request - .post('http://localhost:8000/' + planetName + '/snippets') + .post(apiUrl + planetName + '/snippets') .set({ Authorization: 'Bearer ' + localStorage.getItem('token') }) .send(input) .end(function (req, res) { - console.log('snippet created', res.body) this.trigger({ status: 'snippetCreated', data: res.body @@ -54,7 +68,7 @@ var PlanetStore = Reflux.createStore({ updateSnippet: function (id, input) { input.description = input.description.substring(0, 255) request - .put('http://localhost:8000/snippets/id/' + id) + .put(apiUrl + 'snippets/id/' + id) .set({ Authorization: 'Bearer ' + localStorage.getItem('token') }) @@ -75,7 +89,7 @@ var PlanetStore = Reflux.createStore({ }, deleteSnippet: function (id) { request - .del('http://localhost:8000/snippets/id/' + id) + .del(apiUrl + 'snippets/id/' + id) .set({ Authorization: 'Bearer ' + localStorage.getItem('token') }) @@ -93,6 +107,64 @@ var PlanetStore = Reflux.createStore({ data: snippet }) }.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)) } }) diff --git a/browser/styles/main/containers/PlanetContainer.styl b/browser/styles/main/containers/PlanetContainer.styl index 46bb3727..bbe0bb68 100644 --- a/browser/styles/main/containers/PlanetContainer.styl +++ b/browser/styles/main/containers/PlanetContainer.styl @@ -133,7 +133,7 @@ absolute top bottom left right overflow-y auto li - .snippetItem + .articleItem user-select none border solid 2px transparent padding 10px @@ -142,7 +142,7 @@ .itemHeader clearfix() margin-bottom 5px - .callSign + .callSign, .title float left font-weight 600 font-size 1.1em @@ -151,7 +151,7 @@ line-height 16px color lighten(textColor, 25%) font-size 0.8em - .description + .description, .content margin 10px 0 15px &:hover, &.hover background-color hoverBackgroundColor @@ -183,6 +183,7 @@ absolute bottom right left 1px top 44px + &.snippetDetail>.viewer-body .viewer-detail border-bottom solid 1px borderColor height 150px @@ -202,3 +203,20 @@ absolute left right top 155px 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() diff --git a/browser/styles/mixins/marked.styl b/browser/styles/mixins/marked.styl index 87ddccb3..f4ae7dc3 100644 --- a/browser/styles/mixins/marked.styl +++ b/browser/styles/mixins/marked.styl @@ -1,5 +1,9 @@ marked() line-height 1.2em + hr + border-top none + border-bottom solid 1px borderColor + margin 15px 0 h1 font-size 2em margin 0.67em auto @@ -64,6 +68,8 @@ marked() border none border-radius 0 table + width 100% + margin 15px 0 thead tr background-color tableHeadBgColor