@manuscripts/body-editor 2.6.37 → 2.6.38-LEAN-4186.2

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 (39) hide show
  1. package/dist/cjs/commands.js +26 -4
  2. package/dist/cjs/components/authors/AuthorActions.js +25 -41
  3. package/dist/cjs/components/authors/AuthorDetailsForm.js +30 -11
  4. package/dist/cjs/components/authors/AuthorList.js +11 -2
  5. package/dist/cjs/components/authors/AuthorsModal.js +122 -17
  6. package/dist/cjs/components/authors/DeleteAuthorConfirmationDialog.js +17 -4
  7. package/dist/cjs/components/authors/DraggableAuthor.js +62 -15
  8. package/dist/cjs/components/authors/RequiredFieldConfirmationDialog.js +39 -0
  9. package/dist/cjs/components/authors/SaveAuthorConfirmationDialog.js +19 -1
  10. package/dist/cjs/menus.js +7 -1
  11. package/dist/cjs/plugins/cross-references.js +1 -1
  12. package/dist/cjs/versions.js +1 -1
  13. package/dist/cjs/views/contributors.js +42 -15
  14. package/dist/es/commands.js +24 -3
  15. package/dist/es/components/authors/AuthorActions.js +26 -19
  16. package/dist/es/components/authors/AuthorDetailsForm.js +30 -11
  17. package/dist/es/components/authors/AuthorList.js +11 -2
  18. package/dist/es/components/authors/AuthorsModal.js +123 -18
  19. package/dist/es/components/authors/DeleteAuthorConfirmationDialog.js +18 -5
  20. package/dist/es/components/authors/DraggableAuthor.js +64 -16
  21. package/dist/es/components/authors/RequiredFieldConfirmationDialog.js +32 -0
  22. package/dist/es/components/authors/SaveAuthorConfirmationDialog.js +20 -2
  23. package/dist/es/menus.js +8 -2
  24. package/dist/es/plugins/cross-references.js +1 -1
  25. package/dist/es/versions.js +1 -1
  26. package/dist/es/views/contributors.js +43 -16
  27. package/dist/types/commands.d.ts +1 -0
  28. package/dist/types/components/authors/AuthorActions.d.ts +4 -3
  29. package/dist/types/components/authors/AuthorDetailsForm.d.ts +1 -0
  30. package/dist/types/components/authors/AuthorList.d.ts +2 -0
  31. package/dist/types/components/authors/AuthorsModal.d.ts +1 -0
  32. package/dist/types/components/authors/DeleteAuthorConfirmationDialog.d.ts +0 -2
  33. package/dist/types/components/authors/DraggableAuthor.d.ts +2 -0
  34. package/dist/types/components/authors/RequiredFieldConfirmationDialog.d.ts +7 -0
  35. package/dist/types/versions.d.ts +1 -1
  36. package/dist/types/views/contributors.d.ts +3 -2
  37. package/package.json +2 -2
  38. package/styles/AdvancedEditor.css +31 -2
  39. package/styles/Editor.css +15 -0
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.RequiredFieldConfirmationDialog = void 0;
7
+ const style_guide_1 = require("@manuscripts/style-guide");
8
+ const react_1 = __importDefault(require("react"));
9
+ const styled_components_1 = __importDefault(require("styled-components"));
10
+ const StyledIcon = (0, styled_components_1.default)(style_guide_1.AttentionOrangeIcon) `
11
+ margin-right: 8px;
12
+ `;
13
+ const MessageBox = styled_components_1.default.div `
14
+ display: flex;
15
+ flex-direction: column;
16
+ justify-content: space-between;
17
+ min-height: 90px;
18
+ `;
19
+ const Header = () => (react_1.default.createElement(react_1.default.Fragment, null,
20
+ react_1.default.createElement(StyledIcon, null),
21
+ " Empty required field"));
22
+ const Message = () => {
23
+ return (react_1.default.createElement(MessageBox, null,
24
+ react_1.default.createElement("div", null, "A required filed has been left empty. If you discard, any updates will not be saved!"),
25
+ react_1.default.createElement("div", null, "Would you like to discard or continue editing?")));
26
+ };
27
+ const RequiredFieldConfirmationDialog = ({ isOpen, onSave, onCancel }) => {
28
+ return (react_1.default.createElement(style_guide_1.Dialog, { isOpen: isOpen, category: style_guide_1.Category.confirmation, header: react_1.default.createElement(Header, null), message: react_1.default.createElement(Message, null), actions: {
29
+ secondary: {
30
+ action: onCancel,
31
+ title: 'Discard',
32
+ },
33
+ primary: {
34
+ action: onSave,
35
+ title: 'Continue editing',
36
+ },
37
+ } }));
38
+ };
39
+ exports.RequiredFieldConfirmationDialog = RequiredFieldConfirmationDialog;
@@ -6,8 +6,26 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.SaveAuthorConfirmationDialog = void 0;
7
7
  const style_guide_1 = require("@manuscripts/style-guide");
