From 5822b2ca5a497ce039a32f7f6c8f1adfffe374df Mon Sep 17 00:00:00 2001
From: ahamelers <audrey@ahamelers.com>
Date: Sun, 8 Jul 2018 16:53:44 +0100
Subject: [PATCH] fix: #98

---
 app/components/Create.jsx                    |  20 +-
 app/components/Dashboard.jsx                 |   7 +-
 app/components/DashboardPage.jsx             |  36 +-
 app/components/PubMedSearch.jsx              | 202 +++++------
 app/components/PubMedSearchResult.jsx        |   6 +
 app/components/Submit.jsx                    |  25 +-
 app/components/UnmatchedCitation.jsx         | 340 +++++++++----------
 app/components/ui/atoms/Buttons.jsx          |   7 +-
 app/components/ui/atoms/Page.jsx             |  16 +-
 app/components/ui/atoms/TextArea.jsx         |  68 ++++
 app/components/ui/atoms/index.js             |   3 +-
 app/components/ui/molecules/SearchSelect.jsx |  18 +-
 app/redux/createsubmission.js                |  18 +-
 app/routes.js                                |   2 +-
 server/eutils/api.js                         |   4 +-
 15 files changed, 438 insertions(+), 334 deletions(-)
 create mode 100644 app/components/ui/atoms/TextArea.jsx

diff --git a/app/components/Create.jsx b/app/components/Create.jsx
index 3ef8e475b..95a8f0c1a 100644
--- a/app/components/Create.jsx
+++ b/app/components/Create.jsx
@@ -2,7 +2,7 @@ import React from 'react'
 import { withRouter } from 'react-router-dom'
 import styled from 'styled-components'
 import { th } from '@pubsweet/ui-toolkit'
