From 0afddee243f9c81576de981d66e1495118fe708e Mon Sep 17 00:00:00 2001 From: Alf Eaton <eaton.alf@gmail.com> Date: Mon, 4 Sep 2017 10:35:58 +0100 Subject: [PATCH] Add initial review component --- packages/component-review/package.json | 64 ++++++++++ .../component-review/src/components/Review.js | 46 +++++++ .../src/components/Review.local.scss | 0 .../component-review/src/components/Review.md | 50 ++++++++ .../src/components/ReviewLayout.js | 31 +++++ .../src/components/ReviewLayout.local.scss | 14 +++ .../src/components/ReviewPage.js | 86 ++++++++++++++ .../component-review/src/components/index.js | 1 + packages/component-review/src/index.js | 7 ++ .../component-review/src/lib/validators.js | 34 ++++++ .../component-review/styleguide.config.js | 21 ++++ packages/component-review/webpack.config.js | 112 ++++++++++++++++++ .../xpub-collabra/app/config/journal/index.js | 1 + .../app/config/journal/recommendations.js | 14 +++ packages/xpub-collabra/app/routes.js | 17 ++- packages/xpub-collabra/config/components.json | 1 + packages/xpub-collabra/package.json | 1 + 17 files changed, 496 insertions(+), 4 deletions(-) create mode 100644 packages/component-review/package.json create mode 100644 packages/component-review/src/components/Review.js create mode 100644 packages/component-review/src/components/Review.local.scss create mode 100644 packages/component-review/src/components/Review.md create mode 100644 packages/component-review/src/components/ReviewLayout.js create mode 100644 packages/component-review/src/components/ReviewLayout.local.scss create mode 100644 packages/component-review/src/components/ReviewPage.js create mode 100644 packages/component-review/src/components/index.js create mode 100644 packages/component-review/src/index.js create mode 100644 packages/component-review/src/lib/validators.js create mode 100644 packages/component-review/styleguide.config.js create mode 100644 packages/component-review/webpack.config.js create mode 100644 packages/xpub-collabra/app/config/journal/recommendations.js diff --git a/packages/component-review/package.json b/packages/component-review/package.json new file mode 100644 index 000000000..0062460cd --- /dev/null +++ b/packages/component-review/package.json @@ -0,0 +1,64 @@ +{ + "name": "pubsweet-component-xpub-review", + "version": "0.0.2", + "main": "src", + "author": "Collaborative Knowledge Foundation", + "license": "MIT", + "files": [ + "src", + "dist" + ], + "dependencies": { + "classnames": "^2.2.5", + "lodash": "^4.17.4", + "prop-types": "^15.5.10", + "pubsweet-client": "git+https://gitlab.coko.foundation/pubsweet/pubsweet-client.git", + "pubsweet-component-wax": "^0.1.0", + "pubsweet-component-xpub-app": "^0.0.2", + "react": "^15.6.1", + "react-dom": "^15.6.1", + "react-redux": "^5.0.2", + "react-router": "^3.0.5", + "react-router-redux": "^4.0.7", + "recompose": "^0.25.0", + "redux": "^3.6.0", + "redux-form": "^7.0.3", + "striptags": "^3.1.0", + "xpub-edit": "^0.0.2", + "xpub-selectors": "^0.0.2", + "xpub-ui": "^0.0.2" + }, + "devDependencies": { + "babel-core": "^6.26.0", + "babel-loader": "^7.1.2", + "babel-preset-env": "^1.6.0", + "babel-preset-react": "^6.24.1", + "babel-preset-stage-2": "^6.24.1", + "css-loader": "^0.28.4", + "faker": "^4.1.0", + "file-loader": "^0.11.2", + "node-sass": "^4.5.3", + "react-styleguidist": "^6.0.8", + "sass-loader": "^6.0.6", + "style-loader": "^0.18.2", + "webpack": "^3.5.5", + "webpack-node-externals": "^1.6.0", + "xpub-styleguide": "^0.0.2" + }, + "peerDependencies": { + "prop-types": "^15.5.10", + "pubsweet-client": "git+https://gitlab.coko.foundation/pubsweet/pubsweet-client.git", + "react": "^15.6.1", + "react-dom": "^15.6.1", + "react-redux": "^5.0.2", + "react-router": "^3.0.5" + }, + "scripts": { + "styleguide": "styleguidist server", + "styleguide:build": "styleguidist build", + "clean": "rimraf dist", + "lint": "eslint src", + "prebuild": "npm run clean && npm run lint", + "build": "webpack --progress --profile" + } +} diff --git a/packages/component-review/src/components/Review.js b/packages/component-review/src/components/Review.js new file mode 100644 index 000000000..39d86e47b --- /dev/null +++ b/packages/component-review/src/components/Review.js @@ -0,0 +1,46 @@ +import React from 'react' +import { Button } from 'xpub-ui' +import { NoteEditor } from 'xpub-edit' +import { Field } from 'redux-form' +import { required } from '../lib/validators' +import { RadioGroup, ValidatedField } from 'xpub-ui' +import classes from './Review.local.scss' + +const Review = ({ journal, review, valid, pristine, submitting, handleSubmit, uploadFile }) => ( + <form onSubmit={handleSubmit}> + <div className={classes.section}> + <Field + name="note" + validate={[required]} + component={props => + <ValidatedField {...props.meta}> + <NoteEditor + placeholder="Enter your review…" + title="Review" + {...props.input}/> + </ValidatedField> + }/> + </div> + + <div className={classes.section}> + <Field + name="recommendation" + validate={[required]} + component={props => + <ValidatedField {...props.meta}> + <RadioGroup + inline + options={journal.recommendations} + {...props.input}/> + </ValidatedField> + }/> + </div> + + <div> + {/*<Button type="button" onClick={handleSave}>Save</Button>*/} + <Button type="submit" primary>Submit</Button> + </div> + </form> +) + +export default Review diff --git a/packages/component-review/src/components/Review.local.scss b/packages/component-review/src/components/Review.local.scss new file mode 100644 index 000000000..e69de29bb diff --git a/packages/component-review/src/components/Review.md b/packages/component-review/src/components/Review.md new file mode 100644 index 000000000..fcbda70b9 --- /dev/null +++ b/packages/component-review/src/components/Review.md @@ -0,0 +1,50 @@ +A form for entering a review of a version of a project. + +```js +const { reduxForm } = require('redux-form'); + +const project = { + id: faker.random.uuid(), +}; + +const version = { + id: faker.random.uuid(), + metadata: { + keywords: ['foo', 'bar'] + } +}; + +const review = { + id: faker.random.uuid(), + note: '<p>This is a review</p>', + recommendation: 'accept' +}; + +const journal = { + recommendations: [ + { + value: 'accept', + label: 'Accept', + }, + { + value: 'revise', + label: 'Revise', + }, + { + value: 'reject', + label: 'Reject', + } + ] +} + +const ReviewForm = reduxForm({ + form: 'review', + onSubmit: values => console.log(values), + onChange: values => console.log(values) +})(Review); + +<ReviewForm + version={version} + journal={journal} + initialValues={review}/> +``` diff --git a/packages/component-review/src/components/ReviewLayout.js b/packages/component-review/src/components/ReviewLayout.js new file mode 100644 index 000000000..01217b103 --- /dev/null +++ b/packages/component-review/src/components/ReviewLayout.js @@ -0,0 +1,31 @@ +import React from 'react' +// import classnames from 'classnames' +// import SimpleEditor from 'pubsweet-component-wax/src/SimpleEditor' +import classes from './ReviewLayout.local.scss' +import Review from './Review' + +const ReviewLayout = ({ journal, project, version, review, valid, pristine, submitting, handleSubmit, uploadFile }) => ( + <div className={classes.root}> + <div className={classes.column}> + {/*<SimpleEditor + book={project} + fragment={version} + />*/} + </div> + + <div className={classes.column}> + <table className={classes.metadata}> + <tbody> + <tr> + <th>Keywords</th> + <td>{version.metadata.keywords.join(',')}</td> + </tr> + </tbody> + </table> + + <Review valid={valid} handleSubmit={handleSubmit} uploadFile={uploadFile}/> + </div> + </div> +) + +export default ReviewLayout diff --git a/packages/component-review/src/components/ReviewLayout.local.scss b/packages/component-review/src/components/ReviewLayout.local.scss new file mode 100644 index 000000000..2c67a3dff --- /dev/null +++ b/packages/component-review/src/components/ReviewLayout.local.scss @@ -0,0 +1,14 @@ +.root { + display: flex; + position: absolute; + top: 50px; + left: 0; + right: 0; + bottom: 0; + overflow: hidden; +} + +.column { + height: 100%; + overflow-y: auto; +} diff --git a/packages/component-review/src/components/ReviewPage.js b/packages/component-review/src/components/ReviewPage.js new file mode 100644 index 000000000..45b7fee17 --- /dev/null +++ b/packages/component-review/src/components/ReviewPage.js @@ -0,0 +1,86 @@ +/* global CONFIG */ + +import { debounce } from 'lodash' +import { compose, withProps } from 'recompose' +import { connect } from 'react-redux' +import { push } from 'react-router-redux' +import { reduxForm, SubmissionError } from 'redux-form' +import actions from 'pubsweet-client/src/actions' +import token from 'pubsweet-client/src/helpers/token' +import { withJournal, ConnectPage } from 'pubsweet-component-xpub-app/src/components' +import { selectCollection, selectFragment } from 'xpub-selectors' +import Review from './Review' + +const onSubmit = (values, dispatch, props) => { + console.log('submit', values) + + return dispatch(actions.updateFragment(props.project, { + id: props.review.id, + submitted: true, // TODO: current date? + ...values + })).then(() => { + // TODO: show "thanks for your review" message + dispatch(push(`/`)) + }).catch(error => { + if (error.validationErrors) { + throw new SubmissionError() + } + }) +} + +const onChange = (values, dispatch, props) => { + console.log('change', values) + + return dispatch(actions.updateFragment(props.project, { + id: props.review.id, + // submitted: false, + ...values + })) + + // TODO: display a notification when saving/saving completes/saving fails +} + +const uploadFile = file => dispatch => { + // TODO: import the endpoint URL from a client module + const API_ENDPOINT = CONFIG['pubsweet-server'].API_ENDPOINT + + const data = new FormData() + data.append('file', file) + + const request = new XMLHttpRequest() + request.open('POST', API_ENDPOINT + '/upload') + request.setRequestHeader('Authorization', 'Bearer ' + token()) + request.setRequestHeader('Accept', 'text/plain') // the response is a URL + request.send(data) + + return request +} + +export default compose( + ConnectPage(params => [ + actions.getCollection({ id: params.project }), + actions.getFragment({ id: params.project }, { id: params.version }), + actions.getFragment({ id: params.project }, { id: params.review }), + ]), + withJournal, + connect( + (state, ownProps) => ({ + project: selectCollection(state, ownProps.params.project), + version: selectFragment(state, ownProps.params.version), + review: selectFragment(state, ownProps.params.review) + }), + { + uploadFile + } + ), + withProps(({ review }) => { + return { + initialValues: review + } + }), + reduxForm({ + form: 'review', + onSubmit, + onChange: debounce(onChange, 1000) + }) +)(ReviewLayout) diff --git a/packages/component-review/src/components/index.js b/packages/component-review/src/components/index.js new file mode 100644 index 000000000..c79137444 --- /dev/null +++ b/packages/component-review/src/components/index.js @@ -0,0 +1 @@ +export { default } from './ReviewPage' diff --git a/packages/component-review/src/index.js b/packages/component-review/src/index.js new file mode 100644 index 000000000..0225ff41f --- /dev/null +++ b/packages/component-review/src/index.js @@ -0,0 +1,7 @@ +module.exports = { + frontend: { + components: [ + () => require('./components') + ] + } +} diff --git a/packages/component-review/src/lib/validators.js b/packages/component-review/src/lib/validators.js new file mode 100644 index 000000000..444c5a6b3 --- /dev/null +++ b/packages/component-review/src/lib/validators.js @@ -0,0 +1,34 @@ +import striptags from 'striptags' + +export const required = value => { + return value ? undefined : 'Required' +} + +export const minChars = min => { + const message = `Enter at least ${min} characters` + + return value => { + const text = striptags(value) + + if (!text || text.length < min) { + return message + } + + return undefined + } +} + +export const maxChars = max => { + const message = `Enter no more than ${max} characters` + + return value => { + const text = striptags(value) + + if (!text || text.length > max) { + return message + } + + return undefined + } +} + diff --git a/packages/component-review/styleguide.config.js b/packages/component-review/styleguide.config.js new file mode 100644 index 000000000..1b16d0007 --- /dev/null +++ b/packages/component-review/styleguide.config.js @@ -0,0 +1,21 @@ +module.exports = { + title: 'xpub review style guide', + styleguideComponents: { + StyleGuideRenderer: require.resolve('xpub-styleguide/src/components/StyleGuideRenderer'), + Wrapper: require.resolve('xpub-styleguide/src/components/Wrapper') + }, + context: { + faker: 'faker' + }, + skipComponentsWithoutExample: true, + webpackConfig: require('./webpack.config.js'), + serverPort: 6065, + theme: { + fontFamily: { + base: '"Fira Sans", sans-serif' + }, + color: { + link: 'cornflowerblue' + } + } +} diff --git a/packages/component-review/webpack.config.js b/packages/component-review/webpack.config.js new file mode 100644 index 000000000..cc2081f4b --- /dev/null +++ b/packages/component-review/webpack.config.js @@ -0,0 +1,112 @@ +process.env.BABEL_ENV = 'development' +process.env.NODE_ENV = 'development' + +const path = require('path') +const nodeExternals = require('webpack-node-externals') + +const include = [ + path.join(__dirname, 'src'), + /xpub-[^/]+\/src/, +] + +module.exports = { + entry: './src/index.js', + output: { + filename: 'index.js', + path: path.join(__dirname, 'dist'), + }, + devtool: 'cheap-module-source-map', + externals: [nodeExternals({ + whitelist: [/\.(?!js$).{1,5}$/i] + })], + resolve: { + symlinks: false + }, + module: { + rules: [ + { + oneOf: [ + // ES6 modules + { + test: /\.js$/, + include, + loader: 'babel-loader', + options: { + presets: [ + ['env', { modules: false }], + 'react', + 'stage-2' + ], + cacheDirectory: true, + }, + }, + + // CSS modules + { + test: /\.local\.css$/, + include, + use: [ + 'style-loader', + { + loader: 'css-loader', + options: { + modules: true, + } + } + ], + }, + + // SCSS modules + { + test: /\.local\.scss$/, + include, + use: [ + 'style-loader', + { + loader: 'css-loader', + options: { + modules: true, + importLoaders: 1 + } + }, + 'sass-loader' + ], + }, + + // global CSS + { + test: /\.css$/, + use: [ + 'style-loader', + 'css-loader' + ], + }, + + // global SCSS + { + test: /\.scss$/, + use: [ + 'style-loader', + { + loader: 'css-loader', + options: { + importLoaders: 1 + } + }, + 'sass-loader' + ], + }, + + // Files + { + exclude: [/\.js$/, /\.html$/, /\.json$/], + loader: 'file-loader', + options: { + name: 'static/media/[name].[hash:8].[ext]', + } + } + ] + } + ] + } +} diff --git a/packages/xpub-collabra/app/config/journal/index.js b/packages/xpub-collabra/app/config/journal/index.js index 1806bf3ca..2f85193a2 100644 --- a/packages/xpub-collabra/app/config/journal/index.js +++ b/packages/xpub-collabra/app/config/journal/index.js @@ -1,6 +1,7 @@ export { default as metadata } from './metadata' export { default as declarations } from './declarations' export { default as decisions } from './decisions' +export { default as recommendations } from './recommendations' export { default as sections } from './sections' export { default as articleSections } from './article-sections' export { default as articleTypes } from './article-types' diff --git a/packages/xpub-collabra/app/config/journal/recommendations.js b/packages/xpub-collabra/app/config/journal/recommendations.js new file mode 100644 index 000000000..d297f484d --- /dev/null +++ b/packages/xpub-collabra/app/config/journal/recommendations.js @@ -0,0 +1,14 @@ +export default [ + { + value: 'accept', + label: 'Accept', + }, + { + value: 'revise', + label: 'Revise', + }, + { + value: 'reject', + label: 'Reject', + } +] diff --git a/packages/xpub-collabra/app/routes.js b/packages/xpub-collabra/app/routes.js index 81a149c4e..a3501e383 100644 --- a/packages/xpub-collabra/app/routes.js +++ b/packages/xpub-collabra/app/routes.js @@ -3,10 +3,18 @@ import { Redirect, Route } from 'react-router' import loadable from 'loadable-components' import { App } from 'pubsweet-component-xpub-app/src/components' import { AuthenticatedPage, SignupPage, LoginPage, LogoutPage } from 'pubsweet-component-xpub-authentication/src/components' -import DashboardPage from 'pubsweet-component-xpub-dashboard/src/components' -import SubmitPage from 'pubsweet-component-xpub-submit/src/components' -// import ManuscriptPage from 'pubsweet-component-xpub-manuscript/src/components' -const ManuscriptPage = loadable(() => import('pubsweet-component-xpub-manuscript/src/components')) + +const DashboardPage = loadable(() => + import('pubsweet-component-xpub-dashboard/src/components')) + +const SubmitPage = loadable(() => + import('pubsweet-component-xpub-submit/src/components')) + +const ManuscriptPage = loadable(() => + import('pubsweet-component-xpub-manuscript/src/components')) + +const ReviewPage = loadable(() => + import('pubsweet-component-xpub-review/src/components')) export default ( <Route> @@ -17,6 +25,7 @@ export default ( <Route path="dashboard" component={DashboardPage}/> <Route path="projects/:project/version/:version/submit" component={SubmitPage}/> <Route path="projects/:project/version/:version/manuscript" component={ManuscriptPage}/> + <Route path="projects/:project/version/:version/review/:review" component={ReviewPage}/> </Route> <Route path="signup" component={SignupPage}/> diff --git a/packages/xpub-collabra/config/components.json b/packages/xpub-collabra/config/components.json index 1171bd253..fe6af5e3c 100644 --- a/packages/xpub-collabra/config/components.json +++ b/packages/xpub-collabra/config/components.json @@ -3,6 +3,7 @@ "pubsweet-component-xpub-authentication", "pubsweet-component-xpub-dashboard", "pubsweet-component-xpub-manuscript", + "pubsweet-component-xpub-review", "pubsweet-component-xpub-submit", "pubsweet-component-ink-frontend", "pubsweet-component-ink-backend" diff --git a/packages/xpub-collabra/package.json b/packages/xpub-collabra/package.json index acae52041..fc9a8da7b 100644 --- a/packages/xpub-collabra/package.json +++ b/packages/xpub-collabra/package.json @@ -21,6 +21,7 @@ "pubsweet-component-xpub-authentication": "^0.0.2", "pubsweet-component-xpub-dashboard": "^0.0.2", "pubsweet-component-xpub-manuscript": "^0.0.2", + "pubsweet-component-xpub-review": "^0.0.2", "pubsweet-component-xpub-submit": "^0.0.2", "pubsweet-server": "^1.0.0-alpha.2", "pubsweet-theme-plugin": "^0.0.1", -- GitLab