Commit 3ac7a533 authored by Audrey Hamelers's avatar Audrey Hamelers

Merge branch 'dev' into 'master'

Dev

See merge request !183
parents 7c73735b 7df7478a
Pipeline #13026 passed with stages
in 49 seconds
......@@ -280,6 +280,10 @@
font-weight: bold;
}
.monospace {
font-family: monospace, monospace;
}
dd {
margin-left: 210px;
margin-bottom: 1em;
......@@ -1615,6 +1619,10 @@
font-size: 1.65em;
margin: .5em 0;
}
.manuscript-subtitle {
margin: -.5em 0 .5em;
font-size: 1.45em;
}
.affiliations {
display: block !important;
}
......
import React from 'react'
import styled, { createGlobalStyle } from 'styled-components'
import { Icon, Button } from '@pubsweet/ui'
import { th, darken, lighten } from '@pubsweet/ui-toolkit'
import { Icon, Action } from '@pubsweet/ui'
import { th } from '@pubsweet/ui-toolkit'
import * as PDFJS from 'pdfjs-dist/webpack.js'
import Viewer from './Viewer'
......@@ -31,43 +31,33 @@ const OuterContainer = styled.div`
}
`
const Toolbar = styled.div`
height: calc(${th('gridUnit')} * 3);
background-color: ${darken('colorBackgroundHue', 1)};
box-shadow: 0 0 8px ${darken('colorBackgroundHue', 30)};
background-color: ${th('colorBackgroundHue')};
border-bottom: ${th('borderWidth')} ${th('borderStyle')} ${th('colorBorder')};
position: relative;
z-index: 2;
display: flex;
align-items: center;
justify-content: space-between;
height: calc(${th('gridUnit')} * 6);
padding: ${th('gridUnit')};
box-sizing: border-box;
`
const Zoom = styled(Button)`
const Zoom = styled(Action)`
display: inline-flex;
vertical-align: top;
margin: 0;
height: calc(${th('gridUnit')} * 3);
padding: 0;
min-width: 0;
line-height: calc(${th('gridUnit')} * 3);
border: ${th('borderWidth')} ${th('borderStyle')} ${th('colorBorder')};
background-color: ${th('colorBackgroundHue')};
font-weight: normal;
color: ${th('colorText')};
font-size: ${th('fontSizeBaseSmall')}
&:hover {
background-color: ${lighten('colorBackgroundHue', 4)};
}
align-items: center;
@media screen and (max-width: 450px) {
.hide-mobile {
display: none;
}
}
.rotate svg {
transform: rotate(45deg);
}
`
const Info = styled.span`
display: inline-block;
vertical-align: top;
margin: 0 ${th('gridUnit')};
height: calc(${th('gridUnit')} * 3);
line-height: calc(${th('gridUnit')} * 3);
font-size: ${th('fontSizeBaseSmall')};
`
class PDFViewer extends React.Component {
state = {
......@@ -159,29 +149,37 @@ class PDFViewer extends React.Component {
</Info>
)}
<div>
<Zoom onClick={this.zoomOut}>
<Icon size={2}>minus</Icon>
<Zoom onClick={this.zoomOut} title="Zoom out">
<Icon color="currentColor" size={2.75}>
zoom-out
</Icon>
</Zoom>
<Zoom onClick={this.zoomIn}>
<Icon size={2}>plus</Icon>
<Zoom onClick={this.zoomIn} title="Zoom in">
<Icon color="currentColor" size={2.75}>
zoom-in
</Icon>
</Zoom>
<Info>{(scale * 100).toFixed()}%</Info>
</div>
<div>
<Zoom
onClick={() => this.setState({ scale: 0 })}
style={{ padding: '0 4px', marginRight: '4px' }}
>
Fit&nbsp;<span className="hide-mobile">to width</span>
<Icon className="rotate" color="currentColor" size={2.3}>
maximize-2
</Icon>
<span className="hide-mobile">Fit to width</span>
</Zoom>
</div>
<div>
<Zoom
onClick={() => this.setState({ fullscreen: !fullscreen })}
style={{ padding: '0 4px' }}
>
<Icon color="currentColor" size={2.3}>
{fullscreen ? 'minimize' : 'maximize'}
</Icon>
<span className="hide-mobile">
{fullscreen ? 'Exit full screen' : 'Full screen'}
</span>
<Icon size={2}>{fullscreen ? 'minimize' : 'maximize'}</Icon>
</Zoom>
</div>
</Toolbar>
......
......@@ -10,7 +10,7 @@ import 'pdfjs-dist/web/pdf_viewer.css'
import Loading from './Loading'
const PDFPage = styled.div`
margin: ${th('gridUnit')} auto;
margin: 0 auto;
.page {
margin 0 auto;
position: relative;
......@@ -23,6 +23,9 @@ const PDFPage = styled.div`
position: absolute !important;
}
}
& + div {
margin-top: calc(${th('gridUnit')} * 2);
}
`
class Page extends React.Component {
state = {
......
......@@ -8,7 +8,7 @@ const Pane = styled.div`
text-align: center;
max-width: 100%;
box-sizing: border-box;
height: calc(100vh - (${th('gridUnit')} * 3));
height: calc(100vh - (${th('gridUnit')} * 6));
overflow: auto;
`
......
......@@ -32,6 +32,11 @@ const Columns = styled.div`
box-sizing: border-box;
margin-right: 1%;
margin-left: 1%;
opacity: 1;
transition: opacity 0.5s linear;
&.scaled {
opacity: 0;
}
}
`
const Current = styled.div`
......@@ -61,7 +66,25 @@ const FlexP = styled.div`
justify-content: space-between;
`
class ResolveDuplicates extends React.Component {
state = { error: null }
state = { error: null, success: null }
componentDidUpdate() {
const { duplicates, close } = this.props
const { success } = this.state
if (success && this.portal) {
const redirect = duplicates && duplicates.find(d => d.id === success)
setTimeout(() => {
this.setState({ success: null })
if (duplicates.length < 1) {
close()
}
if (redirect) {
this.props.history.replace(
`/submission/${redirect.id}/${States.admin[redirect.status].url}`,
)
}
}, 2500)
}
}
setRef = portal => {
this.portal = portal
}
......@@ -111,7 +134,7 @@ class ResolveDuplicates extends React.Component {
}
render() {
const { close, manuscript: current, duplicates } = this.props
const { error } = this.state
const { error, success } = this.state
return (
<Mutation
mutation={REPLACE_MANUSCRIPT}
......@@ -135,13 +158,14 @@ class ResolveDuplicates extends React.Component {
}}
>
{(replaceManuscript, { data }) => {
const replace = async (keepId, throwId) => {
const { data } = await replaceManuscript({
variables: { keepId, throwId },
})
if (data.replaceManuscript && throwId === current.id) {
this.props.history.push('/')
}
const replace = (keepId, throwId, e) => {
e.currentTarget.parentNode.classList.add('scaled')
setTimeout(async () => {
await replaceManuscript({
variables: { keepId, throwId },
})
this.setState({ success: keepId })
}, 500)
}
return (
<Portal ref={this.setRef} transparent>
......@@ -149,13 +173,22 @@ class ResolveDuplicates extends React.Component {
<Columns>
<H3>This manuscript</H3>
<H3>Duplicate(s)</H3>
{error && (
<div style={{ flex: '0 0 100%' }}>
<Notification type="error">
Two articles cannot have the same {error.toUpperCase()}.
</Notification>
</div>
)}
{error ||
(success && (
<div style={{ flex: '0 0 100%' }}>
{error && (
<Notification type="error">
Two articles cannot have the same{' '}
{error.toUpperCase()}.
</Notification>
)}
{success && (
<Notification type="success">
Duplicate merged and removed.
</Notification>
)}
</div>
))}
<Current>
<H4>{current.id}</H4>
<Citation journal={current.journal} metadata={current.meta} />
......@@ -178,13 +211,14 @@ class ResolveDuplicates extends React.Component {
))}
</p>
<Button
onClick={() => replace(duplicates[0].id, current.id)}
onClick={e => replace(duplicates[0].id, current.id, e)}
primary
>
Remove this &amp; transfer grants
<Icon color="currentColor">arrow-right</Icon>
</Button>
</Current>
{duplicates.length < 1 && <div />}
{duplicates.map((dupe, i) => (
<React.Fragment key={dupe.id}>
<Dupes>
......@@ -225,7 +259,7 @@ class ResolveDuplicates extends React.Component {
))}
</p>
<Button
onClick={() => replace(current.id, dupe.id)}
onClick={e => replace(current.id, dupe.id, e)}
primary
>
<Icon color="currentColor">arrow-left</Icon>
......
......@@ -2,9 +2,10 @@ import React from 'react'
import { ApolloConsumer } from 'react-apollo'
import styled from 'styled-components'
import { th } from '@pubsweet/ui-toolkit'
import { TextField, RadioGroup, ErrorText, H2, H3 } from '@pubsweet/ui'
import { LoadingIcon, Notification } from '../ui'
import { Action, TextField, RadioGroup, ErrorText, H2, H3 } from '@pubsweet/ui'
import { LoadingIcon, Notification, Toggle } from './ui'
import { GET_USER } from './operations'
import UserIdSearch from './UserIdSearch'
const Joi = require('joi')
......@@ -23,13 +24,21 @@ const NewReviewerForm = styled.div`
display: flex;
align-items: flex-start;
flex-wrap: wrap;
margin-bottom: calc(${th('gridUnit')} * 2);
& > * {
padding: 0 calc(${th('gridUnit')} * 2);
width: calc(${th('gridUnit')} * 42);
}
button {
font-size: ${th('fontSizeBaseSmall')};
}
`
const SearchArea = styled.div`
padding: 0 calc(${th('gridUnit')} * 2);
`
class SelectReviewer extends React.Component {
state = {
showToggle: false,
newFormDisabled: true,
loading: true,
options: [],
......@@ -121,6 +130,7 @@ class SelectReviewer extends React.Component {
surname: selectedReviewer.name.surname,
email: selectedReviewer.email,
newFormDisabled: false,
showToggle: true,
}
}
}
......@@ -143,10 +153,17 @@ class SelectReviewer extends React.Component {
surname,
email,
newFormDisabled,
showToggle,
options,
loading,
} = this.state
const { funding, reviewer, reviewerNote, submitter } = this.props
const {
currentUser,
funding,
reviewer,
reviewerNote,
submitter,
} = this.props
return (
<ApolloConsumer>
{client => {
......@@ -196,12 +213,15 @@ class SelectReviewer extends React.Component {
}
const setReviewer = async value => {
if (value === 'new') {
this.setState({ newFormDisabled: false }, () => {
this.nameInput.querySelector('input').focus()
})
this.setState(
{ showToggle: true, newFormDisabled: false },
() => {
this.nameInput.querySelector('input').focus()
},
)
} else {
const newReviewer = {}
this.setState({ newFormDisabled: true })
this.setState({ showToggle: false, newFormDisabled: true })
if (value === 'reviewer') {
if (reviewerNote) {
this.props.setReviewerNote('delete')
......@@ -231,6 +251,21 @@ class SelectReviewer extends React.Component {
}
}
}
const setReviewerWithId = user => {
const { name, email } = user.identities[0]
const newReviewer = {
id: user.id,
name,
email,
}
this.props.setReviewerNote(newReviewer)
this.setState({
name: name.givenNames,
surname: name.surname,
email,
newFormDisabled: false,
})
}
return (
<div>
<H2>Reviewer</H2>
......@@ -257,6 +292,36 @@ class SelectReviewer extends React.Component {
options={options}
value={selected}
/>
{currentUser.admin && showToggle && (
<NewReviewerForm>
<Toggle>
<Action
className={!newFormDisabled && 'current'}
onClick={() =>
this.setState({ newFormDisabled: false })
}
>
Enter new
</Action>
<Action
className={newFormDisabled && 'current'}
onClick={() =>
this.setState({ newFormDisabled: true })
}
>
Enter ID
</Action>
</Toggle>
</NewReviewerForm>
)}
{showToggle && newFormDisabled && (
<SearchArea>
<UserIdSearch
success={user => setReviewerWithId(user)}
successLabel="Select"
/>
</SearchArea>
)}
{!newFormDisabled && (
<NewReviewerForm>
<div ref={this.setNameRef}>
......
......@@ -9,6 +9,7 @@ import {
RightSide,
Loading,
LoadingIcon,
Notification,
} from './ui'
import Mailer from './mailer'
import { States } from './dashboard'
......@@ -33,7 +34,8 @@ const Progress = styled.span`
margin-left: calc(${th('gridUnit')} * 3);
font-weight: normal;
font-style: italic;
color: ${th('colorTextPlaceholder')} &.saved {
color: ${th('colorTextPlaceholder')};
&.saved {
color: ${th('colorText')};
}
`
......@@ -138,6 +140,11 @@ const SubmissionHeader = BaseComponent => ({
</FlexDiv>
</RightSide>
</ManuscriptHead>
{manuscript.deleted && (
<Notification fullscreen type="error">
This submission has been deleted.
</Notification>
)}
<BaseComponent
currentUser={currentUser}
manuscript={manuscript}
......
import React from 'react'
import styled from 'styled-components'
import { th } from '@pubsweet/ui-toolkit'
import { Button, Link } from '@pubsweet/ui'
import { withApollo } from 'react-apollo'
import { SearchForm as Search, Notification } from './ui'
import { GET_USER_ID } from './operations'
const Result = styled.div`
border: ${th('borderWidth')} ${th('borderStyle')} ${th('colorBorder')};
background-color: ${th('colorBackgroundHue')};
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
& > * {
margin: ${th('gridUnit')};
}
a {
min-width: 310px;
}
button:first-child {
margin-right: ${th('gridUnit')};
}
@media screen and (max-width: 733px) {
span {
display: block;
}
}
`
const SearchForm = styled(Search)`
max-width: 400px;
`
class UserIdSearch extends React.Component {
state = {
query: '',
result: null,
error: null,
}
onSearch(searchForm, e) {
e.preventDefault()
this.setState({
result: null,
error: null,
})
const options = {
query: GET_USER_ID,
variables: { id: this.state.query },
}
this.props.client
.query(options)
.then(response => this.setState({ result: response.data.epmc_user }))
.catch(e => {
const error = e.graphQLErrors
? 'Please enter a valid existing ID'
: 'Error occured. please try again'
this.setState({ error })
})
}
render() {
const { result, query } = this.state
const { name, email } = (result && result.identities[0]) || ''
const { title, givenNames, surname } = name || ''
return (
<React.Fragment>
{this.state.error && (
<Notification type="error">{this.state.error}</Notification>
)}
{!result && (
<SearchForm
name="searchById"
noButton="true"
onChange={e => this.setState({ query: e.target.value.trim() })}
onSubmit={e => this.onSearch('usersById', e)}
placeholder="Enter account ID"
value={query}
/>
)}
{result && (
<Result>
<Link to={`/manage-account/${result.id}`}>
<span>{result.id}</span>
</Link>
<span>{`${title ? `${title} ` : ''}${givenNames} ${surname}`}</span>
<span>{email}</span>
<div>
<Button onClick={() => this.props.success(result)} primary>
{this.props.successLabel}
</Button>
<Button onClick={() => this.setState({ result: null })}>
Cancel
</Button>
</div>
</Result>
)}
</React.Fragment>
)
}
}
export default withApollo(UserIdSearch)
......@@ -23,6 +23,7 @@ const DetailsTable = styled(Table)`
`
const TD = styled.td`
font-size: ${th('fontSizeBaseSmall')};
word-break: break-word;
@media screen and (max-width: 600px) {
display: inline-block;
width: 100%;
......
......@@ -37,6 +37,7 @@ const ActivityPageContainer = ({ currentUser, history, match, ...props }) => (
<ActivityPageWithHeader
currentUser={currentUser}
manuscript={data.activities}
saved={new Date()}
{...props}
/>
)
......
......@@ -4,7 +4,7 @@ import { Mutation } from 'react-apollo'
import styled, { withTheme } from 'styled-components'
import { Action, H2, H3, Icon } from '@pubsweet/ui'
import { th } from '@pubsweet/ui-toolkit'
import { B, Toggle } from '../ui'
import { B, Toggle, Notification } from '../ui'
import PubMedSearch, { JournalSearch } from '../citation-search'
import ManualCitation from './ManualCitation'
import { Exit } from './MetaEdit'
......@@ -73,14 +73,20 @@ class CitationEdit extends React.Component {
componentDidMount() {
const { manuscript } = this.props
const { journal } = manuscript
const newStatus = {
show: journal.meta.pubmedStatus ? 'search' : 'enter',
if (journal) {
const newStatus = {
show: journal.meta.pubmedStatus ? 'search' : 'enter',
}
this.setState(newStatus)
}
this.setState(newStatus)
}
componentDidUpdate(prevProps) {
const oldStatus = prevProps.manuscript.journal.meta.pubmedStatus
const newStatus = this.props.manuscript.journal.meta.pubmedStatus
const oldStatus = prevProps.manuscript.journal
? prevProps.manuscript.journal.meta.pubmedStatus
: !!prevProps.manuscript.meta.unmatchedJournal
const newStatus = this.props.manuscript.journal
? this.props.manuscript.journal.meta.pubmedStatus
: !!this.props.manuscript.meta.unmatchedJournal
if (oldStatus !== newStatus) {
;(() => this.setState({ show: newStatus ? 'search' : 'enter' }))()
}
......@@ -88,7 +94,7 @@ class CitationEdit extends React.Component {
render() {
const { manuscript, change, close, linkAndDelete } = this.props
const { journal, meta } = manuscript
const { notes, articleIds: aids } = meta
const { notes, articleIds: aids, unmatchedJournal } = meta
const articleIds = aids ? aids.map(aid => omit(aid, '__typename')) : []
const pmid = articleIds.find(aid => aid.pubIdType === 'pmid')
const note =
......@@ -97,19 +103,35 @@ class CitationEdit extends React.Component {
return (
<React.Fragment>
<H2>Journal</H2>
{unmatchedJournal && (
<Notification type="error">
{`'${unmatchedJournal}' is not in the NLM catalog`}
</Notification>
)}
<JournalCheck>
<div>
<JournalSearch
journal={journal}
setJournal={e => change({ journalId: e.id })}
journal={journal || { journalTitle: unmatchedJournal }}
setJournal={e => {
if (e.id) {
change({ journalId: e.id, meta: { unmatchedJournal: null } })
} else {
change({
journalId: null,
meta: { unmatchedJournal: e.journalTitle },
})
}
}}
/>
</div>
<div>
<Small>Indexed?</Small>
<p>
<IndexedIcon indexed={journal.meta.pubmedStatus} />
</p>
</div>
{journal && (
<div>
<Small>Indexed?</Small>
<p>
<IndexedIcon indexed={journal.meta.pubmedStatus} />
</p>
</div>
)}
</JournalCheck>
<Title>
<H2>Edit citation</H2>
......@@ -147,6 +169,7 @@ class CitationEdit extends React.Component {
</Action>
<Action
className={show === 'enter' && 'current'}
disabled={unmatchedJournal}
onClick={() => this.setState({ show: 'enter' })}
>
Enter manually
......
......@@ -286,7 +286,7 @@ const EventDescription = ({ audit, manuscript }) => {
const { originalData, objectType, changes } = audit
if (objectType === 'note') {
const notes_type = changes.notes_type || originalData.notes_type
if (notes_type === 'userMessage') {
if (notes_type === 'userMessage' && !changes.deleted) {
const content = JSON.parse(changes.content)
if (content && content.to) {
return (
......@@ -320,8 +320,19 @@ const EventDescription = ({ audit, manuscript }) => {
</React.Fragment>
)
} else if (notes_type === 'selectedReviewer') {