@manuscripts/body-editor 3.12.17 → 3.12.20

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 (57) hide show
  1. package/dist/cjs/commands.js +16 -24
  2. package/dist/cjs/components/affiliations/AffiliationForm.js +5 -6
  3. package/dist/cjs/components/affiliations/AffiliationsModal.js +21 -57
  4. package/dist/cjs/components/affiliations/AffiliationsPanel.js +22 -0
  5. package/dist/cjs/components/authors/AuthorDetailsForm.js +3 -4
  6. package/dist/cjs/components/authors/AuthorsModal.js +15 -48
  7. package/dist/cjs/components/authors/AuthorsPanel.js +18 -0
  8. package/dist/cjs/components/authors/useManageAffiliations.js +6 -4
  9. package/dist/cjs/components/authors-affiliations/AuthorsAndAffiliationsModals.js +134 -0
  10. package/dist/cjs/components/authors-affiliations/GenericPanel.js +136 -0
  11. package/dist/cjs/components/form/ModalFormActions.js +11 -5
  12. package/dist/cjs/keys/title.js +24 -10
  13. package/dist/cjs/lib/__tests__/plugins.test.js +2 -2
  14. package/dist/cjs/lib/context-menu.js +8 -9
  15. package/dist/cjs/plugins/objects.js +24 -19
  16. package/dist/cjs/versions.js +1 -1
  17. package/dist/cjs/views/affiliations.js +5 -11
  18. package/dist/cjs/views/caption.js +1 -1
  19. package/dist/cjs/views/contributors.js +4 -7
  20. package/dist/cjs/views/figure.js +0 -1
  21. package/dist/es/commands.js +16 -24
  22. package/dist/es/components/affiliations/AffiliationForm.js +5 -6
  23. package/dist/es/components/affiliations/AffiliationsModal.js +21 -53
  24. package/dist/es/components/affiliations/AffiliationsPanel.js +15 -0
  25. package/dist/es/components/authors/AuthorDetailsForm.js +3 -4
  26. package/dist/es/components/authors/AuthorsModal.js +15 -45
  27. package/dist/es/components/authors/AuthorsPanel.js +11 -0
  28. package/dist/es/components/authors/useManageAffiliations.js +7 -5
  29. package/dist/es/components/authors-affiliations/AuthorsAndAffiliationsModals.js +93 -0
  30. package/dist/es/components/authors-affiliations/GenericPanel.js +94 -0
  31. package/dist/es/components/form/ModalFormActions.js +11 -5
  32. package/dist/es/keys/title.js +24 -10
  33. package/dist/es/lib/__tests__/plugins.test.js +2 -2
  34. package/dist/es/lib/context-menu.js +8 -9
  35. package/dist/es/plugins/objects.js +25 -20
  36. package/dist/es/versions.js +1 -1
  37. package/dist/es/views/affiliations.js +5 -11
  38. package/dist/es/views/caption.js +1 -1
  39. package/dist/es/views/contributors.js +5 -8
  40. package/dist/es/views/figure.js +0 -1
  41. package/dist/types/components/affiliations/AffiliationForm.d.ts +1 -0
  42. package/dist/types/components/affiliations/AffiliationsModal.d.ts +2 -5
  43. package/dist/types/components/affiliations/AffiliationsPanel.d.ts +11 -0
  44. package/dist/types/components/authors/AuthorDetailsForm.d.ts +1 -0
  45. package/dist/types/components/authors/AuthorsModal.d.ts +2 -4
  46. package/dist/types/components/authors/AuthorsPanel.d.ts +13 -0
  47. package/dist/types/components/authors/useManageAffiliations.d.ts +0 -2
  48. package/dist/types/components/authors-affiliations/AuthorsAndAffiliationsModals.d.ts +29 -0
  49. package/dist/types/components/authors-affiliations/GenericPanel.d.ts +48 -0
  50. package/dist/types/components/form/ModalFormActions.d.ts +1 -0
  51. package/dist/types/versions.d.ts +1 -1
  52. package/package.json +3 -3
  53. package/styles/AdvancedEditor.css +18 -48
  54. package/styles/Editor.css +28 -63
  55. package/dist/cjs/components/authors/AffiliationDrawer.js +0 -24
  56. package/dist/es/components/authors/AffiliationDrawer.js +0 -17
  57. package/dist/types/components/authors/AffiliationDrawer.d.ts +0 -12
@@ -33,8 +33,7 @@ import { getEditorProps } from './plugins/editor-props';
33
33
  import { searchReplaceKey } from './plugins/search-replace';
34
34
  import { checkForCompletion } from './plugins/section_title/autocompletion';
35
35
  import { persistentCursor } from './plugins/persistent-cursor';
