@manuscripts/body-editor 1.7.19 → 1.7.21

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.
Files changed (101) hide show
  1. package/dist/cjs/commands.js +7 -4
  2. package/dist/cjs/components/authors/AffiliationButton.js +105 -0
  3. package/dist/cjs/components/authors/AffiliationForm.js +50 -0
  4. package/dist/cjs/components/authors/AuthorActions.js +61 -0
  5. package/dist/cjs/components/authors/AuthorAffiliations.js +116 -0
  6. package/dist/cjs/components/authors/AuthorDetailsForm.js +97 -0
  7. package/dist/cjs/components/authors/AuthorFormPlaceholder.js +57 -0
  8. package/dist/cjs/components/authors/AuthorList.js +40 -0
  9. package/dist/cjs/components/authors/AuthorsModal.js +226 -0
  10. package/dist/cjs/components/authors/ChangeHandlingForm.js +37 -0
  11. package/dist/cjs/components/authors/DeleteAuthorConfirmationDialog.js +23 -0
  12. package/dist/cjs/components/authors/DraggableAuthor.js +170 -0
  13. package/dist/cjs/components/authors/SaveAuthorConfirmationDialog.js +21 -0
  14. package/dist/cjs/lib/array-reducer.js +2 -0
  15. package/dist/cjs/lib/authors.js +43 -0
  16. package/dist/cjs/lib/context-menu.js +8 -5
  17. package/dist/cjs/lib/track-changes-utils.js +5 -1
  18. package/dist/cjs/lib/utils.js +1 -5
  19. package/dist/cjs/lib/view.js +97 -0
  20. package/dist/cjs/plugins/footnotes/index.js +3 -3
  21. package/dist/cjs/views/base_node_view.js +0 -19
  22. package/dist/cjs/views/bibliography_element.js +3 -2
  23. package/dist/cjs/views/citation_editable.js +3 -2
  24. package/dist/cjs/views/contributors.js +78 -25
  25. package/dist/cjs/views/footnote.js +12 -2
  26. package/dist/cjs/views/footnote_editable.js +1 -1
  27. package/dist/cjs/views/footnotes_element.js +2 -1
  28. package/dist/cjs/views/footnotes_element_editable.js +2 -1
  29. package/dist/cjs/views/inline_footnote.js +5 -0
  30. package/dist/es/commands.js +7 -4
  31. package/dist/es/components/authors/AffiliationButton.js +75 -0
  32. package/dist/es/components/authors/AffiliationForm.js +43 -0
  33. package/dist/es/components/authors/AuthorActions.js +31 -0
  34. package/dist/es/components/authors/AuthorAffiliations.js +85 -0
  35. package/dist/es/components/authors/AuthorDetailsForm.js +67 -0
  36. package/dist/es/components/authors/AuthorFormPlaceholder.js +50 -0
  37. package/dist/es/components/authors/AuthorList.js +33 -0
  38. package/dist/es/components/authors/AuthorsModal.js +196 -0
  39. package/dist/es/components/authors/ChangeHandlingForm.js +10 -0
  40. package/dist/es/components/authors/DeleteAuthorConfirmationDialog.js +16 -0
  41. package/dist/es/components/authors/DraggableAuthor.js +140 -0
  42. package/dist/es/components/authors/SaveAuthorConfirmationDialog.js +14 -0
  43. package/dist/es/configs/editor-views.js +2 -2
  44. package/dist/es/lib/array-reducer.js +2 -0
  45. package/dist/es/lib/authors.js +36 -0
  46. package/dist/es/lib/context-menu.js +8 -5
  47. package/dist/es/lib/track-changes-utils.js +3 -0
  48. package/dist/es/lib/utils.js +1 -4
  49. package/dist/es/lib/view.js +64 -0
  50. package/dist/es/plugins/footnotes/index.js +3 -3
  51. package/dist/es/views/base_node_view.js +0 -19
  52. package/dist/es/views/bibliography_element.js +3 -2
  53. package/dist/es/views/citation_editable.js +3 -2
  54. package/dist/es/views/contributors.js +79 -26
  55. package/dist/es/views/footnote.js +10 -1
  56. package/dist/es/views/footnote_editable.js +2 -2
  57. package/dist/es/views/footnotes_element.js +2 -1
  58. package/dist/es/views/footnotes_element_editable.js +2 -1
  59. package/dist/es/views/inline_footnote.js +5 -0
  60. package/dist/types/components/authors/AffiliationButton.d.ts +8 -0
  61. package/dist/types/components/authors/AffiliationForm.d.ts +7 -0
  62. package/dist/types/components/authors/AuthorActions.d.ts +9 -0
  63. package/dist/types/components/authors/AuthorAffiliations.d.ts +11 -0
  64. package/dist/types/components/authors/AuthorDetailsForm.d.ts +30 -0
  65. package/dist/types/components/authors/AuthorFormPlaceholder.d.ts +2 -0
  66. package/dist/types/components/authors/AuthorList.d.ts +25 -0
  67. package/dist/types/components/authors/AuthorsModal.d.ts +26 -0
  68. package/dist/types/components/authors/ChangeHandlingForm.d.ts +5 -0
  69. package/dist/types/components/authors/DeleteAuthorConfirmationDialog.d.ts +9 -0
  70. package/dist/types/components/authors/DraggableAuthor.d.ts +25 -0
  71. package/dist/types/components/authors/SaveAuthorConfirmationDialog.d.ts +7 -0
  72. package/dist/types/components/outline/DraggableTree.d.ts +1 -1
  73. package/dist/types/configs/ManuscriptsEditor.d.ts +0 -2
  74. package/dist/types/configs/editor-views.d.ts +0 -28
  75. package/dist/types/lib/array-reducer.d.ts +5 -1
  76. package/dist/types/lib/authors.d.ts +23 -0
  77. package/dist/types/lib/track-changes-utils.d.ts +1 -0
  78. package/dist/types/lib/utils.d.ts +0 -1
  79. package/dist/types/lib/view.d.ts +25 -0
  80. package/dist/types/views/base_node_view.d.ts +1 -4
  81. package/dist/types/views/bibliography_element_editable.d.ts +0 -2
  82. package/dist/types/views/blockquote_element_editable.d.ts +0 -2
  83. package/dist/types/views/bullet_list_editable.d.ts +0 -2
  84. package/dist/types/views/contributors.d.ts +25 -8
  85. package/dist/types/views/editable_block.d.ts +0 -2
  86. package/dist/types/views/equation_element_editable.d.ts +0 -2
  87. package/dist/types/views/figure_element_editable.d.ts +0 -2
  88. package/dist/types/views/footnote.d.ts +2 -0
  89. package/dist/types/views/footnote_editable.d.ts +0 -2
  90. package/dist/types/views/footnotes_element_editable.d.ts +0 -2
  91. package/dist/types/views/listing_element_editable.d.ts +0 -2
  92. package/dist/types/views/ordered_list_editable.d.ts +0 -2
  93. package/dist/types/views/paragraph_editable.d.ts +0 -2
  94. package/dist/types/views/placeholder_element_editable.d.ts +0 -2
  95. package/dist/types/views/pullquote_element_editable.d.ts +0 -2
  96. package/dist/types/views/section_title_editable.d.ts +0 -2
  97. package/dist/types/views/table_element_editable.d.ts +0 -2
  98. package/dist/types/views/toc_element_editable.d.ts +0 -2
  99. package/package.json +3 -2
  100. package/styles/AdvancedEditor.css +3 -3
  101. package/styles/Editor.css +5 -5