8
8
  const react_1 = __importDefault(require("react"));
9
+ const styled_components_1 = __importDefault(require("styled-components"));
10
+ const StyledIcon = (0, styled_components_1.default)(style_guide_1.AttentionOrangeIcon) `
11
+ margin-right: 8px;
12
+ `;
13
+ const MessageBox = styled_components_1.default.div `
14
+ display: flex;
15
+ flex-direction: column;
16
+ justify-content: space-between;
17
+ min-height: 72px;
18
+ `;
19
+ const Header = () => (react_1.default.createElement(react_1.default.Fragment, null,
20
+ react_1.default.createElement(StyledIcon, null),
21
+ " Unsaved changes"));
22
+ const Message = () => {
23
+ return (react_1.default.createElement(MessageBox, null,
24
+ react_1.default.createElement("div", null, "You've made changes but not saved them!"),
25
+ react_1.default.createElement("div", null, "Would you like to save or discard your changes?")));
26
+ };
9
27
  const SaveAuthorConfirmationDialog = ({ isOpen, onSave, onCancel }) => {
10
- return (react_1.default.createElement(style_guide_1.Dialog, { isOpen: isOpen, category: style_guide_1.Category.confirmation, header: "You've made changes to this option", message: "Would you like to save or discard your changes?", actions: {
28
+ return (react_1.default.createElement(style_guide_1.Dialog, { isOpen: isOpen, category: style_guide_1.Category.confirmation, header: react_1.default.createElement(Header, null), message: react_1.default.createElement(Message, null), actions: {
11
29
  secondary: {
12
30
  action: onCancel,
13
31
  title: 'Discard',
package/dist/cjs/menus.js CHANGED
@@ -104,10 +104,16 @@ const getEditorMenus = (editor) => {
104
104
  },
105
105
  {
106
106
  id: 'insert-contributors',
107
- label: 'Authors & Affiliations',
107
+ label: 'Authors',
108
108
  isEnabled: isCommandValid(commands_1.insertContributors),
109
109
  run: doCommand(commands_1.insertContributors),
110
110
  },
111
+ {
112
+ id: 'insert-contributors',
113
+ label: 'Affiliations',
114
+ isEnabled: isCommandValid(commands_1.insertAffiliation),
115
+ run: doCommand(commands_1.insertAffiliation),
116
+ },
111
117
  {
112
118
  id: 'insert-keywords',
113
119
  label: 'Keywords',
@@ -23,7 +23,7 @@ const objects_1 = require("./objects");
23
23
  exports.default = () => {
24
24
  return new prosemirror_state_1.Plugin({
25
25
  state: {
26
- init(_, state) {
26
+ init() {
27
27
  return prosemirror_view_1.DecorationSet.empty;
28
28
  },
29
29
  apply(tr, oldDecorationSet, oldState, newState) {
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MATHJAX_VERSION = exports.VERSION = void 0;
4
- exports.VERSION = '2.6.37';
4
+ exports.VERSION = '2.6.38-LEAN-4186.2';
5
5
  exports.MATHJAX_VERSION = '3.2.2';
@@ -115,16 +115,25 @@ class ContributorsView extends block_view_1.default {
115
115
  this.dom = document.createElement('div');
116
116
  this.dom.classList.add('block-container', `block-${this.node.type.name}`);
117
117
  };
118
- this.createEditButton = () => {
118
+ this.authorContextMenu = () => {
119
119
  const can = this.props.getCapabilities();
120
- const button = (0, ReactSubView_1.default)(this.props, style_guide_1.SecondaryButton, {
121
- mini: true,
122
- onClick: () => this.handleEdit(''),
123
- className: 'edit-authors-button',
124
- disabled: !can.editMetadata,
125
- children: 'Edit Authors',
126
- }, this.node, this.getPos, this.view);
127
- this.container.appendChild(button);
120
+ const componentProps = {
121
+ actions: [],
122
+ };
123
+ if (can.editArticle) {
124
+ componentProps.actions.push({
125
+ label: 'New Author',
126
+ action: () => this.handleEdit('', true),
127
+ icon: 'AddOutline',
128
+ });
129
+ componentProps.actions.push({
130
+ label: 'Edit',
131
+ action: () => this.handleEdit(''),
132
+ icon: 'Edit',
133
+ });
134
+ }
135
+ this.contextMenu = (0, ReactSubView_1.default)(this.props, style_guide_1.ContextMenu, componentProps, this.node, this.getPos, this.view, 'context-menu');
136
+ return this.contextMenu;
128
137
  };
129
138
  this.createLegend = () => {
130
139
  const state = affiliations_1.affiliationsKey.getState(this.view.state);
@@ -184,7 +193,7 @@ class ContributorsView extends block_view_1.default {
184
193
  this.contextMenu = (0, ReactSubView_1.default)(this.props, style_guide_1.ContextMenu, componentProps, this.node, this.getPos, this.view, 'context-menu');
185
194
  this.props.popper.show(element, this.contextMenu, 'right-start');
186
195
  };
187
- this.handleEdit = (id) => {
196
+ this.handleEdit = (id, addNew) => {
188
197
  var _a;
189
198
  this.props.popper.destroy();
190
199
  const contributors = (0, view_1.findChildrenAttrsByType)(this.view, transform_1.schema.nodes.contributor);
@@ -197,6 +206,7 @@ class ContributorsView extends block_view_1.default {
197
206
  onSaveAuthor: this.handleSaveAuthor,
198
207
  onDeleteAuthor: this.handleDeleteAuthor,
199
208
  onSaveAffiliation: this.handleSaveAffiliation,
209
+ addNewAuthor: addNew,
200
210
  };
201
211
  (_a = this.popper) === null || _a === void 0 ? void 0 : _a.remove();
202
212
  this.popper = (0, ReactSubView_1.default)(this.props, AuthorsModal_1.AuthorsModal, componentProps, this.node, this.getPos, this.view);
@@ -226,11 +236,29 @@ class ContributorsView extends block_view_1.default {
226
236
  this.view.dispatch(tr.insert(parent.pos + 1, node));
227
237
  };
228
238
  this.insertAffiliationNode = (attrs) => {
229
- const parent = (0, view_1.findChildByType)(this.view, transform_1.schema.nodes.affiliations);
230
- const tr = this.view.state.tr;
231
- const node = transform_1.schema.nodes.affiliation.create(attrs);
232
- this.view.dispatch(tr.insert(parent.pos + 1, node));
239
+ const { view } = this;
240
+ const { dispatch } = view;
241
+ const affiliationsNodeType = transform_1.schema.nodes.affiliations;
242
+ const contributorsNodeType = transform_1.schema.nodes.contributors;
243
+ const affiliationNodeType = transform_1.schema.nodes.affiliation;
244
+ let affiliations = (0, view_1.findChildByType)(view, affiliationsNodeType);
245
+ if (!affiliations) {
246
+ const contributors = (0, view_1.findChildByType)(view, contributorsNodeType);
247
+ if (contributors) {
248
+ const { tr } = this.view.state;
249
+ const affiliationsNode = affiliationsNodeType.create();
250
+ const insertPos = contributors.pos + contributors.node.nodeSize;
251
+ dispatch(tr.insert(insertPos, affiliationsNode));
252
+ affiliations = (0, view_1.findChildByType)(view, affiliationsNodeType);
253
+ }
254
+ }
255
+ if (affiliations) {
256
+ const { tr } = this.view.state;
257
+ const affiliationNode = affiliationNodeType.create(attrs);
258
+ dispatch(tr.insert(affiliations.pos + 1, affiliationNode));
259
+ }
233
260
  };
261
+ this.actionGutterButtons = () => [this.authorContextMenu()];
234
262
  }
235
263
  updateContents() {
236
264
  const affs = affiliations_1.affiliationsKey.getState(this.view.state);
@@ -244,7 +272,6 @@ class ContributorsView extends block_view_1.default {
244
272
  this.version = affs.version;
245
273
  this.container.innerHTML = '';
246
274
  this.buildAuthors(affs);
247
- this.createEditButton();
248
275
  this.createLegend();
249
276
  this.updateSelection();
250
277
  }
@@ -587,9 +587,30 @@ export const insertContributors = (state, dispatch, view) => {
587
587
  const contributors = state.schema.nodes.contributors.create({
588
588
  id: '',
589
589
  });
590
- const affiliations = state.schema.nodes.affiliations.create({ id: '' });
591
- const fragment = Fragment.fromArray([contributors, affiliations]);
592
- const tr = state.tr.insert(pos, fragment);
590
+ const tr = state.tr.insert(pos, contributors);
591
+ if (dispatch) {
592
+ const selection = NodeSelection.create(tr.doc, pos);
593
+ if (view) {
594
+ view.focus();
595
+ }
596
+ dispatch(tr.setSelection(selection).scrollIntoView());
597
+ }
598
+ return true;
599
+ };
600
+ export const insertAffiliation = (state, dispatch, view) => {
601
+ if (getChildOfType(state.doc, schema.nodes.affiliations, true)) {
602
+ return false;
603
+ }
604
+ const title = findChildrenByType(state.doc, state.schema.nodes.title)[0];
605
+ let pos = title.pos + title.node.nodeSize;
606
+ const contributors = findChildrenByType(state.doc, state.schema.nodes.contributors)[0];
607
+ if (contributors) {
608
+ pos = contributors.pos + contributors.node.nodeSize;
609
+ }
610
+ const affiliations = state.schema.nodes.affiliations.create({
611
+ id: '',
612
+ });
613
+ const tr = state.tr.insert(pos, affiliations);
593
614
  if (dispatch) {
594
615
  const selection = NodeSelection.create(tr.doc, pos);
595
616
  if (view) {
@@ -1,31 +1,38 @@
1
- import { ButtonGroup, DeleteIcon, PrimaryButton, SecondaryButton, } from '@manuscripts/style-guide';
2
- import React, { useState } from 'react';
1
+ import { ButtonGroup, IconButton, PlusIcon } from '@manuscripts/style-guide';
2
+ import React from 'react';
3
3
  import styled from 'styled-components';
4
4
  import { DeleteAuthorConfirmationDialog } from './DeleteAuthorConfirmationDialog';
5
5
  const AuthorActionsContainer = styled.div `
6
6
  display: flex;
7
7
  `;
8
- const RemoveButton = styled.div `
9
- display: flex;
10
- align-items: center;
11
-
12
- svg {
13
- cursor: pointer;
14
- }
15
- `;
16
8
  const StyledButtonGroup = styled(ButtonGroup) `
17
9
  flex: 1;
18
10
  `;
19
- export const AuthorActions = ({ author, onSave, onDelete, onCancel, }) => {
20
- const [showDeleteDialog, setShowDeleteDialog] = useState(false);
11
+ const StyledIconButton = styled(IconButton) `
12
+ color: #0d79d0;
13
+ text-align: center;
14
+ font-size: 14px;
15
+ font-style: normal;
16
+ font-weight: 400;
17
+ line-height: 24px;
18
+ width: auto;
19
+ &:disabled {
20
+ color: #c9c9c9 !important;
21
+ background-color: unset !important;
22
+ border: unset;
23
+ }
24
+ svg {
25
+ margin-right: 4px;
26
+ }
27
+ `;
28
+ export const AuthorActions = ({ onSave, onDelete, showDeleteDialog, handleShowDeleteDialog, newAuthor, isDisableSave, }) => {
21
29
  return (React.createElement(AuthorActionsContainer, { "data-cy": "author-action" },
22
- React.createElement(DeleteAuthorConfirmationDialog, { author: author, isOpen: showDeleteDialog, onDelete: () => {
23
- setShowDeleteDialog(true);
30
+ React.createElement(DeleteAuthorConfirmationDialog, { isOpen: showDeleteDialog, onDelete: () => {
31
+ handleShowDeleteDialog();
24
32
  onDelete();
25
- }, onCancel: () => setShowDeleteDialog(false) }),
26
- React.createElement(RemoveButton, { onClick: () => setShowDeleteDialog(true) },
27
- React.createElement(DeleteIcon, null)),
33
+ }, onCancel: () => handleShowDeleteDialog() }),
28
34
  React.createElement(StyledButtonGroup, null,
29
- React.createElement(SecondaryButton, { onClick: onCancel }, "Cancel"),
30
- React.createElement(PrimaryButton, { onClick: onSave }, "Save"))));
35
+ React.createElement(StyledIconButton, { onClick: onSave, disabled: isDisableSave, type: "submit" },
36
+ React.createElement(PlusIcon, null),
37
+ newAuthor ? 'Save Details' : 'Update Details'))));
31
38
  };
@@ -24,6 +24,9 @@ export const LabelText = styled.div `
24
24
  ${(props) => props.theme.font.family.sans};
25
25
  letter-spacing: -0.2px;
26
26
  color: ${(props) => props.theme.colors.text.primary};
27
+ &::before {
28
+ margin-right: 8px !important;
29
+ }
27
30
  `;
28
31
  export const Fieldset = styled.fieldset `
29
32
  padding: 0;
@@ -33,7 +36,17 @@ export const Fieldset = styled.fieldset `
33
36
  const OrcidContainer = styled.div `
34
37
  margin: 16px 0 0;
35
38
  `;
36
- export const AuthorDetailsForm = ({ values, onChange, onSave, actionsRef, }) => {
39
+ const TextFieldWithError = styled(TextField) `
40
+ &:required::placeholder {
41
+ color: ${(props) => props.theme.colors.text.error};
42
+ }
43
+ `;
44
+ const CheckboxContainer = styled.div `
45
+ display: flex;
46
+ align-items: center;
47
+ gap: 32px;
48
+ `;
49
+ export const AuthorDetailsForm = ({ values, onChange, onSave, actionsRef, isEmailRequired, }) => {
37
50
  const formRef = useRef(null);
38
51
  if (actionsRef && !actionsRef.current) {
39
52
  actionsRef.current = {
@@ -43,22 +56,28 @@ export const AuthorDetailsForm = ({ values, onChange, onSave, actionsRef, }) =>
43
56
  },
44
57
  };
45
58
  }
46
- return (React.createElement(Formik, { initialValues: values, onSubmit: onSave, enableReinitialize: true, validateOnChange: false, innerRef: formRef }, (formik) => {
59
+ return (React.createElement(Formik, { initialValues: values, onSubmit: onSave, enableReinitialize: true, validateOnChange: true, innerRef: formRef }, (formik) => {
47
60
  const isAuthor = formik.values.role === 'author';
48
61
  return (React.createElement(ChangeHandlingForm, { onChange: onChange },
49
62
  React.createElement(Fieldset, null,
50
63
  React.createElement(TextFieldGroupContainer, null,
51
64
  React.createElement(Field, { name: 'bibliographicName.given' }, (props) => (React.createElement(TextField, Object.assign({ id: 'given-name', placeholder: 'Given name' }, props.field)))),
52
65
  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")),
66
+ React.createElement(Field, { name: 'email', type: 'email' }, (props) => {
67
+ const placeholder = isEmailRequired
68
+ ? '*Email address (required)'
69
+ : 'Email address';
70
+ return (React.createElement(TextFieldWithError, Object.assign({ required: isEmailRequired, id: 'email', placeholder: placeholder }, props.field)));
71
+ }),
72
+ React.createElement(CheckboxContainer, null,
73
+ React.createElement(CheckboxLabel, { disabled: !isAuthor },
74
+ React.createElement(Field, { name: 'isCorresponding' }, (props) => (React.createElement(CheckboxField, Object.assign({ id: 'isCorresponding', checked: props.field.value, disabled: !isAuthor }, props.field)))),
75
+ React.createElement(LabelText, null, "Corresponding Author")),
76
+ React.createElement(CheckboxLabel, null,
77
+ React.createElement(Field, { name: 'role', type: 'checkbox' }, (props) => (React.createElement(CheckboxField, { name: 'role', checked: isAuthor, onChange: (e) => {
78
+ formik.setFieldValue(props.field.name, e.target.checked ? 'author' : 'other', false);
79
+ } }))),
80
+ React.createElement(LabelText, null, "Include in Authors List"))),
62
81
  React.createElement(OrcidContainer, null,
63
82
  React.createElement(TextFieldLabel, null,
64
83
  React.createElement(LabelText, null, "ORCID"),
@@ -22,9 +22,18 @@ const AuthorListContainer = styled.div `
22
22
  flex: 1;
23
23
  overflow-y: visible;
24
24
  `;
25
- export const AuthorList = ({ author, authors, onSelect, moveAuthor, }) => {
25
+ const AuthorListTitle = styled.h2 `
26
+ color: #6e6e6e;
27
+ font-size: 18px;
28
+ font-weight: 400;
29
+ line-height: 24px;
30
+ margin-left: 14px;
31
+ margin-top: 28px;
32
+ `;
33
+ export const AuthorList = ({ author, authors, onSelect, onDelete, moveAuthor, lastSavedAuthor, }) => {
26
34
  return (React.createElement(DndProvider, { backend: HTML5Backend },
35
+ React.createElement(AuthorListTitle, null, "Existing Authors"),
27
36
  React.createElement(AuthorListContainer, { "data-cy": "authors-list" }, authors.map((a) => {
28
- 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 }));
37
+ return (React.createElement(DraggableAuthor, { key: a.id, author: a, isSelected: a.id === (author === null || author === void 0 ? void 0 : author.id), onClick: () => onSelect(a), onDelete: () => onDelete(), moveAuthor: moveAuthor, showSuccessIcon: lastSavedAuthor === a.id }));
29
38
  }))));
30
39
  };
@@ -16,7 +16,7 @@
16
16
  import { buildBibliographicName, generateID, ObjectTypes, } from '@manuscripts/json-schema';
17
17
  import { AddIcon, CloseButton, ModalBody, ModalContainer, ModalHeader, ModalSidebar, ModalSidebarHeader, ModalSidebarTitle, ScrollableModalContent, SidebarContent, StyledModal, } from '@manuscripts/style-guide';
18
18
  import { isEqual } from 'lodash';
19
- import React, { useReducer, useRef, useState } from 'react';
19
+ import React, { useEffect, useReducer, useRef, useState } from 'react';
20
20
  import styled from 'styled-components';
21
21
  import { arrayReducer } from '../../lib/array-reducer';
22
22
  import { authorComparator, } from '../../lib/authors';
@@ -25,12 +25,13 @@ import { AuthorAffiliations } from './AuthorAffiliations';
25
25
  import { AuthorDetailsForm } from './AuthorDetailsForm';
26
26
  import { AuthorFormPlaceholder } from './AuthorFormPlaceholder';
27
27
  import { AuthorList } from './AuthorList';
28
+ import { RequiredFieldConfirmationDialog } from './RequiredFieldConfirmationDialog';
28
29
  import { SaveAuthorConfirmationDialog } from './SaveAuthorConfirmationDialog';
29
30
  const AddAuthorButton = styled.div `
30
31
  display: flex;
31
32
  align-items: center;
32
33
  margin-bottom: ${(props) => props.theme.grid.unit * 4}px;
33
- margin-left: ${(props) => props.theme.grid.unit * 2}px;
34
+ margin-left: ${(props) => props.theme.grid.unit * 4}px;
34
35
  cursor: pointer;
35
36
  `;
36
37
  const ActionTitle = styled.div `
@@ -50,6 +51,9 @@ const AuthorForms = styled.div `
50
51
  padding-left: ${(props) => props.theme.grid.unit * 3}px;
51
52
  padding-right: ${(props) => props.theme.grid.unit * 3}px;
52
53
  `;
54
+ const StyledSidebarContent = styled(SidebarContent) `
55
+ padding: 0;
56
+ `;
53
57
  const authorsReducer = arrayReducer((a, b) => a.id === b.id);
54
58
  const affiliationsReducer = arrayReducer((a, b) => a.id === b.id);
55
59
  const normalize = (author) => ({
@@ -67,38 +71,97 @@ const normalize = (author) => ({
67
71
  footnote: author.footnote || [],
68
72
  corresp: author.corresp || [],
69
73
  });
70
- export const AuthorsModal = ({ authors: $authors, affiliations: $affiliations, author, onSaveAuthor, onDeleteAuthor, onSaveAffiliation, }) => {
74
+ export const AuthorsModal = ({ authors: $authors, affiliations: $affiliations, author, onSaveAuthor, onDeleteAuthor, onSaveAffiliation, addNewAuthor = false, }) => {
71
75
  const [isOpen, setOpen] = useState(true);
76
+ const [isDisableSave, setDisableSave] = useState(false);
77
+ const [isEmailRequired, setEmailRequired] = useState(false);
72
78
  const [showConfirmationDialog, setShowConfirmationDialog] = useState(false);
79
+ const [showRequiredFieldConfirmationDialog, setShowRequiredFieldConfirmationDialog,] = useState(false);
80
+ const [lastSavedAuthor, setLastSavedAuthor] = useState(null);
81
+ const [showDeleteDialog, setShowDeleteDialog] = useState(false);
82
+ const [newAuthor, setNewAuthor] = useState(false);
83
+ const [unSavedChanges, setUnSavedChanges] = useState(false);
84
+ const [nextAuthor, setNextAuthor] = useState(null);
73
85
  const valuesRef = useRef();
74
86
  const actionsRef = useRef();
75
87
  const [authors, dispatchAuthors] = useReducer(authorsReducer, $authors.sort(authorComparator));
76
88
  const [affiliations, dispatchAffiliations] = useReducer(affiliationsReducer, $affiliations);
89
+ useEffect(() => {
90
+ if (addNewAuthor) {
91
+ handleAddAuthor();
92
+ }
93
+ }, [addNewAuthor]);
77
94
  const [selection, setSelection] = useState(author);
78
95
  const handleSelect = (author) => {
79
96
  const values = valuesRef.current;
80
- if (values && selection && !isEqual(values, normalize(selection))) {
97
+ if (values &&
98
+ selection &&
99
+ !isEqual(values, normalize(selection)) &&
100
+ !isDisableSave) {
81
101
  setShowConfirmationDialog(true);
102
+ setNextAuthor(author);
103
+ }
104
+ else if (isDisableSave && unSavedChanges) {
105
+ setShowRequiredFieldConfirmationDialog(true);
106
+ setNextAuthor(author);
82
107
  }
83
108
  else {
84
109
  setSelection(author);
85
110
  }
86
111
  };
87
- const handleClose = () => {
112
+ useEffect(() => {
88
113
  const values = valuesRef.current;
89
114
  if (values && selection && !isEqual(values, normalize(selection))) {
115
+ setUnSavedChanges(true);
116
+ }
117
+ }, [valuesRef.current]);
118
+ const handleClose = () => {
119
+ if (isDisableSave && unSavedChanges) {
120
+ setShowRequiredFieldConfirmationDialog(true);
121
+ }
122
+ else if (unSavedChanges) {
90
123
  setShowConfirmationDialog(true);
91
124
  }
92
125
  else {
126
+ setShowRequiredFieldConfirmationDialog(false);
127
+ setLastSavedAuthor(null);
93
128
  setOpen(false);
94
129
  }
95
130
  };
131
+ const handleSave = () => {
132
+ if (valuesRef.current && selection) {
133
+ handleSaveAuthor(valuesRef.current);
134
+ }
135
+ if (nextAuthor) {
136
+ setSelection(nextAuthor);
137
+ setNextAuthor(null);
138
+ }
139
+ else if (newAuthor) {
140
+ createNewAuthor();
141
+ setNewAuthor(false);
142
+ }
143
+ setShowConfirmationDialog(false);
144
+ };
145
+ const handleCancel = () => {
146
+ handleResetAuthor();
147
+ if (nextAuthor) {
148
+ setSelection(nextAuthor);
149
+ setNextAuthor(null);
150
+ }
151
+ else if (newAuthor) {
152
+ createNewAuthor();
153
+ setNewAuthor(false);
154
+ }
155
+ setShowConfirmationDialog(false);
156
+ };
96
157
  const handleSaveAuthor = (values) => {
97
158
  if (!values || !selection) {
98
159
  return;
99
160
  }
100
161
  const author = Object.assign(Object.assign({}, selection), values);
101
162
  onSaveAuthor(author);
163
+ setLastSavedAuthor(author.id);
164
+ setUnSavedChanges(false);
102
165
  setSelection(author);
103
166
  setShowConfirmationDialog(false);
104
167
  dispatchAuthors({
@@ -121,7 +184,7 @@ export const AuthorsModal = ({ authors: $authors, affiliations: $affiliations, a
121
184
  state: copy,
122
185
  });
123
186
  };
124
- const handleAddAuthor = () => {
187
+ const createNewAuthor = () => {
125
188
  const name = buildBibliographicName({ given: '', family: '' });
126
189
  const author = {
127
190
  id: generateID(ObjectTypes.Contributor),
@@ -138,12 +201,18 @@ export const AuthorsModal = ({ authors: $authors, affiliations: $affiliations, a
138
201
  corresp: [],
139
202
  footnote: [],
140
203
  };
141
- onSaveAuthor(author);
142
204
  setSelection(author);
143
- dispatchAuthors({
144
- type: 'update',
145
- items: [author],
146
- });
205
+ };
206
+ const handleAddAuthor = () => {
207
+ const values = valuesRef.current;
208
+ if (values && selection && !isEqual(values, normalize(selection))) {
209
+ setShowConfirmationDialog(true);
210
+ setNextAuthor(null);
211
+ setNewAuthor(true);
212
+ }
213
+ else {
214
+ createNewAuthor();
215
+ }
147
216
  };
148
217
  const handleDeleteAuthor = () => {
149
218
  if (!selection) {
@@ -183,9 +252,44 @@ export const AuthorsModal = ({ authors: $authors, affiliations: $affiliations, a
183
252
  var _a;
184
253
  (_a = actionsRef.current) === null || _a === void 0 ? void 0 : _a.reset();
185
254
  setShowConfirmationDialog(false);
255
+ setShowRequiredFieldConfirmationDialog(false);
256
+ setUnSavedChanges(false);
257
+ if (!newAuthor && !nextAuthor) {
258
+ setLastSavedAuthor(null);
259
+ setOpen(false);
260
+ }
261
+ else {
262
+ setNewAuthor(false);
263
+ createNewAuthor();
264
+ }
186
265
  };
187
266
  const handleChangeAuthor = (values) => {
267
+ const isUnchanged = isEqual(normalize(selection), values);
188
268
  valuesRef.current = values;
269
+ const { given, family } = values.bibliographicName;
270
+ const { email, isCorresponding } = values;
271
+ const isNameFilled = (given === null || given === void 0 ? void 0 : given.length) && (family === null || family === void 0 ? void 0 : family.length);
272
+ if (isNameFilled && !isCorresponding && !isUnchanged) {
273
+ setDisableSave(false);
274
+ }
275
+ else if (isCorresponding &&
276
+ (email === null || email === void 0 ? void 0 : email.length) &&
277
+ isNameFilled &&
278
+ !isUnchanged) {
279
+ setDisableSave(false);
280
+ }
281
+ else {
282
+ setDisableSave(true);
283
+ }
284
+ if (isCorresponding && !(email === null || email === void 0 ? void 0 : email.length)) {
285
+ setEmailRequired(true);
286
+ }
287
+ else {
288
+ setEmailRequired(false);
289
+ }
290
+ };
291
+ const handleShowDeleteDialog = () => {
292
+ setShowDeleteDialog((prev) => !prev);
189
293
  };
190
294
  return (React.createElement(StyledModal, { isOpen: isOpen, onRequestClose: () => handleClose(), shouldCloseOnOverlayClick: true },
191
295
  React.createElement(ModalContainer, null,
@@ -195,16 +299,17 @@ export const AuthorsModal = ({ authors: $authors, affiliations: $affiliations, a
195
299
  React.createElement(ModalSidebar, { "data-cy": "authors-sidebar" },
196
300
  React.createElement(ModalSidebarHeader, null,
197
301
  React.createElement(ModalSidebarTitle, null, "Authors")),
198
- React.createElement(SidebarContent, null,
302
+ React.createElement(StyledSidebarContent, null,
199
303
  React.createElement(AddAuthorButton, { "data-cy": "add-author-button", onClick: handleAddAuthor },
200
- React.createElement(AddIcon, { width: 40, height: 40 }),
201
- React.createElement(ActionTitle, null, "Add Author")),
202
- React.createElement(AuthorList, { author: selection, authors: authors, onSelect: handleSelect, moveAuthor: handleMoveAuthor }))),
304
+ React.createElement(AddIcon, { width: 18, height: 18 }),
305
+ React.createElement(ActionTitle, null, "New Author")),
306
+ React.createElement(AuthorList, { author: selection, authors: authors, onSelect: handleSelect, onDelete: handleShowDeleteDialog, moveAuthor: handleMoveAuthor, lastSavedAuthor: lastSavedAuthor }))),
203
307
  React.createElement(ScrollableModalContent, { "data-cy": "author-modal-content" }, selection ? (React.createElement(AuthorForms, null,
204
- React.createElement(SaveAuthorConfirmationDialog, { isOpen: showConfirmationDialog, onSave: () => handleSaveAuthor(valuesRef.current), onCancel: handleResetAuthor }),
205
- React.createElement(AuthorActions, { author: selection, onSave: () => handleSaveAuthor(valuesRef.current), onDelete: handleDeleteAuthor, onCancel: () => setOpen(false) }),
308
+ React.createElement(RequiredFieldConfirmationDialog, { isOpen: showRequiredFieldConfirmationDialog, onSave: () => setShowRequiredFieldConfirmationDialog(false), onCancel: handleCancel }),
309
+ React.createElement(SaveAuthorConfirmationDialog, { isOpen: showConfirmationDialog, onSave: handleSave, onCancel: handleCancel }),
310
+ React.createElement(AuthorActions, { onSave: () => handleSaveAuthor(valuesRef.current), onDelete: handleDeleteAuthor, showDeleteDialog: showDeleteDialog, handleShowDeleteDialog: handleShowDeleteDialog, newAuthor: newAuthor, isDisableSave: isDisableSave }),
206
311
  React.createElement(FormLabel, null, "Details"),
207
- React.createElement(AuthorDetailsForm, { values: normalize(selection), onChange: handleChangeAuthor, onSave: handleSaveAuthor, actionsRef: actionsRef }),
312
+ React.createElement(AuthorDetailsForm, { values: normalize(selection), onChange: handleChangeAuthor, onSave: handleSaveAuthor, actionsRef: actionsRef, isEmailRequired: isEmailRequired }),
208
313
  React.createElement(FormLabel, null, "Affiliations"),
209
314
  React.createElement(AuthorAffiliations, { author: selection, affiliations: affiliations, onSave: handleSaveAffiliation, onAdd: handleAddAffiliation, onRemove: handleRemoveAffiliation }))) : (React.createElement(AuthorFormPlaceholder, null)))))));
210
315
  };
@@ -1,9 +1,22 @@
1
- import { Category, Dialog } from '@manuscripts/style-guide';
1
+ import { AttentionOrangeIcon, Category, Dialog } from '@manuscripts/style-guide';
2
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: {
3
+ import styled from 'styled-components';
4
+ const StyledIcon = styled(AttentionOrangeIcon) `
5
+ margin-right: 8px;
6
+ `;
7
+ const P = styled.p `
8
+ margin-bottom: 4px;
9
+ `;
10
+ const Header = () => (React.createElement(React.Fragment, null,
11
+ React.createElement(StyledIcon, null),
12
+ " Delete author"));
13
+ const Message = () => {
14
+ return (React.createElement(React.Fragment, null,
15
+ React.createElement("p", null, "Are you sure you want to remove this author from the authors list?"),
16
+ React.createElement(P, null, "Do you want to continue?")));
17
+ };
18
+ export const DeleteAuthorConfirmationDialog = ({ isOpen, onDelete, onCancel }) => {
19
+ return (React.createElement(Dialog, { isOpen: isOpen, category: Category.confirmation, header: React.createElement(Header, null), message: React.createElement(Message, null), actions: {
7
20
  secondary: {
8
21
  action: onDelete,
9
22
  title: 'Delete',