36
- import { openAffiliationsModal } from './components/affiliations/AffiliationsModal';
37
- import { openAuthorsModal } from './components/authors/AuthorsModal';
36
+ import { openAuthorsAndAffiliationsModals } from './components/authors-affiliations/AuthorsAndAffiliationsModals';
38
37
  export const addToStart = (state, dispatch) => {
39
38
  const { selection } = state;
40
39
  const props = getEditorProps(state);
@@ -176,7 +175,7 @@ export const createBlock = (nodeType, position, state, dispatch, attrs) => {
176
175
  case schema.nodes.listing_element:
177
176
  node = schema.nodes.listing_element.create({}, [
178
177
  schema.nodes.listing.create(),
179
- createAndFillFigcaptionElement(),
178
+ ...createAndFillCaption(),
180
179
  ]);
181
180
  break;
182
181
  case schema.nodes.equation_element:
@@ -244,10 +243,7 @@ export const insertFigure = (file, state, dispatch) => {
244
243
  });
245
244
  const element = state.schema.nodes.figure_element.createAndFill({}, [
246
245
  figure,
247
- state.schema.nodes.figcaption.create({}, [
248
- state.schema.nodes.caption_title.create(),
249
- state.schema.nodes.caption.create(),
250
- ]),
246
+ schema.nodes.caption.create(undefined, schema.nodes.text_block.create()),
251
247
  ]);
252
248
  const tr = state.tr.insert(position, element);
253
249
  dispatch(tr);
@@ -262,7 +258,7 @@ export const insertEmbed = (state, dispatch, attrs) => {
262
258
  ...attrs,
263
259
  id: generateNodeID(schema.nodes.embed),
264
260
  }, [
265
- createAndFillFigcaptionElement(),
261
+ ...createAndFillCaption(),
266
262
  schema.nodes.alt_text.create(),
267
263
  schema.nodes.long_desc.create(),
268
264
  ]);
@@ -286,12 +282,7 @@ export const insertSupplement = (file, view) => {
286
282
  const supplement = schema.nodes.supplement.createAndFill({
287
283
  id: generateNodeID(schema.nodes.supplement),
288
284
  href: file.id,
289
- }, [
290
- schema.nodes.figcaption.create({}, [
291
- schema.nodes.caption_title.create(),
292
- schema.nodes.caption.create(),
293
- ]),
294
- ]);
285
+ }, createAndFillCaption());
295
286
  const tr = view.state.tr;
296
287
  const { pos } = upsertSupplementsSection(tr, supplement);
297
288
  tr.setSelection(NodeSelection.create(tr.doc, pos));
@@ -560,7 +551,7 @@ export const insertBoxElement = (state, dispatch) => {
560
551
  paragraph,
561
552
  ]);
562
553
  const node = nodes.box_element.createAndFill({}, [
563
- nodes.figcaption.create({}, [nodes.caption_title.create()]),
554
+ nodes.caption_title.create(),
564
555
  section,
565
556
  ]);
566
557
  if (position && dispatch) {
@@ -713,7 +704,7 @@ export const insertContributors = (state, dispatch, view) => {
713
704
  if (dispatch) {
714
705
  const selection = NodeSelection.create(tr.doc, contributors.pos);
715
706
  dispatch(tr.setSelection(selection).scrollIntoView());
716
- openAuthorsModal(contributors.pos, view);
707
+ openAuthorsAndAffiliationsModals(contributors.pos, view, 'authors');
717
708
  }
718
709
  return true;
719
710
  };
@@ -734,7 +725,7 @@ export const insertAffiliation = (state, dispatch, view) => {
734
725
  if (dispatch) {
735
726
  const selection = NodeSelection.create(tr.doc, affiliations.pos);
736
727
  dispatch(tr.setSelection(selection).scrollIntoView());
737
- openAffiliationsModal(affiliations.pos, view);
728
+ openAuthorsAndAffiliationsModals(affiliations.pos, view, 'affiliations');
738
729
  }
739
730
  return true;
740
731
  };
@@ -1073,7 +1064,7 @@ export const createAndFillTableElement = (attrs, config = DEFAULT_TABLE_CONFIG)
1073
1064
  ...attrs,
1074
1065
  id: generateNodeID(schema.nodes.table_element),
1075
1066
  }, [
1076
- createAndFillFigcaptionElement(),
1067
+ schema.nodes.caption_title.create(),
1077
1068
  schema.nodes.table.create({}, tableRows),
1078
1069
  schema.nodes.alt_text.create(),
1079
1070
  schema.nodes.long_desc.create(),
@@ -1084,21 +1075,22 @@ const createAndFillFigureElement = (attrs) => schema.nodes.figure_element.create
1084
1075
  ...attrs,
1085
1076
  id: generateNodeID(schema.nodes.figure_element),
1086
1077
  }, [
1087
- schema.nodes.figure.create({}, [schema.nodes.figcaption.create()]),
1088
- createAndFillFigcaptionElement(),
1078
+ schema.nodes.figure.create(),
1079
+ schema.nodes.caption.create(undefined, schema.nodes.text_block.create()),
1089
1080
  schema.nodes.alt_text.create(),
1090
1081
  schema.nodes.long_desc.create(),
1091
1082
  schema.nodes.listing.create(),
1092
1083
  ]);
1093
- const createAndFillFigcaptionElement = () => schema.nodes.figcaption.create({}, [
1084
+ const createAndFillCaption = () => [
1094
1085
  schema.nodes.caption_title.create(),
1095
- schema.nodes.caption.create(),
1096
- ]);
1086
+ schema.nodes.caption.create(undefined, schema.nodes.text_block.create()),
1087
+ ];
1097
1088
  const createImageElement = (attrs) => schema.nodes.image_element.create({
1098
1089
  ...attrs,
1099
1090
  id: generateNodeID(schema.nodes.image_element),
1100
1091
  }, [
1101
1092
  schema.nodes.figure.create(),
1093
+ schema.nodes.caption.create(undefined, schema.nodes.text_block.create()),
1102
1094
  schema.nodes.alt_text.create(),
1103
1095
  schema.nodes.long_desc.create(),
1104
1096
  ]);
