@manuscripts/body-editor 3.12.1 → 3.12.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/components/affiliations/AffiliationItem.js +7 -11
- package/dist/cjs/components/affiliations/AffiliationList.js +3 -5
- package/dist/cjs/components/affiliations/AffiliationsModal.js +13 -4
- package/dist/cjs/components/authors/AuthorDetailsForm.js +44 -34
- package/dist/cjs/components/authors/AuthorList.js +4 -2
- package/dist/cjs/components/authors/AuthorsModal.js +10 -3
- package/dist/cjs/components/authors/DraggableAuthor.js +7 -11
- package/dist/cjs/components/awards/AwardForm.js +108 -100
- package/dist/cjs/components/hooks/use-debounce.js +15 -0
- package/dist/cjs/components/modal-drawer/GenericDrawerGroup.js +1 -0
- package/dist/cjs/components/references/CitationEditor.js +6 -7
- package/dist/cjs/components/references/ImportBibliographyForm.js +12 -3
- package/dist/cjs/components/references/ReferenceForm/PersonDropDown.js +1 -1
- package/dist/cjs/components/references/ReferenceForm/styled-components.js +12 -3
- package/dist/cjs/components/references/ReferenceSearch.js +2 -2
- package/dist/cjs/components/references/ReferenceSearchResults.js +58 -11
- package/dist/cjs/components/references/ReferenceSearchSection.js +3 -11
- package/dist/cjs/components/references/ReferencesModal.js +5 -4
- package/dist/cjs/components/toolbar/InsertTableDialog.js +11 -3
- package/dist/cjs/components/views/CrossReferenceItems.js +8 -14
- package/dist/cjs/components/views/FootnotesSelector.js +6 -6
- package/dist/cjs/components/views/InsertSpecialCharacter.js +24 -10
- package/dist/cjs/components/views/LinkForm.js +8 -1
- package/dist/cjs/lib/normalize.js +8 -6
- package/dist/cjs/lib/popper.js +4 -2
- package/dist/cjs/versions.js +1 -1
- package/dist/cjs/views/cross_reference_editable.js +1 -0
- package/dist/cjs/views/equation_editable.js +6 -0
- package/dist/cjs/views/inline_equation_editable.js +6 -0
- package/dist/cjs/views/inline_footnote.js +1 -0
- package/dist/es/components/affiliations/AffiliationItem.js +8 -12
- package/dist/es/components/affiliations/AffiliationList.js +3 -5
- package/dist/es/components/affiliations/AffiliationsModal.js +14 -5
- package/dist/es/components/authors/AuthorDetailsForm.js +43 -33
- package/dist/es/components/authors/AuthorList.js +4 -2
- package/dist/es/components/authors/AuthorsModal.js +11 -4
- package/dist/es/components/authors/DraggableAuthor.js +8 -12
- package/dist/es/components/awards/AwardForm.js +110 -102
- package/dist/es/components/hooks/use-debounce.js +15 -1
- package/dist/es/components/modal-drawer/GenericDrawerGroup.js +2 -1
- package/dist/es/components/references/CitationEditor.js +7 -8
- package/dist/es/components/references/ImportBibliographyForm.js +14 -5
- package/dist/es/components/references/ReferenceForm/PersonDropDown.js +1 -1
- package/dist/es/components/references/ReferenceForm/styled-components.js +13 -4
- package/dist/es/components/references/ReferenceSearch.js +3 -3
- package/dist/es/components/references/ReferenceSearchResults.js +26 -12
- package/dist/es/components/references/ReferenceSearchSection.js +4 -12
- package/dist/es/components/references/ReferencesModal.js +6 -5
- package/dist/es/components/toolbar/InsertTableDialog.js +11 -3
- package/dist/es/components/views/CrossReferenceItems.js +9 -15
- package/dist/es/components/views/FootnotesSelector.js +7 -7
- package/dist/es/components/views/InsertSpecialCharacter.js +25 -11
- package/dist/es/components/views/LinkForm.js +9 -2
- package/dist/es/lib/normalize.js +8 -6
- package/dist/es/lib/popper.js +4 -2
- package/dist/es/versions.js +1 -1
- package/dist/es/views/cross_reference_editable.js +1 -0
- package/dist/es/views/equation_editable.js +6 -0
- package/dist/es/views/inline_equation_editable.js +6 -0
- package/dist/es/views/inline_footnote.js +1 -0
- package/dist/types/components/authors/AuthorDetailsForm.d.ts +2 -2
- package/dist/types/components/hooks/use-debounce.d.ts +1 -0
- package/dist/types/components/references/ReferenceForm/styled-components.d.ts +11 -1
- package/dist/types/components/references/ReferenceSearchResults.d.ts +20 -2
- package/dist/types/versions.d.ts +1 -1
- package/package.json +2 -2
- package/styles/popper.css +2 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { AffiliationIcon, CrclTickAnimation, DeleteIcon, } from '@manuscripts/style-guide';
|
|
1
|
+
import { AffiliationIcon, CrclTickAnimation, DeleteIcon, SecondaryIconButton, withNavigableListItem, } from '@manuscripts/style-guide';
|
|
2
2
|
import React, { useRef } from 'react';
|
|
3
3
|
import styled from 'styled-components';
|
|
4
|
-
const AffiliationContainer = styled.div `
|
|
4
|
+
const AffiliationContainer = withNavigableListItem(styled.div `
|
|
5
5
|
padding: ${(props) => props.theme.grid.unit * 2}px 0
|
|
6
6
|
${(props) => props.theme.grid.unit * 2}px;
|
|
7
7
|
display: flex;
|
|
@@ -19,7 +19,7 @@ const AffiliationContainer = styled.div `
|
|
|
19
19
|
&.active {
|
|
20
20
|
border-color: ${(props) => props.theme.colors.border.primary};
|
|
21
21
|
}
|
|
22
|
-
|
|
22
|
+
`);
|
|
23
23
|
const AffiliationName = styled.div `
|
|
24
24
|
margin-left: 12px;
|
|
25
25
|
flex: 1;
|
|
@@ -43,15 +43,11 @@ const AffiliationBox = styled.div `
|
|
|
43
43
|
flex-direction: column;
|
|
44
44
|
margin-left: 4px;
|
|
45
45
|
`;
|
|
46
|
-
const RemoveButton = styled
|
|
47
|
-
display: flex;
|
|
48
|
-
align-items: center;
|
|
49
|
-
margin-right: 8px;
|
|
46
|
+
const RemoveButton = styled(SecondaryIconButton) `
|
|
50
47
|
svg {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
fill: #6e6e6e;
|
|
48
|
+
path[fill='white'] {
|
|
49
|
+
fill: white;
|
|
50
|
+
}
|
|
55
51
|
}
|
|
56
52
|
`;
|
|
57
53
|
export const AffiliationItem = ({ affiliation, isSelected, onClick, onDelete, showSuccessIcon, }) => {
|
|
@@ -75,6 +71,6 @@ export const AffiliationItem = ({ affiliation, isSelected, onClick, onDelete, sh
|
|
|
75
71
|
affiliation.country ? ', ' : '')),
|
|
76
72
|
affiliation.country && React.createElement(React.Fragment, null, affiliation.country))),
|
|
77
73
|
showSuccessIcon && (React.createElement(CrclTickAnimation, { size: 36, style: { position: 'absolute', left: '10px' } })),
|
|
78
|
-
isSelected && (React.createElement(RemoveButton, { onClick: () => onDelete(), "data-tooltip-content": "Delete" },
|
|
74
|
+
isSelected && (React.createElement(RemoveButton, { size: 13, onClick: () => onDelete(), "data-tooltip-content": "Delete" },
|
|
79
75
|
React.createElement(DeleteIcon, { fill: '#6E6E6E' })))));
|
|
80
76
|
};
|
|
@@ -16,18 +16,16 @@
|
|
|
16
16
|
import React from 'react';
|
|
17
17
|
import styled from 'styled-components';
|
|
18
18
|
import { AffiliationItem } from './AffiliationItem';
|
|
19
|
-
|
|
19
|
+
import { withListNavigation } from '@manuscripts/style-guide';
|
|
20
|
+
const AffiliationListContainer = withListNavigation(styled.div `
|
|
20
21
|
flex: 1;
|
|
21
22
|
overflow-y: visible;
|
|
22
|
-
|
|
23
|
-
`;
|
|
23
|
+
`);
|
|
24
24
|
const AffiliationListTitle = styled.h2 `
|
|
25
25
|
color: #6e6e6e;
|
|
26
26
|
font-size: 18px;
|
|
27
27
|
font-weight: 400;
|
|
28
28
|
line-height: 24px;
|
|
29
|
-
margin-left: 14px;
|
|
30
|
-
margin-top: 20px;
|
|
31
29
|
`;
|
|
32
30
|
export const AffiliationList = ({ affiliation, affiliations, onSelect, onDelete, lastSavedAffiliationId, }) => {
|
|
33
31
|
return (React.createElement(React.Fragment, null,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AddIcon, AddUserIcon, AffiliationPlaceholderIcon, CloseButton, InspectorTab, InspectorTabList, InspectorTabPanel, InspectorTabPanels, InspectorTabs, ModalBody, ModalContainer, ModalHeader, ModalSidebar, ModalSidebarHeader, ModalSidebarTitle, ScrollableModalContent, SidebarContent, StyledModal, } from '@manuscripts/style-guide';
|
|
1
|
+
import { AddIcon, AddUserIcon, AffiliationPlaceholderIcon, CloseButton, InspectorTab, InspectorTabList, InspectorTabPanel, InspectorTabPanels, InspectorTabs, outlineStyle, ModalBody, ModalContainer, ModalHeader, ModalSidebar, ModalSidebarHeader, ModalSidebarTitle, ScrollableModalContent, SidebarContent, StyledModal, } from '@manuscripts/style-guide';
|
|
2
2
|
import { generateNodeID, schema } from '@manuscripts/transform';
|
|
3
3
|
import { isEqual } from 'lodash';
|
|
4
4
|
import React, { useCallback, useEffect, useReducer, useRef, useState, } from 'react';
|
|
@@ -328,7 +328,9 @@ export const AffiliationsModal = ({ authors: $authors, affiliations: $affiliatio
|
|
|
328
328
|
React.createElement(AffiliationList, { affiliation: selection, affiliations: affiliations, onSelect: handleSelect, onDelete: handleShowDeleteDialog, lastSavedAffiliationId: savedAffiliationId }))),
|
|
329
329
|
React.createElement(ScrollableModalContent, { "data-cy": "affiliations-modal-content" }, selection ? (React.createElement(React.Fragment, null,
|
|
330
330
|
React.createElement(AffiliationTabs, null,
|
|
331
|
-
React.createElement(ModalFormActions, { type: 'affiliation', form: 'affiliation-form', onDelete: handleDeleteAffiliation, showingDeleteDialog: showingDeleteDialog
|
|
331
|
+
React.createElement(ModalFormActions, { type: 'affiliation', form: 'affiliation-form', onDelete: handleDeleteAffiliation, showingDeleteDialog: showingDeleteDialog &&
|
|
332
|
+
!(showConfirmationDialog ||
|
|
333
|
+
showRequiredFieldConfirmationDialog), showDeleteDialog: handleShowDeleteDialog, newEntity: newAffiliation, isDisableSave: isDisableSave }),
|
|
332
334
|
React.createElement(InspectorTabList, null,
|
|
333
335
|
React.createElement(InspectorTab, null, "Details"),
|
|
334
336
|
React.createElement(InspectorTab, null, "Authors")),
|
|
@@ -363,9 +365,15 @@ function createEmptyAffiliation(priority) {
|
|
|
363
365
|
};
|
|
364
366
|
}
|
|
365
367
|
const StyledSidebarContent = styled(SidebarContent) `
|
|
366
|
-
padding:
|
|
368
|
+
padding: 8px;
|
|
367
369
|
`;
|
|
368
|
-
const AddAffiliationButton = styled.
|
|
370
|
+
const AddAffiliationButton = styled.button `
|
|
371
|
+
background: none;
|
|
372
|
+
border: none;
|
|
373
|
+
margin: 0;
|
|
374
|
+
font: inherit;
|
|
375
|
+
color: inherit;
|
|
376
|
+
width: 100%;
|
|
369
377
|
display: flex;
|
|
370
378
|
align-items: center;
|
|
371
379
|
padding: 12px 8px 12px 12px;
|
|
@@ -376,6 +384,7 @@ const AddAffiliationButton = styled.div `
|
|
|
376
384
|
border-left: 0;
|
|
377
385
|
border-right: 0;
|
|
378
386
|
}
|
|
387
|
+
${outlineStyle}
|
|
379
388
|
`;
|
|
380
389
|
const ActionTitle = styled.div `
|
|
381
390
|
padding-left: ${(props) => props.theme.grid.unit * 2}px;
|
|
@@ -383,7 +392,7 @@ const ActionTitle = styled.div `
|
|
|
383
392
|
const AffiliationTabs = styled(InspectorTabs) `
|
|
384
393
|
position: relative;
|
|
385
394
|
`;
|
|
386
|
-
const AffiliationTabPanel = styled(InspectorTabPanel).attrs({ unmount: false }) `
|
|
395
|
+
const AffiliationTabPanel = styled(InspectorTabPanel).attrs({ tabIndex: -1, unmount: false }) `
|
|
387
396
|
margin-top: ${(props) => props.theme.grid.unit * 4}px;
|
|
388
397
|
`;
|
|
389
398
|
const StyledModalBody = styled(ModalBody) `
|
|
@@ -17,25 +17,11 @@ import { CheckboxField, CheckboxLabel, TextField, InputErrorText, Label, FormRow
|
|
|
17
17
|
import { Field, Formik, getIn, } from 'formik';
|
|
18
18
|
import React, { useEffect, useRef } from 'react';
|
|
19
19
|
import styled from 'styled-components';
|
|
20
|
+
import { normalizeAuthor } from '../../lib/normalize';
|
|
20
21
|
import { ChangeHandlingForm } from '../ChangeHandlingForm';
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
border: none;
|
|
25
|
-
`;
|
|
26
|
-
const OrcidContainer = styled.div `
|
|
27
|
-
margin: 16px 0 0;
|
|
28
|
-
`;
|
|
29
|
-
const TextFieldWithError = styled(TextField) `
|
|
30
|
-
&:required::placeholder {
|
|
31
|
-
color: ${(props) => props.theme.colors.text.error};
|
|
32
|
-
}
|
|
33
|
-
`;
|
|
34
|
-
export const CheckboxContainer = styled.div `
|
|
35
|
-
display: flex;
|
|
36
|
-
align-items: center;
|
|
37
|
-
gap: 32px;
|
|
38
|
-
`;
|
|
22
|
+
const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
23
|
+
const ORCID_URL_REGEX = /^https:\/\/orcid\.org\/\d{4}-\d{4}-\d{4}-\d{3}[0-9Xx]\/?$/;
|
|
24
|
+
const ORCID_INPUT_PATTERN = ORCID_URL_REGEX.source.slice(1, -1);
|
|
39
25
|
export const AuthorDetailsForm = ({ values, onChange, onSave, actionsRef, isEmailRequired, selectedAffiliations, selectedCreditRoles, authorFormRef, }) => {
|
|
40
26
|
const formRef = useRef(null);
|
|
41
27
|
useEffect(() => {
|
|
@@ -57,17 +43,22 @@ export const AuthorDetailsForm = ({ values, onChange, onSave, actionsRef, isEmai
|
|
|
57
43
|
}
|
|
58
44
|
const validateAuthor = (values) => {
|
|
59
45
|
const errors = {};
|
|
60
|
-
|
|
46
|
+
const email = values.email?.trim();
|
|
47
|
+
if (isEmailRequired && !email) {
|
|
61
48
|
errors.email = 'Email address is required';
|
|
62
49
|
}
|
|
63
|
-
else if (
|
|
64
|
-
!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(values.email)) {
|
|
50
|
+
else if (email && !EMAIL_REGEX.test(email)) {
|
|
65
51
|
errors.email = 'Invalid email address';
|
|
66
52
|
}
|
|
53
|
+
const orcid = values.ORCID?.trim();
|
|
54
|
+
if (orcid && !ORCID_URL_REGEX.test(orcid)) {
|
|
55
|
+
errors.ORCID =
|
|
56
|
+
'Please enter a valid ORCID URL: https://orcid.org/xxxx-xxxx-xxxx-xxxx';
|
|
57
|
+
}
|
|
67
58
|
return errors;
|
|
68
59
|
};
|
|
69
|
-
return (React.createElement(Formik, { initialValues: values, onSubmit: onSave, enableReinitialize: true, validateOnChange: true, innerRef: formRef, validate: validateAuthor }, (formik) => {
|
|
70
|
-
return (React.createElement(ChangeHandlingForm, { onChange: onChange, id: "author-details-form", formRef: authorFormRef, noValidate: true },
|
|
60
|
+
return (React.createElement(Formik, { initialValues: values, onSubmit: (submitted) => onSave(normalizeAuthor(submitted)), enableReinitialize: true, validateOnChange: true, innerRef: formRef, validate: validateAuthor }, (formik) => {
|
|
61
|
+
return (React.createElement(ChangeHandlingForm, { onChange: (next) => onChange(normalizeAuthor(next)), id: "author-details-form", formRef: authorFormRef, noValidate: true },
|
|
71
62
|
React.createElement(FormRow, null,
|
|
72
63
|
React.createElement(Field, { name: 'prefix' }, (props) => (React.createElement(React.Fragment, null,
|
|
73
64
|
React.createElement(Label, { htmlFor: "prefix" }, "Prefix"),
|
|
@@ -95,15 +86,34 @@ export const AuthorDetailsForm = ({ values, onChange, onSave, actionsRef, isEmai
|
|
|
95
86
|
React.createElement(TextFieldWithError, { id: 'email', type: "email", required: isEmailRequired, ...props.field, error: hasError }),
|
|
96
87
|
hasError && (React.createElement(InputErrorText, null, getIn(formik.errors, 'email')))));
|
|
97
88
|
})),
|
|
98
|
-
React.createElement(
|
|
99
|
-
React.createElement(
|
|
100
|
-
React.createElement(
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
89
|
+
React.createElement(FormRow, null,
|
|
90
|
+
React.createElement(CheckboxContainer, { "data-cy": "corresponding-author-container" },
|
|
91
|
+
React.createElement(CheckboxLabel, null,
|
|
92
|
+
React.createElement(Field, { name: 'isCorresponding' }, (props) => (React.createElement(CheckboxField, { id: 'isCorresponding', checked: props.field.value, ...props.field }))),
|
|
93
|
+
React.createElement(LabelText, null, "Corresponding Author")))),
|
|
94
|
+
React.createElement(FormRow, null,
|
|
95
|
+
React.createElement(Field, { name: 'ORCID', type: 'text' }, (props) => {
|
|
96
|
+
const hasError = getIn(formik.touched, 'ORCID') &&
|
|
97
|
+
getIn(formik.errors, 'ORCID');
|
|
98
|
+
return (React.createElement(React.Fragment, null,
|
|
99
|
+
React.createElement(Label, { htmlFor: "orcid" }, "ORCID"),
|
|
100
|
+
React.createElement(TextFieldWithError, { id: 'orcid', type: "url", placeholder: 'https://orcid.org/...', ...props.field, pattern: ORCID_INPUT_PATTERN, title: "Please enter a valid ORCID URL: https://orcid.org/xxxx-xxxx-xxxx-xxxx", error: hasError }),
|
|
101
|
+
hasError && (React.createElement(InputErrorText, null, getIn(formik.errors, 'ORCID')))));
|
|
102
|
+
}))));
|
|
108
103
|
}));
|
|
109
104
|
};
|
|
105
|
+
export const Fieldset = styled.fieldset `
|
|
106
|
+
padding: 0;
|
|
107
|
+
margin: 0;
|
|
108
|
+
border: none;
|
|
109
|
+
`;
|
|
110
|
+
const TextFieldWithError = styled(TextField) `
|
|
111
|
+
&:required::placeholder {
|
|
112
|
+
color: ${(props) => props.theme.colors.text.error};
|
|
113
|
+
}
|
|
114
|
+
`;
|
|
115
|
+
export const CheckboxContainer = styled.div `
|
|
116
|
+
display: flex;
|
|
117
|
+
align-items: center;
|
|
118
|
+
gap: 32px;
|
|
119
|
+
`;
|
|
@@ -18,10 +18,12 @@ import { DndProvider } from 'react-dnd';
|
|
|
18
18
|
import { HTML5Backend } from 'react-dnd-html5-backend';
|
|
19
19
|
import styled from 'styled-components';
|
|
20
20
|
import { DraggableAuthor } from './DraggableAuthor';
|
|
21
|
-
|
|
21
|
+
import { withListNavigation } from '@manuscripts/style-guide';
|
|
22
|
+
const AuthorListContainer = withListNavigation(styled.div `
|
|
22
23
|
flex: 1;
|
|
23
24
|
overflow-y: visible;
|
|
24
|
-
|
|
25
|
+
padding: 0 6px;
|
|
26
|
+
`);
|
|
25
27
|
const AuthorListTitle = styled.h2 `
|
|
26
28
|
color: #6e6e6e;
|
|
27
29
|
font-size: 18px;
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* See the License for the specific language governing permissions and
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
|
-
import { AddIcon, AddInstitutionIcon, AddRoleIcon, AuthorPlaceholderIcon, CloseButton, ModalBody, ModalContainer, ModalHeader, ModalSidebar, ModalSidebarHeader, ModalSidebarTitle, ScrollableModalContent, SidebarContent, StyledModal, InspectorTabs, InspectorTabPanel, InspectorTabList, InspectorTab, InspectorTabPanels, } from '@manuscripts/style-guide';
|
|
16
|
+
import { AddIcon, AddInstitutionIcon, AddRoleIcon, AuthorPlaceholderIcon, CloseButton, ModalBody, ModalContainer, ModalHeader, ModalSidebar, ModalSidebarHeader, ModalSidebarTitle, outlineStyle, ScrollableModalContent, SidebarContent, StyledModal, InspectorTabs, InspectorTabPanel, InspectorTabList, InspectorTab, InspectorTabPanels, } from '@manuscripts/style-guide';
|
|
17
17
|
import { generateNodeID, schema, } from '@manuscripts/transform';
|
|
18
18
|
import { cloneDeep, isEqual, omit } from 'lodash';
|
|
19
19
|
import React, { useCallback, useEffect, useReducer, useRef, useState, } from 'react';
|
|
@@ -346,7 +346,13 @@ function createEmptyAuthor(priority) {
|
|
|
346
346
|
prefix: '',
|
|
347
347
|
};
|
|
348
348
|
}
|
|
349
|
-
const AddAuthorButton = styled.
|
|
349
|
+
const AddAuthorButton = styled.button `
|
|
350
|
+
background: none;
|
|
351
|
+
border: none;
|
|
352
|
+
margin: 0;
|
|
353
|
+
font: inherit;
|
|
354
|
+
color: inherit;
|
|
355
|
+
width: 100%;
|
|
350
356
|
display: flex;
|
|
351
357
|
align-items: center;
|
|
352
358
|
padding: 12px 8px 12px 12px;
|
|
@@ -357,6 +363,7 @@ const AddAuthorButton = styled.div `
|
|
|
357
363
|
border-left: 0;
|
|
358
364
|
border-right: 0;
|
|
359
365
|
}
|
|
366
|
+
${outlineStyle}
|
|
360
367
|
`;
|
|
361
368
|
const ActionTitle = styled.div `
|
|
362
369
|
padding-left: ${(props) => props.theme.grid.unit * 2}px;
|
|
@@ -364,11 +371,11 @@ const ActionTitle = styled.div `
|
|
|
364
371
|
const AuthorTabs = styled(InspectorTabs) `
|
|
365
372
|
position: relative;
|
|
366
373
|
`;
|
|
367
|
-
const AuthorTabPanel = styled(InspectorTabPanel).attrs({ unmount: false }) `
|
|
374
|
+
const AuthorTabPanel = styled(InspectorTabPanel).attrs({ tabIndex: -1, unmount: false }) `
|
|
368
375
|
margin-top: ${(props) => props.theme.grid.unit * 4}px;
|
|
369
376
|
`;
|
|
370
377
|
const StyledSidebarContent = styled(SidebarContent) `
|
|
371
|
-
padding:
|
|
378
|
+
padding: 8px;
|
|
372
379
|
`;
|
|
373
380
|
const StyledModalBody = styled(ModalBody) `
|
|
374
381
|
position: relative;
|
|
@@ -13,13 +13,13 @@
|
|
|
13
13
|
* See the License for the specific language governing permissions and
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
|
-
import { Avatar, CorrespondingAuthorIcon, CrclTickAnimation, DeleteIcon, DraggableIcon, } from '@manuscripts/style-guide';
|
|
16
|
+
import { Avatar, CorrespondingAuthorIcon, CrclTickAnimation, DeleteIcon, DraggableIcon, SecondaryIconButton, withNavigableListItem, } from '@manuscripts/style-guide';
|
|
17
17
|
import React, { useRef, useState } from 'react';
|
|
18
18
|
import { useDrag, useDrop } from 'react-dnd';
|
|
19
19
|
import styled from 'styled-components';
|
|
20
20
|
import { authorLabel } from '../../lib/authors';
|
|
21
21
|
import { getDropSide } from '../../lib/dnd';
|
|
22
|
-
const AuthorContainer = styled.div `
|
|
22
|
+
const AuthorContainer = withNavigableListItem(styled.div `
|
|
23
23
|
padding: ${(props) => props.theme.grid.unit * 2}px 0
|
|
24
24
|
${(props) => props.theme.grid.unit * 2}px;
|
|
25
25
|
display: flex;
|
|
@@ -53,7 +53,7 @@ const AuthorContainer = styled.div `
|
|
|
53
53
|
cursor: grabbing;
|
|
54
54
|
border-bottom-color: ${(props) => props.theme.colors.brand.dark};
|
|
55
55
|
}
|
|
56
|
-
|
|
56
|
+
`);
|
|
57
57
|
const AvatarContainer = styled.div `
|
|
58
58
|
display: inline-flex;
|
|
59
59
|
position: relative;
|
|
@@ -86,15 +86,11 @@ const DragHandle = styled(DraggableIcon) `
|
|
|
86
86
|
margin-left: -4px;
|
|
87
87
|
margin-right: -12px;
|
|
88
88
|
`;
|
|
89
|
-
const RemoveButton = styled
|
|
90
|
-
display: flex;
|
|
91
|
-
align-items: center;
|
|
92
|
-
margin-right: 8px;
|
|
89
|
+
const RemoveButton = styled(SecondaryIconButton) `
|
|
93
90
|
svg {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
fill: #6e6e6e;
|
|
91
|
+
path[fill='white'] {
|
|
92
|
+
fill: white;
|
|
93
|
+
}
|
|
98
94
|
}
|
|
99
95
|
`;
|
|
100
96
|
const StyledCrclTickAnimation = styled(CrclTickAnimation) `
|
|
@@ -162,6 +158,6 @@ export const DraggableAuthor = React.memo(({ author, isSelected, onClick, onDele
|
|
|
162
158
|
React.createElement(AuthorNotes, { "data-cy": "author-notes" }, author.isCorresponding && (React.createElement(AuthorBadge, null,
|
|
163
159
|
React.createElement(CorrespondingAuthorIcon, null))))),
|
|
164
160
|
React.createElement(AuthorName, { "data-cy": "author-name" }, authorLabel(author)),
|
|
165
|
-
isSelected && (React.createElement(RemoveButton, { onClick: () => onDelete(), "data-tooltip-content": "Delete" },
|
|
161
|
+
isSelected && (React.createElement(RemoveButton, { size: 13, onClick: () => onDelete(), "data-tooltip-content": "Delete" },
|
|
166
162
|
React.createElement(DeleteIcon, { fill: '#6E6E6E' })))));
|
|
167
163
|
});
|
|
@@ -13,58 +13,49 @@
|
|
|
13
13
|
* See the License for the specific language governing permissions and
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
|
-
import { MultiValueInput, PrimaryButton, SearchIcon, SecondaryButton, TextField, FormRow, Label,
|
|
16
|
+
import { MultiValueInput, PrimaryButton, SearchIcon, SecondaryButton, TextField, FormRow, Label, ButtonGroup, } from '@manuscripts/style-guide';
|
|
17
17
|
import { Field, Formik } from 'formik';
|
|
18
|
-
import React, {
|
|
19
|
-
import styled from 'styled-components';
|
|
18
|
+
import React, { useMemo, useRef } from 'react';
|
|
19
|
+
import styled, { useTheme } from 'styled-components';
|
|
20
20
|
import { ChangeHandlingForm } from '../ChangeHandlingForm';
|
|
21
|
-
import {
|
|
21
|
+
import { components, } from 'react-select';
|
|
22
|
+
import AsyncCreatableSelect from 'react-select/async-creatable';
|
|
23
|
+
import useDebounced from '../hooks/use-debounce';
|
|
24
|
+
const loadOptions = async (inputValue) => {
|
|
25
|
+
try {
|
|
26
|
+
const formattedQuery = inputValue.replace(/\s+/g, '+');
|
|
27
|
+
const response = await fetch(`https://api.crossref.org/funders?query=${encodeURIComponent(formattedQuery)}`);
|
|
28
|
+
if (!response.ok) {
|
|
29
|
+
console.log(`HTTP error! status: ${response.status}`);
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
const data = await response.json();
|
|
33
|
+
const funderOptions = data.message.items.map((funder) => ({
|
|
34
|
+
value: funder.name,
|
|
35
|
+
label: funder.name,
|
|
36
|
+
}));
|
|
37
|
+
return funderOptions.sort((a, b) => a.label.localeCompare(b.label));
|
|
38
|
+
}
|
|
39
|
+
catch (e) {
|
|
40
|
+
console.error('Error fetching funders:', e);
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
};
|
|
22
44
|
export const AwardForm = ({ values, onSave, onCancel, onChange, }) => {
|
|
23
|
-
const [funders, setFunders] = useState([]);
|
|
24
|
-
const [isLoading, setIsLoading] = useState(false);
|
|
25
|
-
const [searchQuery, setSearchQuery] = useState('');
|
|
26
45
|
const formRef = useRef(null);
|
|
27
46
|
const primaryButtonText = values.source ? 'Update funder' : 'Add funder';
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const query = debouncedSearchQuery;
|
|
32
|
-
if (!query) {
|
|
33
|
-
setFunders([]);
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
setIsLoading(true);
|
|
37
|
-
try {
|
|
38
|
-
const formattedQuery = query.replace(/\s+/g, '+');
|
|
39
|
-
const response = await fetch(`https://api.crossref.org/funders?query=${encodeURIComponent(formattedQuery)}`);
|
|
40
|
-
if (!response.ok) {
|
|
41
|
-
throw new Error(`HTTP error! status: ${response.status}`);
|
|
42
|
-
}
|
|
43
|
-
const data = await response.json();
|
|
44
|
-
const funderOptions = data.message.items.map((funder) => ({
|
|
45
|
-
value: funder.name,
|
|
46
|
-
label: funder.name,
|
|
47
|
-
}));
|
|
48
|
-
funderOptions.sort((a, b) => a.label.localeCompare(b.label));
|
|
49
|
-
setFunders(funderOptions);
|
|
50
|
-
}
|
|
51
|
-
catch (error) {
|
|
52
|
-
console.error('Error fetching funders:', error);
|
|
53
|
-
setFunders([]);
|
|
54
|
-
}
|
|
55
|
-
finally {
|
|
56
|
-
setIsLoading(false);
|
|
57
|
-
}
|
|
58
|
-
};
|
|
59
|
-
searchFunders();
|
|
60
|
-
}, [debouncedSearchQuery]);
|
|
61
|
-
const handleFunderSearch = (event) => {
|
|
62
|
-
setSearchQuery(event.target.value);
|
|
63
|
-
};
|
|
47
|
+
const themes = useTheme();
|
|
48
|
+
const customStyles = useMemo(() => getCustomStyles(themes), [themes]);
|
|
49
|
+
const handleOnLoadFunders = useDebounced(async (inputValue) => loadOptions(inputValue), 300);
|
|
64
50
|
const handleCancel = () => {
|
|
65
51
|
formRef.current?.resetForm();
|
|
66
52
|
onCancel();
|
|
67
53
|
};
|
|
54
|
+
const handleInputChange = (value, action) => {
|
|
55
|
+
if (action.action === 'input-change') {
|
|
56
|
+
formRef.current?.setFieldValue('source', value);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
68
59
|
const validate = (values) => {
|
|
69
60
|
const errors = {};
|
|
70
61
|
if (!values.source) {
|
|
@@ -80,18 +71,14 @@ export const AwardForm = ({ values, onSave, onCancel, onChange, }) => {
|
|
|
80
71
|
React.createElement(Field, { type: "hidden", name: "id" }),
|
|
81
72
|
React.createElement(FormRow, null,
|
|
82
73
|
React.createElement(Label, { htmlFor: 'source' }, "Funder name"),
|
|
83
|
-
React.createElement(
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
funders.length > 0 && (React.createElement(SearchResults, null, funders.map((funder) => (React.createElement(SearchResultItem, { key: funder.value, onClick: () => {
|
|
92
|
-
formik.setFieldValue('source', funder.value);
|
|
93
|
-
setFunders([]);
|
|
94
|
-
} }, funder.label)))))),
|
|
74
|
+
React.createElement(Field, { name: "source" }, (props) => (React.createElement(AsyncCreatableSelect, { inputId: 'source', loadOptions: handleOnLoadFunders, onInputChange: handleInputChange, autoFocus: true, onChange: (option) => option &&
|
|
75
|
+
formRef.current?.setFieldValue('source', option.value), inputValue: props.field.value, value: null, isValidNewOption: () => false, components: {
|
|
76
|
+
Input,
|
|
77
|
+
Control,
|
|
78
|
+
Option,
|
|
79
|
+
DropdownIndicator: null,
|
|
80
|
+
IndicatorSeparator: null,
|
|
81
|
+
}, styles: customStyles, placeholder: "Search for funder..." }))),
|
|
95
82
|
formik.errors.source && formik.touched.source && (React.createElement(ErrorText, null, formik.errors.source))),
|
|
96
83
|
React.createElement(FormRow, null,
|
|
97
84
|
React.createElement(Label, { htmlFor: 'code' }, "Grant number"),
|
|
@@ -102,68 +89,89 @@ export const AwardForm = ({ values, onSave, onCancel, onChange, }) => {
|
|
|
102
89
|
React.createElement(Label, { htmlFor: 'recipient' }, "Recipient name"),
|
|
103
90
|
React.createElement(Field, { name: "recipient" }, (props) => (React.createElement(TextField, { id: "recipient", placeholder: "Enter full name", ...props.field })))),
|
|
104
91
|
React.createElement(FormRow, null,
|
|
105
|
-
React.createElement(
|
|
92
|
+
React.createElement(FormActions, null,
|
|
106
93
|
React.createElement(SecondaryButton, { onClick: handleCancel }, "Cancel"),
|
|
107
94
|
React.createElement(PrimaryButton, { type: "submit", disabled: !formik.dirty || formik.isSubmitting }, primaryButtonText)))));
|
|
108
95
|
}));
|
|
109
96
|
};
|
|
110
|
-
const SearchContainer = styled.div `
|
|
111
|
-
position: relative;
|
|
112
|
-
width: 100%;
|
|
113
|
-
`;
|
|
114
97
|
const SearchIconContainer = styled.span `
|
|
115
98
|
display: flex;
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
transform: translateY(-50%);
|
|
120
|
-
z-index: 2;
|
|
99
|
+
align-items: center;
|
|
100
|
+
padding-left: ${(props) => props.theme.grid.unit * 4}px;
|
|
101
|
+
padding-right: ${(props) => props.theme.grid.unit * 2}px;
|
|
121
102
|
|
|
122
103
|
path {
|
|
123
|
-
stroke: ${(props) => props.
|
|
104
|
+
stroke: ${(props) => props.isFocused
|
|
105
|
+
? props.theme.colors.brand.medium
|
|
106
|
+
: props.theme.colors.text.primary};
|
|
124
107
|
}
|
|
108
|
+
`;
|
|
109
|
+
const OptionWrapper = styled.div `
|
|
110
|
+
padding-left: ${(props) => props.theme.grid.unit * 4}px;
|
|
111
|
+
padding-top: ${(props) => props.theme.grid.unit * 2}px;
|
|
112
|
+
padding-bottom: ${(props) => props.theme.grid.unit * 2}px;
|
|
125
113
|
|
|
126
|
-
${
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
stroke: ${(props) => props.theme.colors.brand.medium};
|
|
114
|
+
background-color: ${(props) => {
|
|
115
|
+
if (props.selected) {
|
|
116
|
+
return props.theme.colors.background.selected;
|
|
130
117
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
background-color: ${(props) => props.theme.colors.background.fifth};
|
|
138
|
-
}
|
|
139
|
-
`;
|
|
140
|
-
const SearchResults = styled.div `
|
|
141
|
-
position: absolute;
|
|
142
|
-
top: 100%;
|
|
143
|
-
left: 0;
|
|
144
|
-
right: 0;
|
|
145
|
-
max-height: 200px;
|
|
146
|
-
overflow-y: auto;
|
|
147
|
-
background: white;
|
|
148
|
-
border: 1px solid ${(props) => props.theme.colors.border.secondary};
|
|
149
|
-
border-radius: 4px;
|
|
150
|
-
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
151
|
-
z-index: 1000;
|
|
152
|
-
`;
|
|
153
|
-
const SearchResultItem = styled.div `
|
|
154
|
-
padding: 8px 12px;
|
|
155
|
-
cursor: pointer;
|
|
118
|
+
if (props.focused) {
|
|
119
|
+
return props.theme.colors.background.fifth;
|
|
120
|
+
}
|
|
121
|
+
return 'transparent';
|
|
122
|
+
}};
|
|
123
|
+
|
|
156
124
|
&:hover {
|
|
157
125
|
background-color: ${(props) => props.theme.colors.background.fifth};
|
|
158
126
|
}
|
|
159
127
|
`;
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
128
|
+
const Option = ({ innerProps, innerRef, data, isFocused, isSelected, }) => {
|
|
129
|
+
return (React.createElement(OptionWrapper, { ...innerProps, ref: innerRef, focused: isFocused, selected: isSelected }, data.label));
|
|
130
|
+
};
|
|
131
|
+
const Control = ({ children, ...props }) => {
|
|
132
|
+
return (React.createElement(components.Control, { ...props },
|
|
133
|
+
React.createElement(SearchIconContainer, { isFocused: props.isFocused },
|
|
134
|
+
React.createElement(SearchIcon, null)),
|
|
135
|
+
children));
|
|
136
|
+
};
|
|
137
|
+
const Input = (props) => (React.createElement(components.Input, { ...props, isHidden: false }));
|
|
138
|
+
const getCustomStyles = (theme) => ({
|
|
139
|
+
control: (provided, state) => ({
|
|
140
|
+
...provided,
|
|
141
|
+
minHeight: '40px',
|
|
142
|
+
borderRadius: '4px',
|
|
143
|
+
border: `${state.isFocused ? '2px' : '1px'} solid ${state.isFocused ? theme.colors.brand.default : theme.colors.border.secondary}`,
|
|
144
|
+
backgroundColor: state.isFocused || state.menuIsOpen
|
|
145
|
+
? theme.colors.background.fifth
|
|
146
|
+
: theme.colors.background.primary,
|
|
147
|
+
boxShadow: 'none',
|
|
148
|
+
cursor: 'text',
|
|
149
|
+
transition: 'background-color 0.2s',
|
|
150
|
+
'&:hover': {
|
|
151
|
+
backgroundColor: theme.colors.background.fifth,
|
|
152
|
+
},
|
|
153
|
+
}),
|
|
154
|
+
input: (provided) => ({
|
|
155
|
+
...provided,
|
|
156
|
+
margin: 0,
|
|
157
|
+
padding: 0,
|
|
158
|
+
color: theme.colors.text.primary,
|
|
159
|
+
fontSize: theme.font.size.normal,
|
|
160
|
+
fontFamily: theme.font.family.sans,
|
|
161
|
+
}),
|
|
162
|
+
placeholder: (provided, state) => ({
|
|
163
|
+
...provided,
|
|
164
|
+
margin: 0,
|
|
165
|
+
fontStyle: 'italic',
|
|
166
|
+
color: state.isFocused ? '#c9c9c9' : theme.colors.text.secondary,
|
|
167
|
+
fontSize: theme.font.size.medium,
|
|
168
|
+
fontFamily: theme.font.family.sans,
|
|
169
|
+
fontWeight: theme.font.weight.normal,
|
|
170
|
+
lineHeight: theme.font.lineHeight.large,
|
|
171
|
+
}),
|
|
172
|
+
});
|
|
173
|
+
const FormActions = styled(ButtonGroup) `
|
|
174
|
+
margin: ${(props) => props.theme.grid.unit * 2}px;
|
|
167
175
|
`;
|
|
168
176
|
const ErrorText = styled.div `
|
|
169
177
|
color: ${(props) => props.theme.colors.text.error};
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* See the License for the specific language governing permissions and
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
|
-
import { useEffect, useState } from 'react';
|
|
16
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
17
17
|
export const useDebounce = (value, delay) => {
|
|
18
18
|
const [debouncedValue, setDebouncedValue] = useState(value);
|
|
19
19
|
useEffect(() => {
|
|
@@ -26,3 +26,17 @@ export const useDebounce = (value, delay) => {
|
|
|
26
26
|
}, [value, delay]);
|
|
27
27
|
return debouncedValue;
|
|
28
28
|
};
|
|
29
|
+
export default function useDebounced(fn, delay) {
|
|
30
|
+
const timeoutRef = useRef(null);
|
|
31
|
+
return useCallback((arg) => {
|
|
32
|
+
const timeout = timeoutRef.current;
|
|
33
|
+
if (timeout) {
|
|
34
|
+
clearTimeout(timeout);
|
|
35
|
+
}
|
|
36
|
+
return new Promise((resolve) => {
|
|
37
|
+
timeoutRef.current = setTimeout(async () => {
|
|
38
|
+
resolve(await fn(arg));
|
|
39
|
+
}, delay);
|
|
40
|
+
});
|
|
41
|
+
}, [fn, delay]);
|
|
42
|
+
}
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* See the License for the specific language governing permissions and
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
|
-
import { SelectedItemsBox } from '@manuscripts/style-guide';
|
|
16
|
+
import { outlineStyle, SelectedItemsBox, } from '@manuscripts/style-guide';
|
|
17
17
|
import React from 'react';
|
|
18
18
|
import styled from 'styled-components';
|
|
19
19
|
export function DrawerGroup({ removeItem, selectedItems, onSelect, items, showDrawer, setShowDrawer, title, labelField, Drawer, cy, Icon, buttonText, }) {
|
|
@@ -43,4 +43,5 @@ export const AssignButton = styled.button `
|
|
|
43
43
|
&:hover {
|
|
44
44
|
opacity: 0.8;
|
|
45
45
|
}
|
|
46
|
+
${outlineStyle}
|
|
46
47
|
`;
|