-import { Icon, Button, ErrorText, H2, H3 } from '@pubsweet/ui'
+import { Icon, Button, ErrorText, H2 } from '@pubsweet/ui'
 import {
   updateStatus,
   createNewManuscriptVersion,
@@ -137,17 +137,21 @@ class EPMCCreated extends React.Component {
             <CreatePageHeader currentStep={currentStep} />
             {currentStep === 0 && (
               <div>
-                <H2>
-                  <Icon size={4}>check</Icon>Citation selected
-                </H2>
-                <Citation metadata={metadata} />
-                {this.state.showSearch && (
+                {this.state.showSearch ? (
                   <div>
-                    <H3>Change your citation</H3>
+                    <H2>Citation</H2>
                     <PubMedSearch
                       citationData={this.createNewManuscriptVersion}
+                      metadata={metadata}
                     />
                   </div>
+                ) : (
+                  <div>
+                    <H2>
+                      Citation selected<Icon size={4}>check</Icon>
+                    </H2>
+                    <Citation metadata={metadata} />
+                  </div>
                 )}
               </div>
             )}
@@ -189,7 +193,6 @@ class EPMCCreated extends React.Component {
               />
             )}
             <Buttons>
-              <Status>{status}</Status>
               <div>
                 {!this.state.showSearch && (
                   <Button onClick={this.goPrev}>Back</Button>
@@ -201,6 +204,7 @@ class EPMCCreated extends React.Component {
                   Next
                 </Button>
               </div>
+              <Status>{status}</Status>
             </Buttons>
           </div>
         </StepPanel>
diff --git a/app/components/Dashboard.jsx b/app/components/Dashboard.jsx
index c36703350..a9aebb2a3 100644
--- a/app/components/Dashboard.jsx
+++ b/app/components/Dashboard.jsx
@@ -52,11 +52,8 @@ export default class DashSwitch extends React.Component {
           <StepPanel>
             <div>
               <CreateHeader currentStep={0} />
-              <H2>Citation information</H2>
+              <H2>Citation</H2>
               <PubMedSearch citationData={createManuscript} />
-              <Button onClick={() => this.setState({ showButton: false })}>
-                Cancel
-              </Button>
             </div>
           </StepPanel>
           <InfoPanel>
@@ -69,7 +66,7 @@ export default class DashSwitch extends React.Component {
       <Page>
         <UploadContainer>
           <BigLink onClick={() => this.setState({ showButton: true })} primary>
-            Create New Submission
+            Submit a new manuscript
           </BigLink>
         </UploadContainer>
 
diff --git a/app/components/DashboardPage.jsx b/app/components/DashboardPage.jsx
index 85f067975..e48fb826f 100644
--- a/app/components/DashboardPage.jsx
+++ b/app/components/DashboardPage.jsx
@@ -19,25 +19,35 @@ const reviewerResponse = (project, version, reviewer, status) => dispatch => {
 }
 
 const createManuscript = (citationData, history) => dispatch => {
-  const { title } = citationData
+  const { title, specialInstructions, ...other } = citationData
   dispatch(actions.createCollection({ title })).then(({ collection }) => {
     if (!collection.id) {
       throw new Error('Failed to create a project')
     }
     // TODO: create teams?
     // TODO: rethrow errors so they can be caught here
-    // TODO: change way ID is generated
-    return dispatch(
-      actions.createFragment(collection, {
-        created: new Date(), // TODO: set on server
-        fragmentType: 'version',
-        metadata: citationData,
-        version: 1,
-      }),
-    ).then(({ fragment }) => {
-      const route = `/projects/${collection.id}/versions/${fragment.id}/create`
-      history.push(route)
-    })
+    const data = {
+      created: new Date(), // TODO: set on server
+      fragmentType: 'version',
+      metadata: {
+        title,
+        ...other,
+      },
+      version: 1,
+    }
+    if (specialInstructions) {
+      data.notes = {
+        specialInstructions,
+      }
+    }
+    return dispatch(actions.createFragment(collection, data)).then(
+      ({ fragment }) => {
+        const route = `/projects/${collection.id}/versions/${
+          fragment.id
+        }/create`
+        history.push(route)
+      },
+    )
   })
 }
 
diff --git a/app/components/PubMedSearch.jsx b/app/components/PubMedSearch.jsx
index d9481ea29..cbcf71c29 100644
--- a/app/components/PubMedSearch.jsx
+++ b/app/components/PubMedSearch.jsx
@@ -1,9 +1,21 @@
 import React from 'react'
+import ReactDOM from 'react-dom'
 import { withRouter } from 'react-router-dom'
 import styled from 'styled-components'
 import { th } from '@pubsweet/ui-toolkit'
 import { Button, Icon, H2 } from '@pubsweet/ui'
-import { A, Buttons, IconButton, Loading, LoadingIcon, SearchForm } from './ui/'
+import {
+  A,
+  B,
+  Buttons,
+  Close,
+  CloseIcon,
+  Cover,
+  Loading,
+  LoadingIcon,
+  Page,
+  SearchForm,
+} from './ui/'
 import PubMedSearchResult from './PubMedSearchResult'
 import UnmatchedCitation from './UnmatchedCitation'
 
@@ -13,11 +25,16 @@ const LoadMore = styled(Button)`
 const SearchArea = styled.div`
   margin: 0 auto;
 `
+const Notice = styled.p`
+  margin: 0 auto calc(${th('gridUnit')} * 3);
+  a {
+    display: flex;
+    align-items: center;
+    justify-content: flex-start;
+  }
+`
 const Results = styled.div`
-  max-height: 40vh;
-  overflow: auto;
   text-align: center;
-  border: ${th('borderWidth')} ${th('borderStyle')} ${th('colorBackgroundHue')};
 `
 class PubMedSearch extends React.Component {
   constructor(props) {
@@ -36,28 +53,10 @@ class PubMedSearch extends React.Component {
     this.onLoad = this.onLoad.bind(this)
     this.onQueryChange = this.onQueryChange.bind(this)
     this.onSelected = this.onSelected.bind(this)
+    this.handleUnmatchedInfo = this.handleUnmatchedInfo.bind(this)
   }
-  handleUnmatchedInfo = unmatchedCitationInfo => {
-    if (unmatchedCitationInfo.nlmId) {
-      const metadata = {
-        title: unmatchedCitationInfo.title,
-        journalMeta: {
-          nlmuniqueid: unmatchedCitationInfo.nlmId
-            ? unmatchedCitationInfo.nlmId
-            : '',
-          title: unmatchedCitationInfo.journal,
-        },
-      }
-      this.props.citationData(metadata)
-    } else {
-      const metadata = {
-        title: unmatchedCitationInfo.title,
-        customMeta: {
-          unmatchedJournal: unmatchedCitationInfo.journal,
-        },
-      }
-      this.props.citationData(metadata)
-    }
+  handleUnmatchedInfo(metadata) {
+    this.props.citationData(metadata)
   }
   onQueryChange(event) {
     this.setState({
@@ -149,6 +148,27 @@ class PubMedSearch extends React.Component {
     }
   }
   render() {
+    const { metadata } = this.props
+    const { title, journalMeta } = metadata || {}
+    const { title: journalTitle } = journalMeta || {}
+
+    if (this.state.unmatched) {
+      return ReactDOM.createPortal(
+        <Cover>
+          <Page>
+            <Close>
+              <CloseIcon onClick={() => this.setState({ unmatched: false })} />
+            </Close>
+            <UnmatchedCitation
+              journal={title}
+              title={journalTitle}
+              unmatchedInfo={this.handleUnmatchedInfo}
+            />
+          </Page>
+        </Cover>,
+        document.getElementById('root').firstChild,
+      )
+    }
     return (
       <SearchArea>
         {this.state.inPMC ? (
@@ -163,7 +183,7 @@ class PubMedSearch extends React.Component {
               <Button onClick={() => window.location.reload()} primary>
                 End Submission
               </Button>
-              <IconButton
+              <Button
                 onClick={() =>
                   this.setState({
                     inPMC: null,
@@ -171,85 +191,75 @@ class PubMedSearch extends React.Component {
                   })
                 }
               >
-                <Icon>chevron_left</Icon>Back to Search
-              </IconButton>
+                Back to Search
+              </Button>
             </Buttons>
           </div>
         ) : (
           <div>
-            {this.state.unmatched ? (
-              <div>
-                <UnmatchedCitation
-                  getUnmatchedInfo={this.handleUnmatchedInfo}
-                />
-                <p>
-                  <IconButton
-                    onClick={() => this.setState({ unmatched: false })}
-                  >
-                    <Icon>chevron_left</Icon>Back to Search
-                  </IconButton>
-                </p>
-              </div>
-            ) : (
-              <div>
-                <SearchForm
-                  buttonLabel="Search"
-                  disabled={!this.state.enabled}
-                  label="Search for your article"
-                  name="Search"
-                  onChange={this.onQueryChange}
-                  onSubmit={this.onSearch}
-                  placeholder="Your query"
-                  value={this.state.query}
-                />
-                {this.state.results.length > 0 && (
-                  <Results>
-                    {this.state.results.map(
-                      result =>
-                        result.fulljournalname && (
-                          <PubMedSearchResult
-                            key={result.uid}
-                            onClick={() => this.onSelected(result)}
-                            result={result}
-                          />
-                        ),
-                    )}
-                    {this.state.loading && (
-                      <Loading>
-                        <LoadingIcon />
-                      </Loading>
-                    )}
-                    {this.state.results.length < this.state.hitcount &&
-                      !this.state.loading && (
-                        <LoadMore onClick={this.onLoad} secondary>
-                          Load More Results
-                        </LoadMore>
-                      )}
-                  </Results>
-                )}
-                {this.state.results.length === 0 &&
-                  this.state.loading && (
-                    <Loading>
-                      <LoadingIcon />
-                    </Loading>
-                  )}
+            <SearchForm
+              buttonLabel="Search"
+              disabled={!this.state.enabled}
+              label="Search for your article"
+              name="Search"
+              onChange={this.onQueryChange}
+              onSubmit={this.onSearch}
+              placeholder="Your query"
+              value={this.state.query}
+            />
+            {this.state.hitcount !== null && (
+              <Notice>
+                <B>Select your citation from results</B>
+                <br />
                 {this.state.hitcount === 0 && (
-                  <p>
-                    <A onClick={() => this.setState({ unmatched: true })}>
-                      No results found. Click to enter citation manually.
-                    </A>
-                  </p>
+                  <A onClick={() => this.setState({ unmatched: true })}>
+                    <Icon color="currentColor" size={2}>
+                      info
+                    </Icon>{' '}
+                    No results found. Click to enter citation manually.
+                  </A>
                 )}
                 {this.state.results.length > 0 && (
-                  <p>
-                    <A onClick={() => this.setState({ unmatched: true })}>
-                      Manuscript not in results? Click to enter citation
-                      manually.
-                    </A>
-                  </p>
+                  <A onClick={() => this.setState({ unmatched: true })}>
+                    <Icon color="currentColor" size={2}>
+                      info
+                    </Icon>{' '}
+                    Manuscript not in results? Click to enter citation manually.
+                  </A>
+                )}
+              </Notice>
+            )}
+            {this.state.results.length > 0 && (
+              <Results>
+                {this.state.results.map(
+                  result =>
+                    result.fulljournalname && (
+                      <PubMedSearchResult
+                        key={result.uid}
+                        onClick={() => this.onSelected(result)}
+                        result={result}
+                      />
+                    ),
                 )}
-              </div>
+                {this.state.loading && (
+                  <Loading>
+                    <LoadingIcon />
+                  </Loading>
+                )}
+                {this.state.results.length < this.state.hitcount &&
+                  !this.state.loading && (
+                    <LoadMore onClick={this.onLoad} secondary>
+                      Load More Results
+                    </LoadMore>
+                  )}
+              </Results>
             )}
+            {this.state.results.length === 0 &&
+              this.state.loading && (
+                <Loading>
+                  <LoadingIcon />
+                </Loading>
+              )}
           </div>
         )}
       </SearchArea>
diff --git a/app/components/PubMedSearchResult.jsx b/app/components/PubMedSearchResult.jsx
index 70fdb1ddd..4a2808396 100644
--- a/app/components/PubMedSearchResult.jsx
+++ b/app/components/PubMedSearchResult.jsx
@@ -6,9 +6,15 @@ import { HTMLString } from './ui/'
 const ResultContainer = styled.div`
   padding: calc(${th('gridUnit')} * 2) ${th('gridUnit')};
   text-align: left;
+  background-color: ${th('colorTextReverse')};
+  border: ${th('borderWidth')} ${th('borderStyle')} ${th('colorBorder')};
   &:nth-child(odd) {
     background-color: ${th('colorBackgroundHue')};
   }
+  &:nth-child(even) {
+    border-top: 0;
+    border-bottom: 0;
+  }
   &:hover {
     cursor: pointer;
     color: ${th('colorPrimary')};
diff --git a/app/components/Submit.jsx b/app/components/Submit.jsx
index 7e01b5027..2556c8eb9 100644
--- a/app/components/Submit.jsx
+++ b/app/components/Submit.jsx
@@ -5,7 +5,7 @@ import styled, { css, withTheme } from 'styled-components'
 import { th } from '@pubsweet/ui-toolkit'
 import { Icon, ErrorText, H2, H3 } from '@pubsweet/ui'
 
-import { Page, Close, CloseIcon, A } from './ui/'
+import { Page, Center, Cover, Close, CloseIcon, A } from './ui/'
 import {
   updateStatus,
   createNewManuscriptVersion,
@@ -86,17 +86,6 @@ const Header = styled.div`
     margin-bottom: 0px;
   }
 `
-const Cover = styled.div`
-  position: fixed;
-  right: 0;
-  left: 0;
-  top: 0;
-  bottom: 0;
-  width: 100%;
-  height: 100%;
-  background-color: #fff;
-  text-align: center;
-`
 const Image = styled.img`
   max-width: 100%;
 `
@@ -389,11 +378,13 @@ class EPMCSubmit extends React.Component {
                     onClick={() => this.setState({ showFigure: null })}
                   />
                 </Close>
-                <Image src={showFigure.url} />
-                <p>
-                  <b>{showFigure.label ? `${showFigure.label}:` : ''}</b>
-                  {showFigure.name}
-                </p>
+                <Center>
+                  <Image src={showFigure.url} />
+                  <p>
+                    <b>{showFigure.label ? `${showFigure.label}:` : ''}</b>
+                    {showFigure.name}
+                  </p>
+                </Center>
               </Page>
             </Cover>,
             document.getElementById('root').firstChild,
diff --git a/app/components/UnmatchedCitation.jsx b/app/components/UnmatchedCitation.jsx
index fc847a1bf..97d6bbc82 100644
--- a/app/components/UnmatchedCitation.jsx
+++ b/app/components/UnmatchedCitation.jsx
@@ -1,212 +1,198 @@
 import React from 'react'
-import styled from 'styled-components'
-import { th } from '@pubsweet/ui-toolkit'
-import { ErrorText, TextField } from '@pubsweet/ui'
-import { HTMLString, Loading, LoadingIcon, SearchForm } from './ui/'
-
-const ResultList = styled.ul`
-  list-style-type: none;
-  margin: 0;
-  padding: 0;
-  max-height: calc(${th('gridUnit')} * 42);
-  overflow: auto;
-  text-align: left;
-  border: ${th('borderWidth')} ${th('borderStyle')} ${th('colorBackgroundHue')};
-`
-const Input = styled.div`
-  width: 100%;
-  & > div:first-child {
-    width: 100%;
-    max-width: 100%;
-  }
-`
-const ResultListItem = styled.li`
-  display: flex;
-  align-items: center;
-  padding: 0 ${th('gridUnit')};
-  min-height: calc(${th('gridUnit')} * 6);
-  &:hover {
-    cursor: pointer;
-    color: ${th('colorPrimary')};
-  }
-  &:nth-child(odd) {
-    background-color: ${th('colorBackgroundHue')};
-  }
-`
+import * as Joi from 'joi'
+import { debounce } from 'lodash'
+import { Button, ErrorText, H2, TextField } from '@pubsweet/ui'
+import { Buttons, HTMLString, SearchSelect, TextArea } from './ui/'
 
 export default class UnmatchedCitation extends React.Component {
   constructor(props) {
     super(props)
     this.state = {
       title: '',
-      journal: '',
-      journals: [],
-      ids: [],
-      // id: null,
-      doSubmit: false,
+      query: '',
+      journal: null,
+      note: this.props.note,
       enabled: false,
-      loading: false,
+      journals: [],
       titleMessage: '',
-      journalNotExistsMessage: '',
+      journalMessage: '',
     }
-
-    this.onSearchForJournal = this.onSearchForJournal.bind(this)
     this.onQueryChange = this.onQueryChange.bind(this)
+    this.onSearch = debounce(this.onSearch.bind(this), 100)
+    this.enable = this.enable.bind(this)
+    this.submitData = this.submitData.bind(this)
+    this.onTitleChange = this.onTitleChange.bind(this)
+    this.onJournalChange = this.onJournalChange.bind(this)
   }
-
-  async onSearchForJournal(event) {
-    event.preventDefault()
-
-    this.setState({
-      enabled: false,
-      loading: true,
-      journalNotExistsMessage: '',
-    })
-    if (this.state.doSubmit) {
-      if (this.state.title && this.state.journal) {
-        this.props.getUnmatchedInfo({
-          title: this.state.title,
-          journal: this.state.journal,
-        })
-      } else {
-        this.setState({ titleMessage: 'Title is required.' })
-      }
+  enable() {
+    const validTitle = Joi.validate(this.state.title, Joi.string().required())
+    const validJournal = Joi.validate(
+      this.state.journal,
+      Joi.object({
+        title: Joi.string().required(),
+        nlmuniqueid: Joi.string(),
+      }),
+    )
+    if (!validTitle.error && !validJournal.error) {
+      this.setState({ enabled: true })
     } else {
       this.setState({
-        journals: [],
-        ids: [],
-        // id: null,
-        doSubmit: false,
+        titleMessage: validTitle.error ? 'Title is required.' : '',
+        journalMessage: validJournal.error ? 'Journal is required.' : '',
       })
-
-      const query1 = `/eutils/esearch?term=%22${
-        this.state.journal
-      }%22[Title]%20AND%20(serial[Item%20Type]%20AND%20(all[subset]%20NOT%20none[URL]))&db=nlmcatalog&retstart=0`
-      const response = await fetch(query1, {
-        headers: new Headers({
-          Authorization: `Bearer ${window.localStorage.getItem('token')}`,
-        }),
-      })
-      const json = await response.json()
-      const ids = json.esearchresult.idlist
-      const hitcount = json.esearchresult.count
-
-      if (hitcount > 0) {
-        const query2 = `/eutils/efetch?db=nlmcatalog&id=${ids.join()}`
-        const summary = await fetch(query2, {
-          headers: new Headers({
-            Authorization: `Bearer ${window.localStorage.getItem('token')}`,
-          }),
-        })
-        const xml = await summary.text()
-
-        const titles = xml.match(/<Title(.*)>(.*)<\/Title>/g)
-        const nlmIds = xml.match(/<NlmUniqueID>(.*)<\/NlmUniqueID>/g)
-        const journals = titles
-          ? titles.map(x => x.match(/<Title(.*)>(.*)<\/Title>/)[2])
-          : []
-        this.setState({
-          journals,
-          ids: nlmIds
-            ? nlmIds.map(x => x.match(/<NlmUniqueID>(.*)<\/NlmUniqueID>/)[1])
-            : [],
-        })
+    }
+  }
+  submitData() {
+    if (this.state.enabled) {
+      const metadata = {
+        title: this.state.title,
+      }
+      if (this.state.journal.nlmuniqueid) {
+        metadata.journalMeta = this.state.journal
       } else {
-        this.setState({
-          doSubmit: true,
-          journalNotExistsMessage: (
-            <HTMLString
-              string={`This journal was not found. Do you want to submit &apos;${
-                this.state.journal
-              }&apos; as your journal name?`}
-            />
-          ),
-        })
+        metadata.customMeta = {
+          unmatchedJournal: this.state.journal.title,
+        }
       }
+      metadata.specialInstructions = this.state.note
+      this.props.unmatchedInfo(metadata)
     }
+  }
+  onQueryChange(e) {
+    const query = e.target.value
     this.setState({
-      enabled: true,
-      loading: false,
+      query: e ? query : '',
+      journals: [],
     })
+    if (query.trim().length > 0) {
+      this.onSearch(query)
+    }
   }
+  async onSearch(query) {
+    const eSearch = `/eutils/esearch?db=nlmcatalog&term=${query}*[ta]%20AND%20ncbijournals[filter]&retstart=0`
+    const response = await fetch(eSearch, {
+      headers: new Headers({
+        Authorization: `Bearer ${window.localStorage.getItem('token')}`,
+      }),
+    })
+    const json = await response.json()
+    const ids = json.esearchresult.idlist
+    const hitcount = json.esearchresult.count
 
-  onQueryChange(event) {
-    if (event.target.name === 'Journal') {
-      this.setState({
-        enabled: true,
-        doSubmit: false,
-        journal: event.target.value,
+    if (hitcount > 0) {
+      const eFetch = `/eutils/efetch?db=nlmcatalog&id=${ids.join()}`
+      const summary = await fetch(eFetch, {
+        headers: new Headers({
+          Authorization: `Bearer ${window.localStorage.getItem('token')}`,
+        }),
       })
-    } else if (event.target.name === 'Title')
-      this.setState({
+      const xml = await summary.text()
+      const titles = xml.match(/<TitleMain>([\s\S]*?)<\/TitleMain>/g)
+      let nlmIds = xml.match(/<NlmUniqueID>(.*)<\/NlmUniqueID>/g)
+      nlmIds = nlmIds
+        ? nlmIds.map(x => x.match(/<NlmUniqueID>(.*)<\/NlmUniqueID>/)[1])
+        : []
+      const journals = titles
+        ? titles.map((x, i) => ({
+            title: x.match(/<Title (.*)>(.*)<\/Title>/)[2],
+            nlmuniqueid: nlmIds[i],
+          }))
+        : []
+      this.setState({ journals })
+    }
+  }
+  onTitleChange(event) {
+    this.setState(
+      {
         titleMessage: '',
-        enabled: true,
         title: event.target.value,
-      })
+      },
+      () => {
+        this.enable()
+      },
+    )
+  }
+  onJournalChange(journal) {
+    this.setState(
+      {
+        journalMessage: '',
+        journal,
+        query: journal.title,
+      },
+      () => {
+        this.enable()
+      },
+    )
   }
-
   render() {
     return (
       <div>
-        <Input>
-          <TextField
-            invalidTest={this.state.titleMessage}
-            label="Enter manuscript title"
-            name="Title"
-            onChange={this.onQueryChange}
-            placeholder="The title of your manuscript"
-            value={this.state.title}
-          />
-          {this.state.titleMessage && (
-            <ErrorText>{this.state.titleMessage}</ErrorText>
-          )}
-        </Input>
-        <SearchForm
-          buttonLabel={this.state.doSubmit ? 'Submit' : 'Search'}
-          disabled={!this.state.enabled}
-          label="Search Journals"
-          name="Journal"
-          onChange={this.onQueryChange}
-          onSubmit={this.onSearchForJournal}
-          placeholder="The journal name"
-          value={this.state.journal}
+        <H2>Enter citation information</H2>
+        <TextField
+          invalidTest={this.state.titleMessage}
+          label="Enter manuscript title"
+          name="Title"
+          onChange={this.onTitleChange}
+          placeholder="The title of your manuscript"
+          value={this.state.title}
         />
-        {this.state.journalNotExistsMessage && (
-          <p>{this.state.journalNotExistsMessage}</p>
-        )}
-        {this.state.journals.length > 0 && (
-          <ResultList>
-            {this.state.journals.map((journal, index) => (
-              <ResultListItem
-                key={this.state.ids[index]}
-                onClick={() => {
-                  this.setState({
-                    journal,
-                    // id: this.state.ids[index],
-                  })
-                  if (this.state.title) {
-                    this.props.getUnmatchedInfo({
-                      title: this.state.title,
-                      nlmId: this.state.ids[index],
-                      journal,
-                    })
-                  } else {
-                    this.setState({ titleMessage: 'Title is required.' })
-                  }
-                }}
-              >
-                <p>
-                  <HTMLString string={journal} />
-                </p>
-              </ResultListItem>
-            ))}
-          </ResultList>
+        {this.state.titleMessage && (
+          <ErrorText>{this.state.titleMessage}</ErrorText>
         )}
-        {this.state.loading && (
-          <Loading>
-            <LoadingIcon />
-          </Loading>
+        <SearchSelect
+          invalidTest={this.state.journalMessage}
+          label="Search for Journal"
+          onInput={this.onQueryChange}
+          optionsOnChange={this.onJournalChange}
+          placeholder="Journal title abbreviation"
+          query={this.state.query}
+          selectedOptions={this.props.journal}
+          singleSelect
+        >
+          {this.state.journals.map(journal => (
+            <SearchSelect.Option
+              data-option={journal}
+              key={journal.title + journal.id}
+              propKey={journal.title + journal.id}
+            >
+              <HTMLString string={journal.title} />
+            </SearchSelect.Option>
+          ))}
+          {this.state.query.trim().length > 0 && (
+            <SearchSelect.Option
+              data-option={{ title: this.state.query }}
+              key="submit"
+              propKey="submit"
+            >
+              <em>
+                <HTMLString
+                  string={`Submit &apos;${
+                    this.state.query
+                  }&apos; as your journal name`}
+                />
+              </em>
+            </SearchSelect.Option>
+          )}
+        </SearchSelect>
+        {this.state.journalMessage && (
+          <ErrorText>{this.state.journalMessage}</ErrorText>
         )}
+        <TextArea
+          label="Other journal information (optional)"
+          onChange={event => this.setState({ note: event.target.value })}
+          placeholder="List URL, DOI, Volume/Issue, etc."
+          rows={3}
+          value={this.state.note}
+        />
+        <Buttons>
+          <Button
+            disabled={!this.state.enabled}
+            onClick={() => this.submitData()}
+            primary
+          >
+            Submit
+          </Button>
+        </Buttons>
       </div>
     )
   }
diff --git a/app/components/ui/atoms/Buttons.jsx b/app/components/ui/atoms/Buttons.jsx
index 42ab63baa..81548721b 100644
--- a/app/components/ui/atoms/Buttons.jsx
+++ b/app/components/ui/atoms/Buttons.jsx
@@ -4,13 +4,14 @@ import { Button } from '@pubsweet/ui'
 
 const Buttons = styled.div`
   display: flex;
+  flex-direction: row-reverse;
+  align-items: center;
   justify-content: space-between;
-  flex-flow: row;
   margin: calc(${th('gridUnit')} * 3) 0 calc(${th('gridUnit')} * 4);
 
   div {
-    *:first-child {
-      margin: 0 20px;
+    *:first-child + * {
+      margin-left: 20px;
     }
   }
 `
diff --git a/app/components/ui/atoms/Page.jsx b/app/components/ui/atoms/Page.jsx
index 136ae166d..35ec5f2e8 100644
--- a/app/components/ui/atoms/Page.jsx
+++ b/app/components/ui/atoms/Page.jsx
@@ -1,6 +1,17 @@
 import styled from 'styled-components'
 import { th, override } from '@pubsweet/ui-toolkit'
 
+const Cover = styled.div`
+  position: fixed;
+  right: 0;
+  left: 0;
+  top: 0;
+  bottom: 0;
+  width: 100%;
+  height: 100%;
+  background-color: ${th('colorTextReverse')};
+  overflow: auto;
+`
 const Page = styled.div`
   margin: 0 auto;
   max-width: 1000px;
@@ -24,4 +35,7 @@ const Right = styled.div`
 const A = styled.a`
   ${override('ui.Link')};
 `
-export { Page, Header, Center, Right, A }
+const B = styled.strong`
+  font-weight: 600;
+`
+export { Cover, Page, Header, Center, Right, A, B }
diff --git a/app/components/ui/atoms/TextArea.jsx b/app/components/ui/atoms/TextArea.jsx
new file mode 100644
index 000000000..b85123e85
--- /dev/null
+++ b/app/components/ui/atoms/TextArea.jsx
@@ -0,0 +1,68 @@
+import React from 'react'
+import styled from 'styled-components'
+import { th, override } from '@pubsweet/ui-toolkit'
+
+const Root = styled.div`
+  display: flex;
+  flex-direction: column;
+  box-sizing: border-box;
+  max-width: 100%;
+  ${override('ui.TextArea')};
+`
+
+const Label = styled.label`
+  font-size: ${th('fontSizeBaseSmall')};
+  line-height: ${th('lineHeightBaseSmall')};
+  display: block;
+  ${override('ui.Label')};
+  ${override('ui.TextArea.Label')};
+`
+
+const borderColor = ({ theme, validationStatus = 'default' }) =>
+  ({
+    error: theme.colorError,
+    success: theme.colorSuccess,
+    default: theme.colorBorder,
+    warning: theme.colorWarning,
+  }[validationStatus])
+
+const Area = styled.textarea`
+  border: ${th('borderWidth')} ${th('borderStyle')} ${borderColor};
+  border-radius: ${th('borderRadius')};
+  font-family: inherit;
+  font-size: inherit;
+  padding: calc(${th('gridUnit')} * 1.5) ${th('gridUnit')};
+  line-height: ${th('lineHeightBase')};
+  box-sizing: border-box;
+  max-width: 100%;
+  &::placeholder {
+    color: ${th('colorTextPlaceholder')};
+  }
+
+  ${override('ui.TextArea.Area')};
+`
+
+class TextArea extends React.Component {
+  componentWillMount() {
+    // generate a unique ID to link the label to the input
+    // note this may not play well with server rendering
+    this.inputId = `textarea-${Math.round(Math.random() * 1e12).toString(36)}`
+  }
+  render() {
+    const { label, value = '', readonly, rows = 5, ...props } = this.props
+    return (
+      <Root>
+        {label && <Label htmlFor={this.inputId}>{label}</Label>}
+        <Area
+          id={this.inputId}
+          readOnly={readonly}
+          rows={rows}
+          value={value}
+          {...props}
+        />
+      </Root>
+    )
+  }
+}
+
+export default TextArea
diff --git a/app/components/ui/atoms/index.js b/app/components/ui/atoms/index.js
index dbefc9b6a..8b1c261f9 100644
--- a/app/components/ui/atoms/index.js
+++ b/app/components/ui/atoms/index.js
@@ -2,6 +2,7 @@ export { LoadingIcon, Loading } from './LoadingIcon'
 export { CloseIcon, Close } from './CloseIcon'
 export { default as HTMLString } from './HTMLString'
 export { default as Select } from './Select'
-export { Page, Header, Center, Right, A } from './Page'
+export { default as TextArea } from './TextArea'
+export { Cover, Page, Header, Center, Right, A, B } from './Page'
 export { SplitPage, StepPanel, InfoPanel } from './SplitPage'
 export { Buttons, IconButton } from './Buttons'
diff --git a/app/components/ui/molecules/SearchSelect.jsx b/app/components/ui/molecules/SearchSelect.jsx
index 3bf073110..9e8ceaa55 100644
--- a/app/components/ui/molecules/SearchSelect.jsx
+++ b/app/components/ui/molecules/SearchSelect.jsx
@@ -8,7 +8,9 @@ import { isEqual } from 'lodash'
 const Root = styled.div`
   display: flex;
   flex-direction: column;
-  margin-bottom: ${props => (props.inline ? '0' : props.theme.gridUnit)};
+  margin-bottom: ${props =>
+    props.inline ? '0' : `calc(${props.theme.gridUnit} * 3)`};
+  ${override('ui.SearchSelect')};
 `
 const Label = styled.label`
   font-size: ${th('fontSizeBaseSmall')};
@@ -104,7 +106,7 @@ const SearchInput = styled.input`
   }
 `
 const OptionList = styled.ul`
-  max-height: calc(${th('gridUnit')} * 42);
+  max-height: calc(${th('gridUnit')} * 21);
   background-color: ${th('colorBackground')};
   overflow: auto;
   max-width: 100%;
@@ -215,6 +217,8 @@ class SearchSelect extends React.Component {
     }
   }
   addOption(event, option) {
+    event.preventDefault()
+    event.stopPropagation()
     if (option) {
       if (!this.props.singleSelect) {
         const selectedOptions = [...this.state.selectedOptions]
@@ -224,7 +228,10 @@ class SearchSelect extends React.Component {
           this.props.optionsOnChange(selectedOptions)
         }
       } else {
-        this.setState({ selectedOptions: option })
+        this.setState({
+          selectedOptions: option,
+          inFocus: false,
+        })
         this.props.optionsOnChange(option)
       }
     }
@@ -252,6 +259,7 @@ class SearchSelect extends React.Component {
       displaySelected,
       disabled,
       singleSelect = false,
+      placeholder = 'Find as you type...',
       ...props
     } = this.props
     let selectStyle = '6px 6px 6px 6px'
@@ -301,13 +309,13 @@ class SearchSelect extends React.Component {
               onChange={onInput}
               onFocus={this.addFocus}
               onKeyDown={e => this.keyPressed(e)}
-              placeholder="Find as you type..."
+              placeholder={placeholder}
               type="text"
               value={query}
             />
           </SelectBox>
           {children.some(child => {
-            if (children.length > 2) return true
+            if (children.length > 0) return true
             return child && child.length > 0
           }) && (
             <OptionList>
diff --git a/app/redux/createsubmission.js b/app/redux/createsubmission.js
index b3e382e21..2b1fbfae9 100644
--- a/app/redux/createsubmission.js
+++ b/app/redux/createsubmission.js
@@ -38,19 +38,22 @@ export const newManuscriptCitation = (
     fragmentType,
     version,
     metadata,
+    notes,
     ...otherProps
   } = currentVersion
   const { fundingGroup, customMeta } = metadata
+  const { specialInstructions, ...other } = citationData
+  const newData = { ...other }
   if (fundingGroup) {
-    citationData.fundingGroup = fundingGroup
+    newData.fundingGroup = fundingGroup
   }
   if (customMeta) {
     const { releaseDelay } = customMeta
     if (releaseDelay) {
-      if (citationData.customMeta) {
-        citationData.customMeta.releaseDelay = releaseDelay
+      if (newData.customMeta) {
+        newData.customMeta.releaseDelay = releaseDelay
       } else {
-        citationData.customMeta = {
+        newData.customMeta = {
           releaseDelay,
         }
       }
@@ -59,10 +62,15 @@ export const newManuscriptCitation = (
   const newProps = {
     created: new Date(), // TODO: set on server
     fragmentType: 'version',
-    metadata: citationData,
+    metadata: newData,
     version: currentVersion.version + 1,
     ...otherProps,
   }
+  if (specialInstructions) {
+    newProps.notes = {
+      specialInstructions,
+    }
+  }
   dispatch(actions.createFragment(project, newProps)).then(({ fragment }) => {
     const currentPage = history.location.pathname.substr(
       history.location.pathname.lastIndexOf('/') + 1,
diff --git a/app/routes.js b/app/routes.js
index 8ce4860e0..54db9c1b1 100644
--- a/app/routes.js
+++ b/app/routes.js
@@ -33,7 +33,7 @@ const PrivateRoute = ({ component: Component, ...rest }) => (
     {...rest}
     render={props => (
       <AuthenticatedComponent>
-        <Component {...props} />
+        <Component key={props.match.params.version} {...props} />
       </AuthenticatedComponent>
     )}
   />
diff --git a/server/eutils/api.js b/server/eutils/api.js
index c929392e5..0baee7dab 100644
--- a/server/eutils/api.js
+++ b/server/eutils/api.js
@@ -15,9 +15,9 @@ module.exports = app => {
   app.get('/eutils/esearch', authBearer, (req, res) => {
     res.set({ 'Content-Type': 'application/json' })
 
-    const { term, db, retstart } = req.query
+    const { term, db, retstart, sort } = req.query
     const encodedTerm = encodeURIComponent(term)
-    const url = `https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?db=${db}&term=${encodedTerm}&retmode=json&retstart=${retstart}&retmax=25${
+    const url = `https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?db=${db}&term=${encodedTerm}&sort=${sort}&retmode=json&retstart=${retstart}&retmax=25${
       eutilsApiKey ? `&api_key=${eutilsApiKey}` : ''
     }`
 
-- 
GitLab