@@ -1106,7 +1098,7 @@ const createEmbedElement = (attrs) => schema.nodes.embed.create({
1106
1098
  ...attrs,
1107
1099
  id: generateNodeID(schema.nodes.embed),
1108
1100
  }, [
1109
- createAndFillFigcaptionElement(),
1101
+ ...createAndFillCaption(),
1110
1102
  schema.nodes.alt_text.create(),
1111
1103
  schema.nodes.long_desc.create(),
1112
1104
  ]);
@@ -3,14 +3,13 @@ import { Field, Formik } from 'formik';
3
3
  import React, { useRef } from 'react';
4
4
  import { ChangeHandlingForm } from '../ChangeHandlingForm';
5
5
  export const AffiliationForm = ({ values, onSave, onChange, actionsRef, }) => {
6
- if (actionsRef && !actionsRef.current) {
6
+ const formRef = useRef(null);
7
+ if (actionsRef) {
7
8
  actionsRef.current = {
8
- reset: () => {
9
- formRef.current?.resetForm();
10
- },
9
+ reset: () => formRef.current?.resetForm(),
10
+ submitForm: () => formRef.current?.submitForm(),
11
11
  };
12
12
  }
13
- const formRef = useRef(null);
14
13
  const validateAffiliation = (values) => {
15
14
  const errors = {};
16
15
  if (!values.institution?.trim()) {
@@ -18,7 +17,7 @@ export const AffiliationForm = ({ values, onSave, onChange, actionsRef, }) => {
18
17
  }
19
18
  return errors;
20
19
  };
21
- return (React.createElement(Formik, { initialValues: values, onSubmit: onSave, innerRef: formRef, enableReinitialize: true, validate: validateAffiliation }, (formik) => (React.createElement(ChangeHandlingForm, { onChange: onChange, id: "affiliation-form", noValidate: true },
20
+ return (React.createElement(Formik, { initialValues: values, onSubmit: (attrs) => onSave(attrs), innerRef: formRef, enableReinitialize: true, validate: validateAffiliation }, (formik) => (React.createElement(ChangeHandlingForm, { onChange: onChange, id: "affiliation-form", noValidate: true },
22
21
  React.createElement(FormRow, null,
23
22
  React.createElement(Field, { name: "institution" }, (props) => {
24
23
  const hasError = formik.touched.institution && formik.errors.institution;
@@ -1,4 +1,4 @@
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';
1
+ import { AddIcon, 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';
@@ -11,37 +11,45 @@ import { ConfirmationDialog, DialogType } from '../dialog/ConfirmationDialog';
11
11
  import FormFooter from '../form/FormFooter';
12
12
  import { FormPlaceholder } from '../form/FormPlaceholder';
13
13
  import { ModalFormActions } from '../form/ModalFormActions';
14
- import { GenericDrawer } from '../modal-drawer/GenericDrawer';
15
- import { DrawerGroup } from '../modal-drawer/GenericDrawerGroup';
14
+ import { AuthorsPanel } from '../authors/AuthorsPanel';
16
15
  import { AffiliationForm } from './AffiliationForm';
17
16
  import { AffiliationList } from './AffiliationList';
18
- import { getEditorProps } from '../../plugins/editor-props';
19
- import ReactSubView from '../../views/ReactSubView';
20
- import { deleteNode, findChildrenAttrsByType, updateNodeAttrs, } from '../../lib/view';
21
17
  function makeAuthorItems(authors) {
22
18
  return authors.map((author) => ({
23
19
  id: author.id,
24
20
  label: `${author.given} ${author.family}`,
25
21
  }));
26
22
  }
27
- export const AffiliationsModal = ({ authors: $authors, affiliations: $affiliations, affiliation, onSaveAffiliation, onDeleteAffiliation, onUpdateAuthors, addNewAffiliation = false, }) => {
23
+ export const AffiliationsModal = ({ authors: $authors, affiliations: $affiliations, affiliation, onSaveAffiliation, onDeleteAffiliation, onUpdateAuthors, addNewAffiliation = false, onClose, onOpenAuthorsModal, }) => {
28
24
  const [isOpen, setIsOpen] = useState(true);
29
25
  const [selection, setSelection] = useState(affiliation);
30
26
  const [showingDeleteDialog, setShowDeleteDialog] = useState(false);
31
27
  const valuesRef = useRef(undefined);
32
28
  const actionsRef = useRef(undefined);
33
29
  const [authors, dispatchAuthors] = useReducer(authorsReducer, $authors.sort(authorComparator));
30
+ useEffect(() => {
31
+ dispatchAuthors({
32
+ type: 'set',
33
+ state: [...$authors].sort(authorComparator),
34
+ });
35
+ }, [$authors]);
34
36
  const [affiliations, dispatchAffiliations] = useReducer(affiliationsReducer, $affiliations);
35
37
  const [isDisableSave, setIsDisableSave] = useState(true);
36
38
  const [newAffiliation, setNewAffiliation] = useState(false);
37
39
  const [showRequiredFieldConfirmationDialog, setShowRequiredFieldConfirmationDialog,] = useState(false);
38
40
  const [showConfirmationDialog, setShowConfirmationDialog] = useState(false);
39
- const [showAuthorDrawer, setShowAuthorDrawer] = useState(false);
40
41
  const [selectedAuthorIds, setSelectedAuthorIds] = useState([]);
41
42
  const [pendingSelection, setPendingSelection] = useState(null);
42
43
  const [pendingAction, setPendingAction] = useState(null);
43
44
  const [savedAffiliationId, setSavedAffiliationId] = useState(undefined);
44
45
  const [affiliationAuthorMap, setAffiliationAuthorMap] = useState(new Map());
46
+ const prevIsOpenRef = useRef(isOpen);
47
+ useEffect(() => {
48
+ if (prevIsOpenRef.current && !isOpen) {
49
+ onClose?.();
50
+ }
51
+ prevIsOpenRef.current = isOpen;
52
+ }, [isOpen, onClose]);
45
53
  useEffect(() => {
46
54
  if (!selection) {
47
55
  return;
@@ -106,7 +114,6 @@ export const AffiliationsModal = ({ authors: $authors, affiliations: $affiliatio
106
114
  setNewAffiliation(false);
107
115
  setSelection(affiliation);
108
116
  setSelectedAuthorIds(affiliatedAuthorIds);
109
- setShowAuthorDrawer(false);
110
117
  setAffiliationAuthorMap((prevMap) => {
111
118
  const newMap = new Map(prevMap);
112
119
  newMap.set(affiliation.id, affiliatedAuthorIds);
@@ -146,7 +153,6 @@ export const AffiliationsModal = ({ authors: $authors, affiliations: $affiliatio
146
153
  newMap.set(affiliation.id, selectedAuthorIds);
147
154
  return newMap;
148
155
  });
149
- setShowAuthorDrawer(false);
150
156
  setSavedAffiliationId(affiliation.id);
151
157
  setTimeout(() => {
152
158
  setSavedAffiliationId(undefined);
@@ -240,7 +246,6 @@ export const AffiliationsModal = ({ authors: $authors, affiliations: $affiliatio
240
246
  setNewAffiliation(true);
241
247
  setSelection(emptyAffiliation);
242
248
  setSelectedAuthorIds([]);
243
- setShowAuthorDrawer(false);
244
249
  };
245
250
  useEffect(() => {
246
251
  if (addNewAffiliation) {
@@ -288,7 +293,6 @@ export const AffiliationsModal = ({ authors: $authors, affiliations: $affiliatio
288
293
  const handleConfirmationCancel = () => {
289
294
  setShowConfirmationDialog(false);
290
295
  setShowRequiredFieldConfirmationDialog(false);
291
- setShowAuthorDrawer(false);
292
296
  if (pendingAction === 'select' && pendingSelection) {
293
297
  setSelection(pendingSelection);
294
298
  setNewAffiliation(false);
@@ -329,19 +333,17 @@ export const AffiliationsModal = ({ authors: $authors, affiliations: $affiliatio
329
333
  React.createElement(AffiliationList, { affiliation: selection, affiliations: affiliations, onSelect: handleSelect, onDelete: handleShowDeleteDialog, lastSavedAffiliationId: savedAffiliationId }))),
330
334
  React.createElement(ScrollableModalContent, { "data-cy": "affiliations-modal-content" }, selection ? (React.createElement(React.Fragment, null,
331
335
  React.createElement(AffiliationTabs, null,
332
- React.createElement(ModalFormActions, { type: 'affiliation', form: 'affiliation-form', onDelete: handleDeleteAffiliation, showingDeleteDialog: showingDeleteDialog &&
336
+ React.createElement(ModalFormActions, { type: 'affiliation', form: 'affiliation-form', onSubmitForm: () => actionsRef.current?.submitForm?.(), onDelete: handleDeleteAffiliation, showingDeleteDialog: showingDeleteDialog &&
333
337
  !(showConfirmationDialog ||
334
338
  showRequiredFieldConfirmationDialog), showDeleteDialog: handleShowDeleteDialog, newEntity: newAffiliation, isDisableSave: isDisableSave }),
335
339
  React.createElement(InspectorTabList, null,
336
340
  React.createElement(InspectorTab, null, "Details"),
337
- React.createElement(InspectorTab, null, "Authors")),
341
+ onOpenAuthorsModal && React.createElement(InspectorTab, null, "Authors")),
338
342
  React.createElement(InspectorTabPanels, null,
339
343
  React.createElement(AffiliationTabPanel, null,
340
- React.createElement(AffiliationForm, { values: checkID(selection, 'affiliation'), onSave: () => handleSaveAffiliation(valuesRef.current), onChange: handleAffiliationChange, actionsRef: actionsRef })),
341
- React.createElement(AffiliationTabPanel, null,
342
- React.createElement(DrawerGroup, { Drawer: GenericDrawer, removeItem: (id) => {
343
- setSelectedAuthorIds((prev) => prev.filter((authorId) => authorId !== id));
344
- }, selectedItems: selectedAuthors, onSelect: selectAuthor, items: makeAuthorItems(authors), showDrawer: showAuthorDrawer, setShowDrawer: setShowAuthorDrawer, title: "Authors", cy: "affiliations", labelField: "label", buttonText: "Affiliate Authors", Icon: React.createElement(AddUserIcon, { width: 16, height: 16 }) })))),
344
+ React.createElement(AffiliationForm, { values: checkID(selection, 'affiliation'), onSave: (attrs) => handleSaveAffiliation(attrs), onChange: handleAffiliationChange, actionsRef: actionsRef })),
345
+ onOpenAuthorsModal && (React.createElement(AffiliationTabPanel, null,
346
+ React.createElement(AuthorsPanel, { items: makeAuthorItems(authors), selectedItems: selectedAuthors, onSelect: selectAuthor, onOpenAuthorsModal: onOpenAuthorsModal }))))),
345
347
  React.createElement(ConfirmationDialog, { isOpen: showRequiredFieldConfirmationDialog, onPrimary: () => setShowRequiredFieldConfirmationDialog(false), onSecondary: handleConfirmationCancel, type: DialogType.REQUIRED, entityType: "affiliation" }),
346
348
  React.createElement(ConfirmationDialog, { isOpen: showConfirmationDialog, onPrimary: handleConfirmationSave, onSecondary: handleConfirmationCancel, type: DialogType.SAVE, entityType: "affiliation" }))) : (React.createElement(FormPlaceholder, { type: "affiliation", title: "Affiliation Details", message: "Select an affiliation from the list to display it's details here.", placeholderIcon: React.createElement(AffiliationPlaceholderIcon, null) })))),
347
349
  React.createElement(FormFooter, { onCancel: handleClose }))));
@@ -407,37 +409,3 @@ const StyledModalSidebarHeader = styled(ModalSidebarHeader) `
407
409
  margin-top: 8px;
408
410
  margin-bottom: 16px;
409
411
  `;
410
- export const openAffiliationsModal = (pos, view) => {
411
- if (!view) {
412
- return;
413
- }
414
- const { state } = view;
415
- const props = getEditorProps(state);
416
- const contributors = findChildrenAttrsByType(view, schema.nodes.contributor);
417
- const componentProps = {
418
- affiliations: [],
419
- authors: contributors,
420
- onSaveAffiliation: (affiliation) => handleSaveAffiliation(view, affiliation, pos),
421
- onDeleteAffiliation: (affiliation) => handleDeleteAffiliation(view, affiliation),
422
- onUpdateAuthors: (authors) => handleUpdateAuthors(view, authors),
423
- addNewAffiliation: true,
424
- };
425
- const dialog = ReactSubView(props, AffiliationsModal, componentProps, state.doc, () => pos, view);
426
- view.focus();
427
- document.body.appendChild(dialog);
428
- };
429
- export const handleSaveAffiliation = (view, affiliation, affiliationsPos) => {
430
- const update = updateNodeAttrs(view, schema.nodes.affiliation, affiliation);
431
- if (!update) {
432
- const node = schema.nodes.affiliation.create(affiliation);
433
- view.dispatch(view.state.tr.insert(affiliationsPos + 1, node));
434
- }
435
- };
436
- export const handleDeleteAffiliation = (view, affiliation) => {
437
- deleteNode(view, affiliation.id);
438
- };
439
- export const handleUpdateAuthors = (view, authors) => {
440
- authors.forEach((author) => {
441
- updateNodeAttrs(view, schema.nodes.contributor, author);
442
- });
443
- };
@@ -0,0 +1,15 @@
1
+ import { AffiliationPlaceholderIcon } from '@manuscripts/style-guide';
2
+ import React from 'react';
3
+ import { GenericPanel, ListItem, ListItems, useListSelectedIds, } from '../authors-affiliations/GenericPanel';
4
+ function affiliationSecondaryLine(item) {
5
+ const line = [item.city, item.county, item.country].filter(Boolean).join(', ');
6
+ return line || undefined;
7
+ }
8
+ export const AffiliationsPanel = ({ items, selectedItems = [], onSelect, onOpenAffiliationsModal, }) => {
9
+ const selectedIds = useListSelectedIds(selectedItems);
10
+ return (React.createElement(GenericPanel, { title: "Institutional Affiliations", createLabel: "Create New Affiliation", onCreate: onOpenAffiliationsModal, createDataCy: "add-affiliations-link", emptyDataCy: "affiliations-panel-empty", isEmpty: items.length === 0, emptyIcon: React.createElement(AffiliationPlaceholderIcon, null), emptyMessage: React.createElement(React.Fragment, null,
11
+ "There are no affiliations attributed yet!",
12
+ React.createElement("br", null),
13
+ "Click \u2018Create New Affiliation\u2019") },
14
+ React.createElement(ListItems, { "data-cy": "affiliations-panel" }, items.map((item) => (React.createElement(ListItem, { key: item.id, selected: selectedIds.has(item.id), onClick: () => onSelect(item.id), primary: item.institution, secondary: affiliationSecondaryLine(item) }))))));
15
+ };
@@ -34,11 +34,10 @@ export const AuthorDetailsForm = ({ values, onChange, onSave, actionsRef, isEmai
34
34
  formRef.current.setFieldValue('creditRoles', selectedCreditRoles);
35
35
  }
36
36
  }, [selectedCreditRoles]);
37
- if (actionsRef && !actionsRef.current) {
37
+ if (actionsRef) {
38
38
  actionsRef.current = {
39
- reset: () => {
40
- formRef.current?.resetForm();
41
- },
39
+ reset: () => formRef.current?.resetForm(),
40
+ submitForm: () => formRef.current?.submitForm(),
42
41
  };
43
42
  }
44
43
  const validateAuthor = (values) => {
@@ -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, outlineStyle, ScrollableModalContent, SidebarContent, StyledModal, InspectorTabs, InspectorTabPanel, InspectorTabList, InspectorTab, InspectorTabPanels, } from '@manuscripts/style-guide';
16
+ import { AddIcon, 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';
@@ -26,18 +26,22 @@ import FormFooter from '../form/FormFooter';
26
26
  import { FormPlaceholder } from '../form/FormPlaceholder';
27
27
  import { ModalFormActions } from '../form/ModalFormActions';
28
28
  import { DrawerGroup } from '../modal-drawer/GenericDrawerGroup';
29
- import { AffiliationsDrawer } from './AffiliationDrawer';
29
+ import { AffiliationsPanel } from '../affiliations/AffiliationsPanel';
30
30
  import { AuthorDetailsForm } from './AuthorDetailsForm';
31
31
  import { AuthorList } from './AuthorList';
32
32
  import { CreditDrawer } from './CreditDrawer';
33
33
  import { useManageAffiliations } from './useManageAffiliations';
34
34
  import { useManageCredit } from './useManageCredit';
35
- import { getEditorProps } from '../../plugins/editor-props';
36
- import ReactSubView from '../../views/ReactSubView';
37
- import { deleteNode, findChildrenAttrsByType, updateNodeAttrs, } from '../../lib/view';
38
35
  export const authorsReducer = arrayReducer((a, b) => a.id === b.id);
39
- export const AuthorsModal = ({ authors: $authors, affiliations: $affiliations, author, onSaveAuthor, onDeleteAuthor, addNewAuthor = false, }) => {
36
+ export const AuthorsModal = ({ authors: $authors, affiliations: $affiliations, author, onSaveAuthor, onDeleteAuthor, addNewAuthor = false, onOpenAffiliationsModal, onClose, }) => {
40
37
  const [isOpen, setOpen] = useState(true);
38
+ const prevIsOpenRef = useRef(true);
39
+ useEffect(() => {
40
+ if (prevIsOpenRef.current && !isOpen) {
41
+ onClose?.();
42
+ }
43
+ prevIsOpenRef.current = isOpen;
44
+ }, [isOpen, onClose]);
41
45
  const [isDisableSave, setDisableSave] = useState(true);
42
46
  const [isEmailRequired, setEmailRequired] = useState(false);
43
47
  const [showConfirmationDialog, setShowConfirmationDialog] = useState(false);
@@ -60,7 +64,7 @@ export const AuthorsModal = ({ authors: $authors, affiliations: $affiliations, a
60
64
  }
61
65
  }, [addNewAuthor]);
62
66
  const [selection, setSelection] = useState(author);
63
- const { showAffiliationDrawer, setShowAffiliationDrawer, selectedAffiliations, setSelectedAffiliations, removeAffiliation, selectAffiliation, affiliations, } = useManageAffiliations(selection, $affiliations);
67
+ const { selectedAffiliations, setSelectedAffiliations, selectAffiliation, affiliations, } = useManageAffiliations(selection, $affiliations);
64
68
  useEffect(() => {
65
69
  const currentAuthor = selection;
66
70
  const relevantAffiliations = affiliations.filter((item) => currentAuthor?.affiliationIDs?.includes(item.id));
@@ -87,12 +91,10 @@ export const AuthorsModal = ({ authors: $authors, affiliations: $affiliations, a
87
91
  else {
88
92
  updateAffiliationSelection(author);
89
93
  setSelection(author);
90
- setShowAffiliationDrawer(false);
91
94
  setNewAuthor(false);
92
95
  }
93
96
  }
94
97
  else {
95
- setShowAffiliationDrawer(false);
96
98
  updateAffiliationSelection(author);
97
99
  setSelection(author);
98
100
  setNewAuthor(false);
@@ -133,7 +135,6 @@ export const AuthorsModal = ({ authors: $authors, affiliations: $affiliations, a
133
135
  setSelection(nextAuthor);
134
136
  setNextAuthor(null);
135
137
  setNewAuthor(false);
136
- setShowAffiliationDrawer(false);
137
138
  updateAffiliationSelection(nextAuthor);
138
139
  setIsCreatingNewAuthor(false);
139
140
  }
@@ -166,7 +167,6 @@ export const AuthorsModal = ({ authors: $authors, affiliations: $affiliations, a
166
167
  }
167
168
  setShowConfirmationDialog(false);
168
169
  setShowRequiredFieldConfirmationDialog(false);
169
- setShowAffiliationDrawer(false);
170
170
  };
171
171
  const saveAuthor = (values) => {
172
172
  if (!values || !selection) {
@@ -185,7 +185,6 @@ export const AuthorsModal = ({ authors: $authors, affiliations: $affiliations, a
185
185
  setSelection(author);
186
186
  setShowConfirmationDialog(false);
187
187
  setNewAuthor(false);
188
- setShowAffiliationDrawer(false);
189
188
  setIsCreatingNewAuthor(false);
190
189
  dispatchAuthors({
191
190
  type: 'update',
@@ -242,7 +241,6 @@ export const AuthorsModal = ({ authors: $authors, affiliations: $affiliations, a
242
241
  }
243
242
  else {
244
243
  createNewAuthor();
245
- setShowAffiliationDrawer(false);
246
244
  }
247
245
  };
248
246
  const deleteAuthor = () => {
@@ -311,19 +309,19 @@ export const AuthorsModal = ({ authors: $authors, affiliations: $affiliations, a
311
309
  React.createElement(AuthorList, { author: selection, authors: authors, onSelect: selectAuthor, onDelete: () => setShowDeleteDialog((prev) => !prev), moveAuthor: moveAuthor, lastSavedAuthor: lastSavedAuthor }))),
312
310
  React.createElement(ScrollableModalContent, { "data-cy": "author-modal-content" }, selection ? (React.createElement(React.Fragment, null,
313
311
  React.createElement(AuthorTabs, null,
314
- React.createElement(ModalFormActions, { form: 'author-details-form', type: "author", onDelete: deleteAuthor, showingDeleteDialog: showingDeleteDialog, showDeleteDialog: () => setShowDeleteDialog((prev) => !prev), newEntity: newAuthor ||
312
+ React.createElement(ModalFormActions, { form: 'author-details-form', onSubmitForm: () => actionsRef.current?.submitForm?.(), type: "author", onDelete: deleteAuthor, showingDeleteDialog: showingDeleteDialog, showDeleteDialog: () => setShowDeleteDialog((prev) => !prev), newEntity: newAuthor ||
315
313
  (isCreatingNewAuthor &&
316
314
  !showConfirmationDialog &&
317
315
  !showRequiredFieldConfirmationDialog), isDisableSave: isDisableSave }),
318
316
  React.createElement(InspectorTabList, null,
319
317
  React.createElement(InspectorTab, null, "Details"),
320
- React.createElement(InspectorTab, null, "Affiliations"),
318
+ onOpenAffiliationsModal && (React.createElement(InspectorTab, null, "Affiliations")),
321
319
  React.createElement(InspectorTab, null, "Contributions (CRediT)")),
322
320
  React.createElement(InspectorTabPanels, null,
323
321
  React.createElement(AuthorTabPanel, null,
324
322
  React.createElement(AuthorDetailsForm, { values: normalizeAuthor(selection), onChange: changeAuthor, onSave: saveAuthor, actionsRef: actionsRef, isEmailRequired: isEmailRequired, selectedAffiliations: selectedAffiliations.map((a) => a.id), authorFormRef: authorFormRef, selectedCreditRoles: selectedCreditRoles })),
325
- React.createElement(AuthorTabPanel, null,
326
- React.createElement(DrawerGroup, { Drawer: AffiliationsDrawer, removeItem: removeAffiliation, selectedItems: selectedAffiliations, onSelect: selectAffiliation, items: affiliations, showDrawer: showAffiliationDrawer, setShowDrawer: setShowAffiliationDrawer, title: "Affiliations", buttonText: "Assign Institutions", cy: "affiliations", labelField: "institution", Icon: React.createElement(AddInstitutionIcon, { width: 16, height: 16 }) })),
323
+ onOpenAffiliationsModal && (React.createElement(AuthorTabPanel, null,
324
+ React.createElement(AffiliationsPanel, { items: affiliations, selectedItems: selectedAffiliations, onSelect: selectAffiliation, onOpenAffiliationsModal: onOpenAffiliationsModal }))),
327
325
  React.createElement(AuthorTabPanel, null,
328
326
  React.createElement(DrawerGroup, { Drawer: CreditDrawer, removeItem: removeCreditRole, selectedItems: selectedCreditRoles.map((r) => ({
329
327
  id: r.vocabTerm,
@@ -391,31 +389,3 @@ const StyledModalBody = styled(ModalBody) `
391
389
  const StyledModalSidebarHeader = styled(ModalSidebarHeader) `
392
390
  margin-bottom: 16px;
393
391
  `;
394
- export const openAuthorsModal = (pos, view) => {
395
- if (!view) {
396
- return;
397
- }
398
- const { state } = view;
399
- const props = getEditorProps(state);
400
- const affiliations = findChildrenAttrsByType(view, schema.nodes.affiliation);
401
- const componentProps = {
402
- authors: [],
403
- affiliations,
404
- onSaveAuthor: (contributor) => handleSaveContributor(view, contributor, pos),
405
- onDeleteAuthor: (contributor) => handleDeleteContributor(view, contributor),
406
- addNewAuthor: true,
407
- };
408
- const dialog = ReactSubView(props, AuthorsModal, componentProps, state.doc, () => pos, view);
409
- view.focus();
410
- document.body.appendChild(dialog);
411
- };
412
- export const handleSaveContributor = (view, contributor, contributorsPos) => {
413
- const update = updateNodeAttrs(view, schema.nodes.contributor, contributor);
414
- if (!update) {
415
- const node = schema.nodes.contributor.create(contributor);
416
- view.dispatch(view.state.tr.insert(contributorsPos + 1, node));
417
- }
418
- };
419
- export const handleDeleteContributor = (view, contributor) => {
420
- deleteNode(view, contributor.id);
421
- };
@@ -0,0 +1,11 @@
1
+ import { AuthorPlaceholderIcon } from '@manuscripts/style-guide';
2
+ import React from 'react';
3
+ import { GenericPanel, ListItem, ListItems, useListSelectedIds, } from '../authors-affiliations/GenericPanel';
4
+ export const AuthorsPanel = ({ items, selectedItems = [], onSelect, onOpenAuthorsModal, }) => {
5
+ const selectedIds = useListSelectedIds(selectedItems);
6
+ return (React.createElement(GenericPanel, { title: "Authors", createLabel: "Add New Author", onCreate: onOpenAuthorsModal, createDataCy: "add-authors-link", emptyDataCy: "authors-panel-empty", isEmpty: items.length === 0, emptyIcon: React.createElement(AuthorPlaceholderIcon, null), emptyMessage: React.createElement(React.Fragment, null,
7
+ "There are no authors attributed yet!",
8
+ React.createElement("br", null),
9
+ "Click \u2018Add New Author\u2019") },
10
+ React.createElement(ListItems, { "data-cy": "authors-panel" }, items.map((item) => (React.createElement(ListItem, { key: item.id, selected: selectedIds.has(item.id), onClick: () => onSelect(item.id), primary: item.label }))))));
11
+ };
@@ -1,9 +1,13 @@
1
- import { useReducer, useState } from 'react';
1
+ import { useEffect, useReducer, useState } from 'react';
2
2
  import { arrayReducer } from '../../lib/array-reducer';
3
3
  export const affiliationsReducer = arrayReducer((a, b) => a.id === b.id);
4
4
  export const useManageAffiliations = (selection, $affiliations) => {
5
- const [affiliations] = useReducer(affiliationsReducer, $affiliations);
6
- const [showAffiliationDrawer, setShowAffiliationDrawer] = useState(false);
5
+ const [affiliations, dispatchAffiliations] = useReducer(affiliationsReducer, [
6
+ ...$affiliations,
7
+ ]);
8
+ useEffect(() => {
9
+ dispatchAffiliations({ type: 'set', state: [...$affiliations] });
10
+ }, [$affiliations]);
7
11
  const [selectedAffiliations, setSelectedAffiliations] = useState([]);
8
12
  const removeAffiliation = (affId) => {
9
13
  if (!selection) {
@@ -26,8 +30,6 @@ export const useManageAffiliations = (selection, $affiliations) => {
26
30
  setSelectedAffiliations(affiliations.filter((item) => newAffiliations.includes(item.id)));
27
31
  };
28
32
  return {
29
- showAffiliationDrawer,
30
- setShowAffiliationDrawer,
31
33
  selectedAffiliations,
32
34
  setSelectedAffiliations,
33
35
  removeAffiliation,
@@ -0,0 +1,93 @@
1
+ /*!
2
+ * © 2026 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 { schema, } from '@manuscripts/transform';
17
+ import React, { useState } from 'react';
18
+ import { getEditorProps } from '../../plugins/editor-props';
19
+ import ReactSubView from '../../views/ReactSubView';
20
+ import { deleteNode, findChildByType, findChildrenAttrsByType, updateNodeAttrs, } from '../../lib/view';
21
+ import { AffiliationsModal, } from '../affiliations/AffiliationsModal';
22
+ import { AuthorsModal } from '../authors/AuthorsModal';
23
+ function insertNode(parentType, childType) {
24
+ return (view, attrs) => {
25
+ const parent = findChildByType(view, parentType);
26
+ if (parent) {
27
+ view.dispatch(view.state.tr.insert(parent.pos + 1, childType.create(attrs)));
28
+ }
29
+ };
30
+ }
31
+ function upsertNode(nodeType, insertFn) {
32
+ return (view, attrs) => {
33
+ if (!updateNodeAttrs(view, nodeType, attrs)) {
34
+ insertFn(view, attrs);
35
+ }
36
+ };
37
+ }
38
+ const insertAuthorNode = insertNode(schema.nodes.contributors, schema.nodes.contributor);
39
+ const insertAffiliationNode = insertNode(schema.nodes.affiliations, schema.nodes.affiliation);
40
+ const upsertAuthor = upsertNode(schema.nodes.contributor, insertAuthorNode);
41
+ const upsertAffiliation = upsertNode(schema.nodes.affiliation, insertAffiliationNode);
42
+ export const AuthorsAndAffiliationsModals = ({ initialModal, view, author, affiliation, addNewAuthor, addNewAffiliation, }) => {
43
+ const [showOverlay, setShowOverlay] = useState(false);
44
+ const [authors, setAuthors] = useState(() => findChildrenAttrsByType(view, schema.nodes.contributor));
45
+ const [affiliations, setAffiliations] = useState(() => findChildrenAttrsByType(view, schema.nodes.affiliation));
46
+ const handleOpenOverlay = () => setShowOverlay(true);
47
+ const handleOverlayClose = () => {
48
+ setShowOverlay(false);
49
+ setAuthors(findChildrenAttrsByType(view, schema.nodes.contributor));
50
+ setAffiliations(findChildrenAttrsByType(view, schema.nodes.affiliation));
51
+ };
52
+ const authorsProps = {
53
+ author,
54
+ authors,
55
+ affiliations,
56
+ addNewAuthor,
57
+ onSaveAuthor: (a) => upsertAuthor(view, a),
58
+ onDeleteAuthor: (a) => deleteNode(view, a.id),
59
+ };
60
+ const affiliationsProps = {
61
+ affiliation,
62
+ authors,
63
+ affiliations,
64
+ addNewAffiliation,
65
+ onSaveAffiliation: (a) => upsertAffiliation(view, a),
66
+ onDeleteAffiliation: (a) => deleteNode(view, a.id),
67
+ onUpdateAuthors: (updated) => updated.forEach((a) => updateNodeAttrs(view, schema.nodes.contributor, a)),
68
+ };
69
+ if (initialModal === 'authors') {
70
+ return (React.createElement(React.Fragment, null,
71
+ React.createElement(AuthorsModal, { ...authorsProps, onOpenAffiliationsModal: handleOpenOverlay }),
72
+ showOverlay && (React.createElement(AffiliationsModal, { ...affiliationsProps, addNewAffiliation: true, onClose: handleOverlayClose }))));
73
+ }
74
+ return (React.createElement(React.Fragment, null,
75
+ React.createElement(AffiliationsModal, { ...affiliationsProps, onOpenAuthorsModal: handleOpenOverlay }),
76
+ showOverlay && (React.createElement(AuthorsModal, { ...authorsProps, addNewAuthor: true, onClose: handleOverlayClose }))));
77
+ };
78
+ export const openAuthorsAndAffiliationsModals = (pos, view, initialModal) => {
79
+ if (!view) {
80
+ return;
81
+ }
82
+ const { state } = view;
83
+ const props = getEditorProps(state);
84
+ const componentProps = {
85
+ initialModal,
86
+ view: view,
87
+ addNewAuthor: initialModal === 'authors',
88
+ addNewAffiliation: initialModal === 'affiliations',
89
+ };
90
+ const dialog = ReactSubView(props, AuthorsAndAffiliationsModals, componentProps, state.doc, () => pos, view);
91
+ view.focus();
92
+ document.body.appendChild(dialog);
93
+ };