diff --git a/backend/api/v1/contributors.js b/backend/api/v1/contributors.js index 325a64f6..7f30ac20 100644 --- a/backend/api/v1/contributors.js +++ b/backend/api/v1/contributors.js @@ -1,17 +1,63 @@ const express = require('express') +import { orderBy } from 'lodash' -const ContributorsService = require('../../modules/contributors/services/ContributorsService') +const axios = require('axios') +const c = require('../../../config/config.json') + +const env = process.env.NODE_ENV === 'dev' ? 'dev' : 'prod' +const base_api = c[env]['base_api'] + env + + +const filterItems = (items, query) => { + if (!query) { + return items + } + + return items.filter(item => { + return ( + item.prettyname.toLowerCase().indexOf(query) > -1 || + item.bio.toLowerCase().indexOf(query) > -1 + ) + }) +} + +const addContributor = (data ) => { + return axios.post(base_api + '/blog/contributors/add', data).then(res => res.data) +} module.exports = cache => { const router = express.Router() - router.get('/list', function(req, res) { - ContributorsService.getAllContributors() - .then(contributors => { - res.send(contributors) - }) - .catch(err => { - res.send(err) - }) + + router.get('/', (req, res) => { + const { q } = req.query + let contributors = cache().contributors + + contributors = filterItems( + orderBy( + Object.keys(contributors).map(id => ({ + ...contributors[id], + id + })), + 'sort_rank' + ), + q + ) + + return res.send(contributors) + }) + + router.get('/list', (req, res) => { + const contributors = cache().contributors + res.send(contributors) + }) + + router.post('/', (req, res) => { + const contributor = req.body + + addContributor(contributor) + .then(data => res.send(data)) + .catch(error => res.send({ success: false, error: error.message })) }) + return router } diff --git a/package.json b/package.json index a9203d4c..d4481154 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "cookie-session": "^2.0.0-beta.3", "css-loader": "^0.26.2", "draft-js": "^0.10.5", + "draftjs-md-converter": "^0.1.7", "draftjs-to-html": "^0.8.3", "e164": "0.0.5", "echarts": "^4.0.4", @@ -67,6 +68,7 @@ "letsencrypt-express": "^2.0.7", "lodash": "^4.17.4", "lodash.isarray": "^4.0.0", + "markdown-draft-js": "^0.6.2", "marked": "^0.3.19", "mathjax": "^2.7.0", "mime": "^2.2.2", diff --git a/shared/Blog/Components/RelatedContent.js b/shared/Blog/Components/RelatedContent.js index 1dde43b1..5c29afd1 100644 --- a/shared/Blog/Components/RelatedContent.js +++ b/shared/Blog/Components/RelatedContent.js @@ -1,13 +1,7 @@ -import React from 'react' -import ReactDOM from 'react-dom' -import { connect } from 'react-redux' +import React, { Component } from 'react' import { Link } from 'react-router' -class RelatedContent extends React.Component { - constructor(props) { - super(props) - } - +class RelatedContent extends Component { render() { var { items } = this.props if (!items) { @@ -19,14 +13,17 @@ class RelatedContent extends React.Component { } return (
- {items.map(function(item) { + {items.map((item, index) => { var dest = item.dest var title = item.title var body = item.body var type = item.type if (type == 'person') { return ( -
+
@@ -45,7 +42,7 @@ class RelatedContent extends React.Component { var publish_date = item.publish_date var guid = item.guid return ( -
+

{title}

@@ -63,7 +60,7 @@ class RelatedContent extends React.Component { var publish_date = item.publish_date var guid = item.guid return ( -
+

{title}

@@ -76,8 +73,7 @@ class RelatedContent extends React.Component {
) } else { - console.log('UNSUPPORTED TYPE: ' + type) - return
+ return
} })}
diff --git a/shared/Contributors/Forms/AddContributor.js b/shared/Contributors/Forms/AddContributor.js new file mode 100644 index 00000000..922b51d5 --- /dev/null +++ b/shared/Contributors/Forms/AddContributor.js @@ -0,0 +1,148 @@ +import React from 'react' +import moment from 'moment' +import { isEmpty } from 'lodash' +import { Field, reduxForm } from 'redux-form' +import FormController from '../../Forms/Components/FormController/FormController' +import { + renderField, + renderSelect, + renderZip +} from '../../Forms/Components/Field' +import RichTextarea from '../../Forms/Components/RichTextarea' +import ImageUploadField from '../../Forms/Components/ImageUploadField' + +const env = process.env.NODE_ENV === 'dev' ? 'dev' : 'prod' +const config = require('../../../config/config.json') +const c = config[env] + +export const FORM_KEY = 'addContributor' + +const validate = values => { + let errors = {} + + if (isEmpty(values.prettyname)) { + errors.prettyname = 'Pretty Name field is required' + } + + if (isEmpty(values.img)) { + errors.img = 'Image field is required' + } + + if (isEmpty(values.author)) { + errors.author = 'Author field is required' + } + + if (isEmpty(values.twitter)) { + errors.twitter = 'Author field is required' + } + + if (isEmpty(values.linkedin)) { + errors.linkedin = 'Linkedin field is required' + } + + if (isEmpty(values.bio)) { + errors.bio = 'Bio field is required' + } + + if (isEmpty(values.sort_rank)) { + errors.sort_rank = 'Sort rank field is required' + } else if (values.sort_rank < 0) { + errors.sort_rank = 'Number should be positive' + } + + return errors +} + +const AddContributorForm = ({ + children, + handleSubmit, + pristine, + reset, + submitting, + allowSubmit, + activeStep, + errorMessage, + ready, + recording, + stop, + review, + submit, + complete, + submittedUrl, + error, + showSubmit, + customError, + customSuccess +}) => ( + + + + + + + + + + + + + + + +) + +export default reduxForm({ + form: FORM_KEY, + validate +})(AddContributorForm) diff --git a/shared/Forms/Components/Field/Select.js b/shared/Forms/Components/Field/Select.js index a32be327..1316e4a6 100644 --- a/shared/Forms/Components/Field/Select.js +++ b/shared/Forms/Components/Field/Select.js @@ -11,7 +11,8 @@ export const renderSelect = ({ fieldWrapperClasses = '', labelWrapperClasses = '', inputWrapperStyles = '', - options = [] + options = [], + blankOption = false }) => { return (
@@ -25,7 +26,7 @@ export const renderSelect = ({ touched && invalid ? 'has-danger' : '' }`} > -
diff --git a/shared/components/admin/AdminOld.js b/shared/components/admin/AdminOld.js index b643f52e..a71aba3c 100644 --- a/shared/components/admin/AdminOld.js +++ b/shared/components/admin/AdminOld.js @@ -76,10 +76,7 @@ class Admin extends Component {
- - -
- +
diff --git a/shared/components/admin/BlogList.js b/shared/components/admin/BlogList.js new file mode 100644 index 00000000..0dd2c20c --- /dev/null +++ b/shared/components/admin/BlogList.js @@ -0,0 +1,142 @@ +import React, { Component } from 'react' +import { connect } from 'react-redux' +import styled from 'styled-components' +import moment from 'moment' + +import BlogListItem from '../../Blog/Components/BlogListItem' +import RelatedContent from '../../Blog/Components/RelatedContent' +import { Link } from 'react-router' + +const fakeClick = e => { + e.preventDefault() + e.stopPropagation() +} + +const normalizeDate = d => moment(d).format('YYYY-MM-DD') + +class BlogList extends Component { + getAuthor = (author = '') => this.props.contributors.get(author.toLowerCase().trim()).toJS() + + renderPreview() { + const { blog } = this.props + const contributor = this.getAuthor(blog.author) || {} + + return ( + + + + + ) + } + + render() { + const { odd, blog } = this.props + const { blog_id, prettyname } = blog + + blog.publish_date = normalizeDate(blog.publish_date) + + return ( + + + + + + Edit + + + + + + + + + + {this.renderPreview()} + + + ) + } +} + +export default connect(state => ({ + contributors: state.site.get('contributors') +}))(BlogList) + +const Wrapper = styled.div` + background: #f9faf9; + margin-bottom: 12px; + border: 1px solid #e1e3e2; + display: flex; + flex-direction: column; + + ${props => + props.open && + ` + background: rgba(240, 217, 67, 0.1); + `}; +` + +const Inner = styled.div` + padding: 20px 30px; +` + +const Row = styled.div` + display: flex; + align-items: center; +` + +const Label = styled.div` + display: flex; + align-items: center; + margin-right: 10px; +` + +const Value = styled.div` + flex: 1; + + > input { + width: 100%; + } +` + +const Preview = styled.div`` + +const Heading = Row.extend` + font-size: 24px; + color: #2d1454; + + height: 60px; +` + +const ID = styled.span` + color: #000; +` + +const EditButton = styled(Link)` + width: 160px; + font-size: 16px; + border: none; + border-radius: 5px; + margin-left: 10px; + background: #f0d943; + display: inline-block; + border: none; + line-height: 27px; + text-align: center; + color: #fff !important; + + &:hover { + color: #fff; + border: none; + } +` diff --git a/shared/components/admin/BlogUpdater.js b/shared/components/admin/BlogUpdater.js deleted file mode 100644 index 0c2d26dc..00000000 --- a/shared/components/admin/BlogUpdater.js +++ /dev/null @@ -1,133 +0,0 @@ -import React from 'react' -import ReactDOM from 'react-dom' -import querystring from 'querystring' -import { connect } from 'react-redux' - -class BlogUpdater extends React.Component { - constructor(props) { - super(props) - this.state = props.blog - } - - update(me, title, event) { - console.log(event) - console.log(event.target) - console.log(event.target.value) - var s = me.state - s[title] = event.target.value - me.setState(s) - } - - save(blog_id, title, abstract, author, publish_date, dispatch) { - console.log('save') - dispatch({ - type: 'CMS_UPDATE_BLOG', - payload: { - blog_id, - title, - abstract, - author, - publish_date, - dispatch - } - }) - } - - delete(blog_id, dispatch) { - console.log('delete') - dispatch({ type: 'CMS_DELETE_BLOG', payload: { blog_id, dispatch } }) - } - - componentDidMount() {} - - render() { - var dispatch = this.props.dispatch - var blog = this.state - var blog_id = blog['blog_id'] - var title = blog['title'] - var abstract = blog['abstract'] - var author = blog['author'] - var publish_date = blog['publish_date'] - var prettyname = blog['prettyname'] - var me = this - return ( -
-
-
blog_id:
-
{blog_id}
-
-
-
Prettyname:
-
- https://dataskeptic.com/blog{prettyname} -
-
-
-
Title:
-
- -
-
-
-
Abstract:
-
-