Commit fd68cb74 authored by Aanand Prasad's avatar Aanand Prasad

Merge branch 'master' into styled-components

parents 19e8f333 a0188136
This diff is collapsed.
{
"name": "pubsweet-component-xpub-find-reviewers",
"version": "0.0.2",
"main": "src",
"author": "Collaborative Knowledge Foundation",
"license": "MIT",
"files": [
"src",
"dist"
],
"dependencies": {
"react": "^15.6.1",
"react-dom": "^15.6.1",
"react-redux": "^5.0.2",
"react-router-dom": "^4.2.2",
"recompose": "^0.26.0",
"redux": "^3.6.0"
},
"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": "^1.1.5",
"node-sass": "^4.5.3",
"react-styleguidist": "^6.0.8",
"sass-loader": "^6.0.6",
"style-loader": "^0.19.0",
"webpack": "^3.8.1",
"webpack-node-externals": "^1.6.0",
"xpub-styleguide": "^0.0.2"
},
"peerDependencies": {
"react": "^15.6.1",
"react-dom": "^15.6.1",
"react-redux": "^5.0.2",
"react-router-dom": "^4.2.2",
"redux": "^3.6.0"
},
"scripts": {
"styleguide": "styleguidist server",
"styleguide:build": "styleguidist build",
"clean": "rimraf dist",
"prebuild": "npm run clean && npm run lint",
"build": "webpack --progress --profile"
}
}
import React from 'react'
import classnames from 'classnames'
import { Icon, PlainButton } from '@pubsweet/ui'
import Paper from './Paper'
import classes from './Author.local.scss'
import ProjectLink from './ProjectLink'
const Author = ({ author, toggleAuthor, project, version }) => (
<div className={classes.root}>
{author.hidden ? (
<h3 className={classes.title}>
<PlainButton
className={classes.toggle}
onClick={toggleAuthor('hidden')}
>
Show
</PlainButton>
</h3>
) : (
<h3 className={classes.title}>
<ProjectLink
id={author.id}
page="find-reviewers/author"
project={project}
target="_blank"
version={version}
>
{author.name}
</ProjectLink>
{author.flagged && (
<span className={classes.marker} title="Potential problems!">
<Icon color="orange">alert_circle</Icon>
</span>
)}
{author.boardMember && (
<span className={classes.marker} title="Editorial board member">
<Icon color="green">zap</Icon>
</span>
)}
{!author.assigned && (
<PlainButton
className={classnames(classes.toggle, classes.showOnHover)}
onClick={toggleAuthor('hidden')}
>
Hide
</PlainButton>
)}
<PlainButton
className={classnames(classes.toggle, classes.showOnHover, {
[classes.assigned]: !!author.assigned,
})}
disabled={author.flagged}
onClick={toggleAuthor('assigned')}
>
{author.assigned ? 'Assigned' : 'Assign'}
</PlainButton>
<PlainButton
className={classnames(classes.toggle, classes.showOnHover, {
[classes.expanded]: !!author.expanded,
})}
onClick={toggleAuthor('expanded')}
>
{author.expanded ? 'Collapse' : 'Expand'}
</PlainButton>
</h3>
)}
{author.expanded &&
!author.hidden && (
<div className={classes.papers}>
{author.papers.map(paper => (
<Paper
id={paper.id}
key={paper.id}
paper={paper}
project={project}
version={version}
/>
))}
</div>
)}
</div>
)
export default Author
.papers {
margin: 10px;
}
.title {
display: flex;
}
.marker {
height: 1em;
margin-left: 1ch;
}
.toggle {
color: #777;
font-size: 10px;
margin-left: 2ch;
}
.root:not(:hover) .showOnHover:not(.assigned) {
display: none;
}
.assigned {
color: blue;
}
.expanded {
color: red;
}
A suggested author, with a list of authored papers.
```js
const author = {
id: faker.random.uuid(),
name: faker.name.findName(),
papers: [
{
id: faker.random.uuid(),
title: faker.lorem.sentence(20, 5),
scorePercent: faker.random.number({ min: 1, max: 100 })
},
{
id: faker.random.uuid(),
title: faker.lorem.sentence(20, 5),
scorePercent: faker.random.number({ min: 1, max: 100 })
},
{
id: faker.random.uuid(),
title: faker.lorem.sentence(20, 5),
scorePercent: faker.random.number({ min: 1, max: 100 })
}
]
};
<Author author={author}/>
```
import React from 'react'
import { Button } from '@pubsweet/ui'
import classes from './AuthorLayout.local.scss'
import Paper from './Paper'
import { publicationYears } from '../lib/author'
const AuthorLayout = ({ author, assignReviewer, project, version }) => (
<div className={classes.root}>
<h1 className={classes.title}>
{author && (
<a href={author.url} target="_blank">
{author.name}
</a>
)}
</h1>
<div className={classes.meta}>
<div className={classes.email}>{author.email}</div>
<div className={classes.affiliation}>{author.affiliation}</div>
<div className={classes.history}>
{publicationYears(author.papers).map(item => (
<span
className={classes.year}
key={item.year}
style={{ fontSize: item.size }}
>
{item.year}
</span>
))}
</div>
<div>
<Button onClick={assignReviewer}>Assign</Button>
</div>
</div>
<div className={classes.row}>
<div className={classes.column}>
{!!author.papers.length && (
<div>
<h2 className={classes.heading}>Papers</h2>
{author.papers.map(paper => (
<Paper
id={paper.paperId}
key={paper.paperId}
paper={paper}
project={project}
version={version}
/>
))}
</div>
)}
</div>
<div className={classes.column} />
</div>
</div>
)
export default AuthorLayout
.root {
padding: 0 10px;
}
.root a {
color: inherit;
font-style: normal;
}
.title {
font-family: var(--font-author);
}
.title a {
color: inherit;
font-style: normal;
}
.heading {
font-family: var(--font-author);
}
.root a:hover {
color: var(--color-primary);
}
.root a:visited {
color: inherit;
}
.history {
margin: 10px 0;
}
.history .year {
margin: 0 3px;
}
import React from 'react'
import faker from 'faker'
import { compose } from 'recompose'
import { connect } from 'react-redux'
import { actions } from 'pubsweet-client'
import { ConnectPage } from 'xpub-connect'
import { selectCollection, selectFragment } from 'xpub-selectors'
import AuthorLayout from './AuthorLayout'
import { scholar } from '../lib/scholar'
// import { newestFirst } from '../lib/sort'
class AuthorPage extends React.Component {
state = {
author: null,
}
componentDidMount() {
const { match } = this.props
this.fetch(match.params.id)
}
componentWillReceiveProps(nextProps) {
const { match } = nextProps
if (match.params.id !== this.props.match.params.id) {
this.fetch(match.params.id)
}
}
fetch(id) {
this.setState({
author: null,
})
scholar(`author/${id}`).then(author => {
if (this.props.match.params.id !== id) return
author.email = faker.internet.email()
author.affiliation = [
faker.address.streetAddress(),
faker.address.city(),
faker.address.country(),
].join(', ')
this.setState({ author })
// TODO: need years from the API, this is too many requests
// Promise.all(
// author.papers.map(paper => scholar(`paper/${paper.paperId}`)),
// ).then(papers => {
// author.papers = papers.sort(newestFirst)
// this.setState({ author })
// })
})
}
assignReviewer = () => {
// const { author } = this.state
// TODO: need to know the submission to assign this reviewer to
}
render() {
const { author } = this.state
const { project, version } = this.props
if (!author) return <div>Loading</div>
return (
<AuthorLayout
assignReviewer={this.assignReviewer}
author={author}
project={project}
version={version}
/>
)
}
}
export default compose(
ConnectPage(({ match }) => [
actions.getCollection({ id: match.params.project }),
actions.getFragment(
{ id: match.params.project },
{ id: match.params.version },
),
]),
connect((state, { match }) => {
const project = selectCollection(state, match.params.project)
const version = selectFragment(state, match.params.version)
return { project, version }
}),
)(AuthorPage)
import React from 'react'
import Author from './Author'
import Paper from './Paper'
import Metadata from './Metadata'
import classes from './FindReviewersLayout.local.scss'
const FindReviewersLayout = ({
authors,
papers,
project,
version,
error,
toggleAuthor,
}) => (
<div className={classes.root}>
<Metadata version={version} />
{error && <div>{error}</div>}
{authors ? (
<div className={classes.row}>
<div className={classes.column}>
<h2 className={classes.heading}>Similar Papers</h2>
{papers.map(paper => (
<Paper
id={paper.id}
key={paper.id}
paper={paper}
project={project}
version={version}
/>
))}
</div>
<div className={classes.column}>
<h2 className={classes.heading}>Suggested Reviewers</h2>
{authors.map(author => (
<Author
author={author}
key={author.id}
project={project}
toggleAuthor={toggleAuthor(author.id)}
version={version}
/>
))}
</div>
</div>
) : (
<div>Searching for reviewers</div>
)}
</div>
)
export default FindReviewersLayout
.root {
padding: 0 20px;
}
.root a {
color: inherit;
font-style: normal;
}
.root a:hover {
color: var(--color-primary);
}
.root a:visited {
color: inherit;
}
.row {
display: flex;
}
.column {
flex: 1;
font-family: var(--font-interface);
padding-right: 20px;
}
.heading {
display: flex;
font-family: var(--font-author);
justify-content: space-between;
}
.button {
font-size: 12px;
}
A page showing submission metadata and a list of suggested reviewers and similar papers.
```js
const papers = [
{
id: faker.random.uuid(),
title: faker.lorem.sentence(20, 5),
scorePercent: faker.random.number({ min: 1, max: 100 })
},
{
id: faker.random.uuid(),
title: faker.lorem.sentence(20, 5),
scorePercent: faker.random.number({ min: 1, max: 100 })
},
{
id: faker.random.uuid(),
title: faker.lorem.sentence(20, 5),
scorePercent: faker.random.number({ min: 1, max: 100 })
}
]
const authors = [
{
id: faker.random.uuid(),
name: faker.name.findName(),
papers: papers.slice(0, 2)
},
{
id: faker.random.uuid(),
name: faker.name.findName(),
papers: papers.slice(0, 1)
},
{
id: faker.random.uuid(),
name: faker.name.findName(),
papers: papers.slice(1, 2)
}
];
const version = {
metadata: {
title: faker.lorem.sentence(20, 5),
abstract: faker.lorem.sentences(5),
authors: [
faker.name.findName(),
faker.name.findName(),
faker.name.findName(),
]
}
};
<FindReviewersLayout
authors={authors}
papers={papers}
version={version}
error={null}
/>
```
import React from 'react'
import { compose } from 'recompose'
import { connect } from 'react-redux'
import { actions } from 'pubsweet-client'
import { ink } from 'pubsweet-component-ink-frontend/actions'
import { ConnectPage } from 'xpub-connect'
import { selectCollection, selectFragment } from 'xpub-selectors'
import { calculateAuthorScores, calculatePaperScores } from '../lib/score'
import FindReviewersLayout from './FindReviewersLayout'
class FindReviewersPage extends React.Component {
state = {
papers: null,
authors: null,
error: null,
}
componentDidMount() {
const { version } = this.props
this.fetch(version)
}
componentWillReceiveProps(nextProps) {
const { version } = nextProps
if (version.id !== this.props.version.id) {
this.fetch(version)
}
}
fetch({ metadata: { title, abstract } }) {
const text = [title, abstract].join('\n\n')
this.setState({
papers: null,
authors: null,
error: null,
})
const file = new File([text], 'paper.txt', { type: 'text/plain' })
const options = {
recipe: 'find-reviewers',
outputFileName: 'paper_mlt.json',
}
this.props
.ink(file, options)
.then(response => {
const data = JSON.parse(response.converted)
const papers = calculatePaperScores(data)
const authors = calculateAuthorScores(papers)
// TODO: use actual board member data
authors.forEach(author => {
author.boardMember = Math.random() > 0.95
author.flagged = Math.random() > 0.9
author.expanded = true // TODO: remove the "expanded" property if not useful
})
this.setState({ papers, authors })
})
.catch(error => {
console.error(error)
this.setState({
error:
'Sorry, there was a problem calculating reviewers for this submission',
})
})
}
toggleAuthor = id => field => () => {
const { authors } = this.state
const index = authors.findIndex(author => author.id === id)
authors[index][field] = !authors[index][field]
this.setState({ authors })
}
render() {
const { error, authors, papers } = this.state
return (
<FindReviewersLayout
authors={authors}
error={error}
papers={papers}
project={this.props.project}
toggleAuthor={this.toggleAuthor}
version={this.props.version}
/>
)
}
}
export default compose(
ConnectPage(({ match }) => [
actions.getCollection({ id: match.params.project }),
actions.getFragment(
{ id: match.params.project },
{ id: match.params.version },
),
]),
connect(
(state, { match }) => {
const project = selectCollection(state, match.params.project)
const version = selectFragment(state, match.params.version)
return { project, version }
},
{ ink },
),
)(FindReviewersPage)