@@ -0,0 +1,85 @@
1
+ import AddAuthor from '@manuscripts/assets/react/AddAuthor';
2
+ import { generateID, ObjectTypes } from '@manuscripts/json-schema';
3
+ import React, { useContext, useMemo, useState } from 'react';
4
+ import CreatableSelect from 'react-select/creatable';
5
+ import styled, { ThemeContext } from 'styled-components';
6
+ import { affiliationLabel, } from '../../lib/authors';
7
+ import { AffiliationButton } from './AffiliationButton';
8
+ const AddAffiliation = styled(AddAuthor) `
9
+ width: 24px;
10
+ height: 24px;
11
+ margin-right: 4px;
12
+
13
+ circle,
14
+ use {
15
+ fill: ${(props) => props.theme.colors.brand.default};
16
+ }
17
+
18
+ path {
19
+ mask: none;
20
+ }
21
+ `;
22
+ export const AddAffiliationIndicator = () => React.createElement(AddAffiliation, null);
23
+ const getSelectStyles = (focus, theme) => ({
24
+ backgroundColor: focus
25
+ ? theme.colors.background.fifth
26
+ : theme.colors.background.primary,
27
+ borderColor: focus
28
+ ? theme.colors.border.field.active
29
+ : theme.colors.border.field.default,
30
+ '&:hover': {
31
+ backgroundColor: theme.colors.background.fifth,
32
+ },
33
+ borderRadius: theme.grid.radius.default,
34
+ boxShadow: 'none',
35
+ fontFamily: theme.font.family.sans,
36
+ });
37
+ export const AuthorAffiliations = ({ author, affiliations, onSave, onAdd, onRemove, }) => {
38
+ const theme = useContext(ThemeContext);
39
+ const [searchText, setSearchText] = useState('');
40
+ const affiliationMap = useMemo(() => new Map(affiliations.map((a) => [a.id, a])), [affiliations]);
41
+ const authorAffiliations = useMemo(() => {
42
+ var _a;
43
+ return (_a = author.affiliations) === null || _a === void 0 ? void 0 : _a.map((i) => affiliationMap.get(i));
44
+ }, [affiliationMap, author]);
45
+ const otherAffiliations = useMemo(() => {
46
+ var _a;
47
+ const copy = new Map(affiliationMap);
48
+ (_a = author.affiliations) === null || _a === void 0 ? void 0 : _a.forEach((i) => copy.delete(i));
49
+ return Array.from(copy.values());
50
+ }, [affiliationMap, author]);
51
+ const options = otherAffiliations.map((a) => ({
52
+ value: a.id,
53
+ label: affiliationLabel(a),
54
+ }));
55
+ const handleChange = (id) => {
56
+ const affiliation = affiliationMap.get(id);
57
+ if (!affiliation) {
58
+ return;
59
+ }
60
+ onAdd(affiliation);
61
+ setSearchText('');
62
+ };
63
+ const handleCreate = (institution) => {
64
+ const affiliation = {
65
+ id: generateID(ObjectTypes.Affiliation),
66
+ addressLine1: '',
67
+ city: '',
68
+ country: '',
69
+ county: '',
70
+ department: '',
71
+ institution: institution,
72
+ postCode: '',
73
+ priority: 0,
74
+ };
75
+ onSave(affiliation);
76
+ onAdd(affiliation);
77
+ };
78
+ return (React.createElement(React.Fragment, null,
79
+ authorAffiliations.map((a) => (React.createElement(AffiliationButton, { key: `${author.id}-${a.id}`, affiliation: a, onSave: onSave, onRemove: () => onRemove(a) }))),
80
+ React.createElement(CreatableSelect, { options: options, isMulti: false, isClearable: true, value: null, inputValue: searchText, components: {
81
+ IndicatorsContainer: AddAffiliationIndicator,
82
+ }, placeholder: "Begin typing to add affiliation", noOptionsMessage: () => 'Type the name of an institution', onChange: (i) => i && handleChange(i.value), onInputChange: setSearchText, onCreateOption: (v) => v && handleCreate(v), isValidNewOption: (t) => !!t, styles: {
83
+ control: (provided, state) => (Object.assign(Object.assign({}, provided), getSelectStyles(state.isFocused, theme))),
84
+ } })));
85
+ };
@@ -0,0 +1,67 @@
1
+ /*!
2
+ * © 2019 Atypon Systems LLC
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ import { CheckboxField, CheckboxLabel, TextField, TextFieldGroupContainer, TextFieldLabel, } from '@manuscripts/style-guide';
17
+ import { Field, Formik } from 'formik';
18
+ import React, { useRef } from 'react';
19
+ import styled from 'styled-components';
20
+ import { ChangeHandlingForm } from './ChangeHandlingForm';
21
+ export const LabelText = styled.div `
22
+ font: ${(props) => props.theme.font.weight.normal}
23
+ ${(props) => props.theme.font.size.normal} / 1
24
+ ${(props) => props.theme.font.family.sans};
25
+ letter-spacing: -0.2px;
26
+ color: ${(props) => props.theme.colors.text.primary};
27
+ `;
28
+ export const Fieldset = styled.fieldset `
29
+ padding: 0;
30
+ margin: 0;
31
+ border: none;
32
+ `;
33
+ const OrcidContainer = styled.div `
34
+ margin: 16px 0 0;
35
+ `;
36
+ export const AuthorDetailsForm = ({ values, onChange, onSave, actionsRef, }) => {
37
+ const formRef = useRef(null);
38
+ if (actionsRef && !actionsRef.current) {
39
+ actionsRef.current = {
40
+ reset: () => {
41
+ var _a;
42
+ (_a = formRef.current) === null || _a === void 0 ? void 0 : _a.resetForm();
43
+ },
44
+ };
45
+ }
46
+ return (React.createElement(Formik, { initialValues: values, onSubmit: onSave, enableReinitialize: true, validateOnChange: false, innerRef: formRef }, (formik) => {
47
+ const isAuthor = formik.values.role === 'author';
48
+ return (React.createElement(ChangeHandlingForm, { onChange: onChange },
49
+ React.createElement(Fieldset, null,
50
+ React.createElement(TextFieldGroupContainer, null,
51
+ React.createElement(Field, { name: 'bibliographicName.given' }, (props) => (React.createElement(TextField, Object.assign({ id: 'given-name', placeholder: 'Given name' }, props.field)))),
52
+ React.createElement(Field, { name: 'bibliographicName.family' }, (props) => (React.createElement(TextField, Object.assign({ id: 'family-name', placeholder: 'Family name' }, props.field))))),
53
+ React.createElement(Field, { name: 'email', type: 'email' }, (props) => (React.createElement(TextField, Object.assign({ id: 'email', placeholder: 'Email address' }, props.field)))),
54
+ React.createElement(CheckboxLabel, { disabled: !isAuthor },
55
+ React.createElement(Field, { name: 'isCorresponding' }, (props) => (React.createElement(CheckboxField, Object.assign({ id: 'isCorresponding', checked: props.field.value, disabled: !isAuthor }, props.field)))),
56
+ React.createElement(LabelText, null, "Corresponding Author")),
57
+ React.createElement(CheckboxLabel, null,
58
+ React.createElement(Field, { name: 'role', type: 'checkbox' }, (props) => (React.createElement(CheckboxField, { name: 'role', checked: isAuthor, onChange: (e) => {
59
+ formik.setFieldValue(props.field.name, e.target.checked ? 'author' : 'other', false);
60
+ } }))),
61
+ React.createElement(LabelText, null, "Include in Authors List")),
62
+ React.createElement(OrcidContainer, null,
63
+ React.createElement(TextFieldLabel, null,
64
+ React.createElement(LabelText, null, "ORCID"),
65
+ React.createElement(Field, { name: 'ORCIDIdentifier', type: 'text' }, (props) => (React.createElement(TextField, Object.assign({ id: 'orcid', placeholder: 'https://orcid.org/...' }, props.field)))))))));
66
+ }));
67
+ };
@@ -0,0 +1,50 @@
1
+ import ContributorDetails from '@manuscripts/assets/react/ContributorDetailsPlaceholder';
2
+ import React from 'react';
3
+ import styled from 'styled-components';
4
+ const OuterContainer = styled.div `
5
+ display: flex;
6
+ flex-direction: column;
7
+ align-items: center;
8
+ justify-content: center;
9
+ height: 100%;
10
+ overflow-y: auto;
11
+ `;
12
+ const InnerContainer = styled.div `
13
+ text-align: center;
14
+ max-width: 480px;
15
+ font-size: ${(props) => props.theme.font.size.xlarge};
16
+ line-height: ${(props) => props.theme.font.lineHeight.large};
17
+ `;
18
+ const Placeholder = styled.div `
19
+ display: flex;
20
+ justify-content: center;
21
+ margin-bottom: ${(props) => props.theme.grid.unit * 5}px;
22
+ `;
23
+ const Action = styled.div `
24
+ display: flex;
25
+ align-items: center;
26
+ justify-content: center;
27
+ font-size: ${(props) => props.theme.font.size.xlarge};
28
+ font-weight: ${(props) => props.theme.font.weight.medium};
29
+ letter-spacing: -0.5px;
30
+ `;
31
+ const Message = styled.div `
32
+ max-width: 400px;
33
+ font-size: ${(props) => props.theme.font.size.xlarge};
34
+ margin-top: ${(props) => props.theme.grid.unit * 6}px;
35
+ font-weight: ${(props) => props.theme.font.weight.light};
36
+ color: ${(props) => props.theme.colors.text.secondary};
37
+
38
+ @media (max-width: 850px) {
39
+ margin-right: ${(props) => props.theme.grid.unit * 5}px;
40
+ margin-left: ${(props) => props.theme.grid.unit * 5}px;
41
+ max-width: 350px;
42
+ }
43
+ `;
44
+ export const AuthorFormPlaceholder = () => (React.createElement(OuterContainer, { "data-cy": 'author-details' },
45
+ React.createElement(InnerContainer, null,
46
+ React.createElement(Placeholder, null,
47
+ React.createElement(ContributorDetails, null)),
48
+ React.createElement(React.Fragment, null,
49
+ React.createElement(Action, null, "Author Details"),
50
+ React.createElement(Message, null, "Select an author from the list to display their details here.")))));
@@ -0,0 +1,33 @@
1
+ /*!
2
+ * © 2019 Atypon Systems LLC
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ import { DragLayer } from '@manuscripts/style-guide';
17
+ import React from 'react';
18
+ import { DndProvider } from 'react-dnd';
19
+ import { HTML5Backend } from 'react-dnd-html5-backend';
20
+ import styled from 'styled-components';
21
+ import { DraggableAuthor } from './DraggableAuthor';
22
+ const AuthorListContainer = styled.div `
23
+ flex: 1;
24
+ overflow-y: visible;
25
+ `;
26
+ export const AuthorList = ({ author, authors, onSelect, moveAuthor, }) => {
27
+ return (React.createElement(DndProvider, { backend: HTML5Backend },
28
+ React.createElement(AuthorListContainer, null,
29
+ React.createElement(DragLayer, null),
30
+ authors.map((a) => {
31
+ return (React.createElement(DraggableAuthor, { key: a.id, author: a, isSelected: a.id === (author === null || author === void 0 ? void 0 : author.id), onClick: () => onSelect(a), moveAuthor: moveAuthor }));
32
+ }))));
33
+ };
@@ -0,0 +1,196 @@
1
+ /*!
2
+ * © 2024 Atypon Systems LLC
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ import AddIcon from '@manuscripts/assets/react/AddIcon';
17
+ import { buildBibliographicName, generateID, ObjectTypes, } from '@manuscripts/json-schema';
18
+ import { CloseButton, ModalBody, ModalContainer, ModalHeader, ModalSidebar, ModalSidebarHeader, ModalSidebarTitle, ScrollableModalContent, SidebarContent, StyledModal, } from '@manuscripts/style-guide';
19
+ import { isEqual } from 'lodash';
20
+ import React, { useReducer, useRef, useState } from 'react';
21
+ import styled from 'styled-components';
22
+ import { arrayReducer } from '../../lib/array-reducer';
23
+ import { authorComparator, } from '../../lib/authors';
24
+ import { AuthorActions } from './AuthorActions';
25
+ import { AuthorAffiliations } from './AuthorAffiliations';
26
+ import { AuthorDetailsForm } from './AuthorDetailsForm';
27
+ import { AuthorFormPlaceholder } from './AuthorFormPlaceholder';
28
+ import { AuthorList } from './AuthorList';
29
+ import { SaveAuthorConfirmationDialog } from './SaveAuthorConfirmationDialog';
30
+ const AddAuthorButton = styled.div `
31
+ display: flex;
32
+ align-items: center;
33
+ margin-bottom: ${(props) => props.theme.grid.unit * 4}px;
34
+ margin-left: ${(props) => props.theme.grid.unit * 2}px;
35
+ cursor: pointer;
36
+ `;
37
+ const ActionTitle = styled.div `
38
+ padding-left: ${(props) => props.theme.grid.unit * 2}px;
39
+ `;
40
+ const FormLabel = styled.legend `
41
+ margin-top: 12px;
42
+ margin-bottom: 12px;
43
+ font: ${(props) => props.theme.font.weight.normal}
44
+ ${(props) => props.theme.font.size.xlarge} /
45
+ ${(props) => props.theme.font.lineHeight.large}
46
+ ${(props) => props.theme.font.family.sans};
47
+ letter-spacing: -0.4px;
48
+ color: ${(props) => props.theme.colors.text.secondary};
49
+ `;
50
+ const AuthorForms = styled.div `
51
+ padding-left: ${(props) => props.theme.grid.unit * 3}px;
52
+ padding-right: ${(props) => props.theme.grid.unit * 3}px;
53
+ `;
54
+ const authorsReducer = arrayReducer((a, b) => a.id === b.id);
55
+ const affiliationsReducer = arrayReducer((a, b) => a.id === b.id);
56
+ const normalize = (author) => {
57
+ return {
58
+ id: author.id,
59
+ role: author.role,
60
+ affiliations: author.affiliations || [],
61
+ bibliographicName: author.bibliographicName,
62
+ email: author.email || '',
63
+ isCorresponding: author.isCorresponding || false,
64
+ ORCIDIdentifier: author.ORCIDIdentifier || '',
65
+ priority: author.priority,
66
+ isJointContributor: author.isJointContributor || false,
67
+ };
68
+ };
69
+ export const AuthorsModal = ({ authors: $authors, affiliations: $affiliations, author, onSaveAuthor, onDeleteAuthor, onSaveAffiliation, }) => {
70
+ const [isOpen, setOpen] = useState(true);
71
+ const [showConfirmationDialog, setShowConfirmationDialog] = useState(false);
72
+ const valuesRef = useRef();
73
+ const actionsRef = useRef();
74
+ const [authors, dispatchAuthors] = useReducer(authorsReducer, $authors.sort(authorComparator));
75
+ const [affiliations, dispatchAffiliations] = useReducer(affiliationsReducer, $affiliations);
76
+ const [selection, setSelection] = useState(author);
77
+ const handleSelect = (author) => {
78
+ const values = valuesRef.current;
79
+ if (values && selection && !isEqual(values, normalize(selection))) {
80
+ setShowConfirmationDialog(true);
81
+ }
82
+ else {
83
+ setSelection(author);
84
+ }
85
+ };
86
+ const handleSaveAuthor = (values) => {
87
+ if (!values || !selection) {
88
+ return;
89
+ }
90
+ const author = Object.assign(Object.assign({}, selection), values);
91
+ onSaveAuthor(author);
92
+ setSelection(author);
93
+ setShowConfirmationDialog(false);
94
+ dispatchAuthors({
95
+ type: 'update',
96
+ items: [author],
97
+ });
98
+ };
99
+ const handleMoveAuthor = (from, to) => {
100
+ const copy = [...authors];
101
+ const order = copy.map((_, i) => (i === from ? to : i));
102
+ copy.sort((a, b) => order[authors.indexOf(a)] - order[authors.indexOf(b)]);
103
+ copy.forEach((a, i) => {
104
+ if (a.priority !== i) {
105
+ a.priority = i;
106
+ onSaveAuthor(a);
107
+ }
108
+ });
109
+ dispatchAuthors({
110
+ type: 'set',
111
+ state: copy,
112
+ });
113
+ };
114
+ const handleAddAuthor = () => {
115
+ const name = buildBibliographicName({ given: '', family: '' });
116
+ const author = {
117
+ id: generateID(ObjectTypes.Contributor),
118
+ role: 'author',
119
+ affiliations: [],
120
+ bibliographicName: name,
121
+ email: '',
122
+ isCorresponding: false,
123
+ ORCIDIdentifier: '',
124
+ priority: authors.length,
125
+ isJointContributor: false,
126
+ };
127
+ onSaveAuthor(author);
128
+ setSelection(author);
129
+ dispatchAuthors({
130
+ type: 'update',
131
+ items: [author],
132
+ });
133
+ };
134
+ const handleDeleteAuthor = () => {
135
+ if (!selection) {
136
+ return;
137
+ }
138
+ onDeleteAuthor(selection);
139
+ setSelection(undefined);
140
+ dispatchAuthors({
141
+ type: 'delete',
142
+ item: selection,
143
+ });
144
+ };
145
+ const handleSaveAffiliation = (affiliation) => {
146
+ onSaveAffiliation(affiliation);
147
+ dispatchAffiliations({
148
+ type: 'update',
149
+ items: [affiliation],
150
+ });
151
+ };
152
+ const handleAddAffiliation = (affiliation) => {
153
+ if (!valuesRef.current) {
154
+ return;
155
+ }
156
+ const values = valuesRef.current;
157
+ const affiliations = values.affiliations || [];
158
+ handleSaveAuthor(Object.assign(Object.assign({}, values), { affiliations: [...affiliations, affiliation.id] }));
159
+ };
160
+ const handleRemoveAffiliation = (affiliation) => {
161
+ var _a;
162
+ if (!valuesRef.current) {
163
+ return;
164
+ }
165
+ const values = valuesRef.current;
166
+ handleSaveAuthor(Object.assign(Object.assign({}, values), { affiliations: (_a = values.affiliations) === null || _a === void 0 ? void 0 : _a.filter((i) => i !== affiliation.id) }));
167
+ };
168
+ const handleResetAuthor = () => {
169
+ var _a;
170
+ (_a = actionsRef.current) === null || _a === void 0 ? void 0 : _a.reset();
171
+ setShowConfirmationDialog(false);
172
+ };
173
+ const handleChangeAuthor = (values) => {
174
+ valuesRef.current = values;
175
+ };
176
+ return (React.createElement(StyledModal, { isOpen: isOpen, onRequestClose: () => setOpen(false), shouldCloseOnOverlayClick: true },
177
+ React.createElement(ModalContainer, null,
178
+ React.createElement(ModalHeader, null,
179
+ React.createElement(CloseButton, { onClick: () => setOpen(false), "data-cy": 'modal-close-button' })),
180
+ React.createElement(ModalBody, null,
181
+ React.createElement(ModalSidebar, { "data-cy": 'authors-sidebar' },
182
+ React.createElement(ModalSidebarHeader, null,
183
+ React.createElement(ModalSidebarTitle, null, "Authors")),
184
+ React.createElement(SidebarContent, null,
185
+ React.createElement(AddAuthorButton, { onClick: handleAddAuthor },
186
+ React.createElement(AddIcon, { width: 40, height: 40 }),
187
+ React.createElement(ActionTitle, null, "Add Author")),
188
+ React.createElement(AuthorList, { author: selection, authors: authors, onSelect: handleSelect, moveAuthor: handleMoveAuthor }))),
189
+ React.createElement(ScrollableModalContent, null, selection ? (React.createElement(AuthorForms, null,
190
+ React.createElement(SaveAuthorConfirmationDialog, { isOpen: showConfirmationDialog, onSave: () => handleSaveAuthor(valuesRef.current), onCancel: handleResetAuthor }),
191
+ React.createElement(AuthorActions, { author: selection, onSave: () => handleSaveAuthor(valuesRef.current), onDelete: handleDeleteAuthor, onCancel: () => setOpen(false) }),
192
+ React.createElement(FormLabel, null, "Details"),
193
+ React.createElement(AuthorDetailsForm, { values: normalize(selection), onChange: handleChangeAuthor, onSave: handleSaveAuthor, actionsRef: actionsRef }),
194
+ React.createElement(FormLabel, null, "Affiliations"),
195
+ React.createElement(AuthorAffiliations, { author: selection, affiliations: affiliations, onSave: handleSaveAffiliation, onAdd: handleAddAffiliation, onRemove: handleRemoveAffiliation }))) : (React.createElement(AuthorFormPlaceholder, null)))))));
196
+ };
@@ -0,0 +1,10 @@
1
+ import { Form, useFormikContext } from 'formik';
2
+ import React, { useEffect } from 'react';
3
+ export const ChangeHandlingForm = (props) => {
4
+ const { values } = useFormikContext();
5
+ useEffect(() => {
6
+ var _a;
7
+ (_a = props.onChange) === null || _a === void 0 ? void 0 : _a.call(props, values);
8
+ }, [props.onChange, values]);
9
+ return React.createElement(Form, null, props.children);
10
+ };
@@ -0,0 +1,16 @@
1
+ import { Category, Dialog } from '@manuscripts/style-guide';
2
+ import React from 'react';
3
+ import { authorLabel } from '../../lib/authors';
4
+ export const DeleteAuthorConfirmationDialog = ({ isOpen, author, onDelete, onCancel }) => {
5
+ const label = authorLabel(author);
6
+ return (React.createElement(Dialog, { isOpen: isOpen, category: Category.confirmation, header: 'Remove author', message: `Are you sure you want to remove ${label} from the authors list?`, actions: {
7
+ secondary: {
8
+ action: onDelete,
9
+ title: 'Delete',
10
+ },
11
+ primary: {
12
+ action: onCancel,
13
+ title: 'Cancel',
14
+ },
15
+ } }));
16
+ };
@@ -0,0 +1,140 @@
1
+ /*!
2
+ * © 2019 Atypon Systems LLC
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ import CorrespondingAuthorBadge from '@manuscripts/assets/react/CorrespondingAuthorBadge';
17
+ import VerticalEllipsis from '@manuscripts/assets/react/VerticalEllipsis';
18
+ import { Avatar } from '@manuscripts/style-guide';
19
+ import React, { useRef, useState } from 'react';
20
+ import { useDrag, useDrop } from 'react-dnd';
21
+ import styled from 'styled-components';
22
+ import { authorLabel } from '../../lib/authors';
23
+ const AuthorContainer = styled.div `
24
+ padding: ${(props) => props.theme.grid.unit * 2}px;
25
+ display: flex;
26
+ justify-content: space-between;
27
+ align-items: center;
28
+ cursor: pointer;
29
+ transition: background-color 0.25s;
30
+ border: 1px solid transparent;
31
+ border-left: 0;
32
+ border-right: 0;
33
+
34
+ &:hover,
35
+ &.active {
36
+ background: ${(props) => props.theme.colors.background.fifth};
37
+ }
38
+
39
+ &.active {
40
+ border-color: ${(props) => props.theme.colors.border.primary};
41
+ }
42
+
43
+ &.dragging {
44
+ visibility: hidden;
45
+ }
46
+
47
+ &.drop-before {
48
+ border-top-color: ${(props) => props.theme.colors.brand.dark};
49
+ }
50
+
51
+ &.drop-after {
52
+ border-bottom-color: ${(props) => props.theme.colors.brand.dark};
53
+ }
54
+ `;
55
+ const AvatarContainer = styled.div `
56
+ display: inline-flex;
57
+ position: relative;
58
+ `;
59
+ const AuthorBadge = styled.div `
60
+ display: inline-flex;
61
+ align-items: center;
62
+ justify-content: center;
63
+ position: relative;
64
+ `;
65
+ const AuthorNotes = styled.div `
66
+ position: absolute;
67
+ top: 0;
68
+ right: 0;
69
+
70
+ & ${AuthorBadge}:not(:last-child) {
71
+ right: -20%;
72
+ }
73
+ `;
74
+ const AuthorName = styled.div `
75
+ margin-left: 12px;
76
+ flex: 1;
77
+ `;
78
+ const DragHandle = styled(VerticalEllipsis) `
79
+ cursor: move;
80
+ `;
81
+ const BEFORE = {
82
+ id: 'before',
83
+ diff: -0.5,
84
+ };
85
+ const AFTER = {
86
+ id: 'after',
87
+ diff: 0.5,
88
+ };
89
+ const getDropPosition = (element, monitor) => {
90
+ const rect = element.getBoundingClientRect();
91
+ const middle = (rect.bottom - rect.top) / 2;
92
+ const cursor = monitor.getClientOffset();
93
+ return cursor.y > middle + rect.top ? AFTER : BEFORE;
94
+ };
95
+ export const DraggableAuthor = ({ author, isSelected, onClick, moveAuthor, }) => {
96
+ const [dropPosition, setDropPosition] = useState();
97
+ const ref = useRef(null);
98
+ const [{ isDragging }, dragRef] = useDrag({
99
+ item: {
100
+ type: 'author',
101
+ author,
102
+ },
103
+ collect: (monitor) => ({
104
+ isDragging: monitor.isDragging(),
105
+ }),
106
+ });
107
+ const [{ isOver }, dropRef] = useDrop({
108
+ accept: 'author',
109
+ collect: (monitor) => ({
110
+ isOver: monitor.isOver(),
111
+ }),
112
+ hover: (item, monitor) => {
113
+ if (!ref.current) {
114
+ return;
115
+ }
116
+ const position = getDropPosition(ref.current, monitor);
117
+ setDropPosition(position);
118
+ },
119
+ drop: (item, monitor) => {
120
+ if (!ref.current) {
121
+ return;
122
+ }
123
+ const position = getDropPosition(ref.current, monitor);
124
+ const from = item.author.priority;
125
+ const to = author.priority;
126
+ moveAuthor(from, to + position.diff);
127
+ },
128
+ });
129
+ dragRef(dropRef(ref));
130
+ const dragClass = isDragging ? 'dragging' : '';
131
+ const dropClass = isOver && dropPosition ? `drop-${dropPosition.id}` : '';
132
+ const activeClass = isSelected ? 'active' : '';
133
+ return (React.createElement(AuthorContainer, { ref: ref, className: `${dragClass} ${dropClass} ${activeClass}`, onClick: onClick },
134
+ React.createElement(AvatarContainer, null,
135
+ React.createElement(Avatar, { size: 36, color: '#6e6e6e' }),
136
+ React.createElement(AuthorNotes, null, author.isCorresponding && (React.createElement(AuthorBadge, null,
137
+ React.createElement(CorrespondingAuthorBadge, null))))),
138
+ React.createElement(AuthorName, null, authorLabel(author)),
139
+ isSelected && React.createElement(DragHandle, null)));
140
+ };
@@ -0,0 +1,14 @@
1
+ import { Category, Dialog } from '@manuscripts/style-guide';
2
+ import React from 'react';
3
+ export const SaveAuthorConfirmationDialog = ({ isOpen, onSave, onCancel }) => {
4
+ return (React.createElement(Dialog, { isOpen: isOpen, category: Category.confirmation, header: "You've made changes to this option", message: "Would you like to save or discard your changes?", actions: {
5
+ secondary: {
6
+ action: onCancel,
7
+ title: 'Discard',
8
+ },
9
+ primary: {
10
+ action: onSave,
11
+ title: 'Save',
12
+ },
13
+ } }));
14
+ };
@@ -19,7 +19,7 @@ import bibliographyElement from '../views/bibliography_element_editable';
19
19
  import blockquoteElement from '../views/blockquote_element_editable';
20
20
  import bulletList from '../views/bullet_list_editable';
21
21
  import citation from '../views/citation_editable';
22
- import contributorsSection from '../views/contributors';
22
+ import contributors from '../views/contributors';
23
23
  import crossReference from '../views/cross_reference_editable';
24
24
  import empty from '../views/empty';
25
25
  import equation from '../views/equation_editable';
@@ -54,7 +54,7 @@ export default (props, dispatch) => {
54
54
  bullet_list: bulletList(props),
55
55
  citation: citation(props, dispatch),
56
56
  cross_reference: crossReference(props, dispatch),
57
- contributors: contributorsSection(props, dispatch),
57
+ contributors: contributors(props, dispatch),
58
58
  affiliations: affiliationsSection(props, dispatch),
59
59
  affiliation: affiliation(props, dispatch),
60
60
  equation: equation(props),
@@ -36,6 +36,8 @@ export const arrayReducer = (isEqual = (a, b) => a === b) => {
36
36
  return handleUpdate(state, action.items);
37
37
  case 'delete':
38
38
  return handleDelete(state, action.item);
39
+ case 'set':
40
+ return action.state;
39
41
  }
40
42
  };
41
43
  };