@manuscripts/body-editor 3.12.67 → 3.13.0

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 (289) hide show
  1. package/dist/cjs/components/toolbar/type-selector/styles.js +2 -1
  2. package/dist/cjs/versions.js +1 -1
  3. package/dist/es/components/toolbar/type-selector/styles.js +2 -1
  4. package/dist/es/versions.js +1 -1
  5. package/dist/types/components/toolbar/type-selector/styles.d.ts +2 -2
  6. package/dist/types/lib/footnotes.d.ts +4 -2
  7. package/dist/types/lib/utils.d.ts +5 -4
  8. package/dist/types/versions.d.ts +1 -1
  9. package/package.json +37 -36
  10. package/{dist/es/lib/statistics.js → src/__mocks__/styleMock.ts} +2 -14
  11. package/src/assets.ts +20 -0
  12. package/src/classes/collabProvider.ts +38 -0
  13. package/src/clipboard.ts +54 -0
  14. package/src/commands.ts +2109 -0
  15. package/src/components/ChangeHandlingForm.tsx +54 -0
  16. package/src/components/LanguageDropdown/index.tsx +343 -0
  17. package/src/components/affiliations/AffiliationForm.tsx +238 -0
  18. package/src/components/affiliations/AffiliationItem.tsx +151 -0
  19. package/src/components/affiliations/AffiliationList.tsx +71 -0
  20. package/src/components/affiliations/AffiliationsModal.tsx +676 -0
  21. package/src/components/affiliations/AffiliationsPanel.tsx +76 -0
  22. package/src/components/affiliations/CreateAffiliationModal.tsx +135 -0
  23. package/src/components/authors/AuthorDetailsForm.tsx +438 -0
  24. package/src/components/authors/AuthorList.tsx +81 -0
  25. package/src/components/authors/AuthorsModal.tsx +642 -0
  26. package/src/components/authors/AuthorsPanel.tsx +69 -0
  27. package/src/components/authors/CreateAuthorModal.tsx +172 -0
  28. package/src/components/authors/CreditDrawer.tsx +71 -0
  29. package/src/components/authors/DraggableAuthor.tsx +228 -0
  30. package/src/components/authors/useManageAffiliations.tsx +77 -0
  31. package/src/components/authors/useManageCredit.tsx +63 -0
  32. package/src/components/authors-affiliations/AuthorsAndAffiliationsModals.tsx +170 -0
  33. package/src/components/authors-affiliations/GenericPanel.tsx +164 -0
  34. package/src/components/authors-affiliations/ModalTabs.tsx +100 -0
  35. package/src/components/awards/AwardForm.tsx +338 -0
  36. package/src/components/awards/AwardModal.tsx +120 -0
  37. package/src/components/awards/DeleteAwardDiaolog.tsx +52 -0
  38. package/src/components/dialog/ConfirmationDialog.tsx +131 -0
  39. package/src/components/form/CreateModalStyles.tsx +37 -0
  40. package/src/components/form/FormFooter.tsx +54 -0
  41. package/src/components/form/FormPlaceholder.tsx +71 -0
  42. package/src/components/form/ModalFormActions.tsx +88 -0
  43. package/src/components/form/UnsavedLabel.tsx +49 -0
  44. package/src/components/hooks/use-debounce.ts +56 -0
  45. package/src/components/hooks/useAffiliationShowsErrorIndicator.ts +51 -0
  46. package/src/components/hooks/useAuthorShowsErrorIndicator.ts +73 -0
  47. package/src/components/keyboard-shortcuts-modal/FormattedShortcut.tsx +56 -0
  48. package/src/components/keyboard-shortcuts-modal/KeyboardShortcutsModal.tsx +283 -0
  49. package/src/components/keyboard-shortcuts-modal/keyboard-shortcuts.ts +304 -0
  50. package/src/components/keywords/AddKeywordInline.tsx +284 -0
  51. package/src/components/keywords/DeleteKeywordDialog.tsx +52 -0
  52. package/src/components/modal-drawer/GenericDrawer.tsx +65 -0
  53. package/src/components/modal-drawer/GenericDrawerGroup.tsx +125 -0
  54. package/src/components/outline/DraggableTree.tsx +438 -0
  55. package/src/components/outline/ManuscriptOutline.tsx +127 -0
  56. package/src/components/outline/Outline.tsx +141 -0
  57. package/src/components/references/BibliographyItemSource.ts +80 -0
  58. package/src/components/references/CitationEditor.tsx +301 -0
  59. package/src/components/references/CitationViewer.tsx +62 -0
  60. package/src/components/references/ImportBibliographyForm.tsx +232 -0
  61. package/src/components/references/ImportBibliographyModal.tsx +159 -0
  62. package/src/components/references/ReferenceForm/PersonDropDown.tsx +93 -0
  63. package/src/components/references/ReferenceForm/ReferenceForm.tsx +701 -0
  64. package/src/components/references/ReferenceForm/config.ts +133 -0
  65. package/src/components/references/ReferenceForm/styled-components.ts +156 -0
  66. package/src/components/references/ReferenceLine.tsx +43 -0
  67. package/src/components/references/ReferenceSearch.tsx +163 -0
  68. package/src/components/references/ReferenceSearchResults.tsx +113 -0
  69. package/src/components/references/ReferenceSearchResultsPlaceholder.tsx +71 -0
  70. package/src/components/references/ReferenceSearchSection.tsx +124 -0
  71. package/src/components/references/ReferencesEditor.tsx +61 -0
  72. package/src/components/references/ReferencesModal.tsx +352 -0
  73. package/src/components/references/SearchInput.tsx +98 -0
  74. package/src/components/toolbar/InsertEmbedDialog.tsx +222 -0
  75. package/src/components/toolbar/InsertTableDialog.tsx +208 -0
  76. package/src/components/toolbar/ListMenuItem.tsx +203 -0
  77. package/src/components/toolbar/helpers.ts +547 -0
  78. package/src/components/toolbar/type-selector/OptionComponent.tsx +89 -0
  79. package/src/components/toolbar/type-selector/TypeSelector.tsx +157 -0
  80. package/src/components/toolbar/type-selector/styles.ts +79 -0
  81. package/src/components/views/CrossReferenceItems.tsx +199 -0
  82. package/src/components/views/DeleteEmbedDialog.tsx +83 -0
  83. package/src/components/views/DeleteFootnoteDialog.tsx +54 -0
  84. package/src/components/views/FigureDropdown.tsx +423 -0
  85. package/src/components/views/FootnotesSelector.tsx +255 -0
  86. package/src/components/views/InsertSpecialCharacter.tsx +245 -0
  87. package/src/components/views/LinkForm.tsx +220 -0
  88. package/src/components/views/TableCellContextMenu.tsx +241 -0
  89. package/src/configs/ManuscriptsEditor.ts +140 -0
  90. package/src/configs/editor-plugins.ts +119 -0
  91. package/src/configs/editor-views.ts +139 -0
  92. package/src/icons.ts +62 -0
  93. package/src/index.ts +65 -0
  94. package/src/keys/highlight.ts +65 -0
  95. package/src/keys/index.ts +33 -0
  96. package/src/keys/keyword.ts +69 -0
  97. package/src/keys/list.ts +156 -0
  98. package/src/keys/misc.ts +112 -0
  99. package/src/keys/title.ts +221 -0
  100. package/src/lib/__tests__/__fixtures__/doc.json +388 -0
  101. package/src/lib/__tests__/__snapshots__/paste.test.ts.snap +24 -0
  102. package/src/lib/__tests__/authors.test.ts +202 -0
  103. package/src/lib/__tests__/helpers.test.ts +46 -0
  104. package/src/lib/__tests__/paste.test.ts +57 -0
  105. package/src/lib/__tests__/plugins.test.ts +176 -0
  106. package/src/lib/__tests__/url.test.ts +31 -0
  107. package/src/lib/array-reducer.ts +68 -0
  108. package/src/lib/authors-and-affiliations.ts +81 -0
  109. package/src/lib/authors.ts +67 -0
  110. package/src/lib/capabilities.ts +55 -0
  111. package/src/lib/codemirror.ts +37 -0
  112. package/src/lib/comments.ts +123 -0
  113. package/src/lib/context-menu.ts +745 -0
  114. package/src/lib/copy.ts +30 -0
  115. package/src/lib/crossref.ts +90 -0
  116. package/src/lib/dnd.ts +29 -0
  117. package/src/lib/doc.ts +167 -0
  118. package/src/lib/dompurify.ts +28 -0
  119. package/src/lib/files.ts +180 -0
  120. package/src/lib/footnotes.ts +83 -0
  121. package/src/lib/get-media-type.ts +105 -0
  122. package/src/lib/helpers.ts +199 -0
  123. package/src/lib/hierarchy.ts +117 -0
  124. package/src/lib/languages.ts +37 -0
  125. package/src/lib/lists.ts +56 -0
  126. package/src/lib/math.ts +111 -0
  127. package/src/lib/media.ts +334 -0
  128. package/src/lib/navigation-utils.ts +235 -0
  129. package/src/lib/normalize.ts +70 -0
  130. package/src/lib/paste.ts +304 -0
  131. package/src/lib/placeholder.ts +32 -0
  132. package/src/lib/platform.ts +18 -0
  133. package/src/lib/plugins.ts +22 -0
  134. package/src/lib/popper.ts +180 -0
  135. package/src/lib/position-menu.ts +184 -0
  136. package/src/lib/references.ts +94 -0
  137. package/src/lib/template.ts +29 -0
  138. package/src/lib/track-changes-utils.ts +34 -0
  139. package/{dist/types/lib/statistics.d.ts → src/lib/url.ts} +11 -8
  140. package/src/lib/use-do-with-debounce.ts +39 -0
  141. package/src/lib/utils.ts +323 -0
  142. package/src/lib/view.ts +100 -0
  143. package/src/menus.tsx +684 -0
  144. package/src/node-type-icons.tsx +83 -0
  145. package/src/plugins/__tests__/__fixtures__/deleted-footnote.json +140 -0
  146. package/src/plugins/__tests__/__fixtures__/inserted-footnote.json +153 -0
  147. package/src/plugins/__tests__/footnotes.test.ts +67 -0
  148. package/src/plugins/accessibility_element.ts +219 -0
  149. package/src/plugins/add-subtitle.ts +102 -0
  150. package/src/plugins/affiliations.ts +136 -0
  151. package/src/plugins/alt-titles.ts +219 -0
  152. package/src/plugins/bibliography.ts +235 -0
  153. package/src/plugins/comments.ts +328 -0
  154. package/src/plugins/cross-references.ts +70 -0
  155. package/src/plugins/detect-inconsistency/detect-inconsistency-utils.ts +102 -0
  156. package/src/plugins/detect-inconsistency/index.ts +55 -0
  157. package/src/plugins/detect-inconsistency/validators.ts +283 -0
  158. package/src/plugins/doi.ts +66 -0
  159. package/src/plugins/editor-props.ts +39 -0
  160. package/src/plugins/elements.ts +60 -0
  161. package/src/plugins/footnotes.ts +298 -0
  162. package/src/plugins/link.ts +53 -0
  163. package/src/plugins/lock-body.ts +136 -0
  164. package/src/plugins/move-node.ts +55 -0
  165. package/src/plugins/objects.ts +125 -0
  166. package/src/plugins/paragraphs.ts +90 -0
  167. package/src/plugins/persist.ts +64 -0
  168. package/src/plugins/persistent-cursor.ts +66 -0
  169. package/src/plugins/placeholder.ts +219 -0
  170. package/src/plugins/prevent-empty.ts +50 -0
  171. package/src/plugins/search-replace/index.ts +157 -0
  172. package/src/plugins/search-replace/lib.ts +95 -0
  173. package/src/plugins/section_category.ts +212 -0
  174. package/src/plugins/section_title/autocompletion.ts +87 -0
  175. package/src/plugins/section_title/index.ts +105 -0
  176. package/src/plugins/selected-suggestion.ts +243 -0
  177. package/src/plugins/tables-cursor-fix.ts +143 -0
  178. package/src/plugins/translations.ts +263 -0
  179. package/src/rules.ts +60 -0
  180. package/src/selection.ts +112 -0
  181. package/src/testing/default-capabilities.ts +48 -0
  182. package/src/testing/default-editor-data.ts +73 -0
  183. package/src/testing/empty-editor-doc.json +62 -0
  184. package/src/testing/polyfill-dom.ts +43 -0
  185. package/src/testing/section-categories.json +161 -0
  186. package/src/testing/setup-editor.ts +55 -0
  187. package/src/tests.ts +42 -0
  188. package/src/toolbar.tsx +259 -0
  189. package/src/types/@citation-js/core.d.ts +16 -0
  190. package/src/types/@citation-js/index.d.ts +16 -0
  191. package/src/types/@citation-js/plugin-bibtex.d.ts +16 -0
  192. package/src/types/@citation-js/plugin-csl.d.ts +16 -0
  193. package/src/types/@citation-js/plugin-doi.d.ts +16 -0
  194. package/src/types/@citation-js/plugin-enw.d.ts +16 -0
  195. package/src/types/@citation-js/plugin-pubmed.d.ts +16 -0
  196. package/src/types/@citation-js/plugin-ris.d.ts +16 -0
  197. package/src/types/citeproc.d.ts +124 -0
  198. package/src/types/css.d.ts +16 -0
  199. package/src/types/json.d.ts +17 -0
  200. package/src/types/prosemirror-dev-tools.d.ts +16 -0
  201. package/src/types/prosemirror-dropcursor.d.ts +16 -0
  202. package/src/types/styled-components.d.ts +23 -0
  203. package/src/types.ts +49 -0
  204. package/src/useEditor.ts +244 -0
  205. package/src/versions.ts +3 -0
  206. package/src/views/ReactSubView.tsx +145 -0
  207. package/src/views/abstracts.ts +46 -0
  208. package/src/views/accessibility_element.ts +86 -0
  209. package/src/views/affiliations.ts +254 -0
  210. package/src/views/alt_title.ts +85 -0
  211. package/src/views/alt_titles_section.ts +97 -0
  212. package/src/views/attachment.ts +160 -0
  213. package/src/views/attachments.ts +72 -0
  214. package/src/views/author_notes.ts +96 -0
  215. package/src/views/award.ts +194 -0
  216. package/src/views/awards.ts +59 -0
  217. package/src/views/base_node_view.ts +85 -0
  218. package/src/views/bibliography_element.ts +313 -0
  219. package/src/views/block_view.ts +100 -0
  220. package/src/views/blockquote_element.ts +26 -0
  221. package/src/views/blockquote_element_editable.ts +24 -0
  222. package/src/views/box_element.ts +33 -0
  223. package/src/views/caption.ts +37 -0
  224. package/src/views/caption_title.ts +33 -0
  225. package/src/views/citation.ts +67 -0
  226. package/src/views/citation_editable.ts +312 -0
  227. package/src/views/contributors.ts +341 -0
  228. package/src/views/creators.ts +82 -0
  229. package/src/views/cross_reference.ts +74 -0
  230. package/src/views/cross_reference_editable.ts +113 -0
  231. package/src/views/editable_block.ts +141 -0
  232. package/src/views/embed.ts +345 -0
  233. package/src/views/empty.ts +26 -0
  234. package/src/views/equation.ts +47 -0
  235. package/src/views/equation_editable.ts +84 -0
  236. package/src/views/equation_element.ts +29 -0
  237. package/src/views/equation_element_editable.ts +21 -0
  238. package/src/views/figure.ts +84 -0
  239. package/src/views/figure_editable.ts +361 -0
  240. package/src/views/figure_element.ts +197 -0
  241. package/src/views/figure_element_editable.ts +21 -0
  242. package/src/views/footnote.ts +236 -0
  243. package/src/views/footnotes_element.ts +54 -0
  244. package/src/views/general_table_footnote.ts +140 -0
  245. package/src/views/hero_image.ts +76 -0
  246. package/src/views/hero_image_editable.ts +21 -0
  247. package/src/views/image_element.ts +373 -0
  248. package/src/views/image_element_editable.ts +21 -0
  249. package/src/views/inline_equation.ts +47 -0
  250. package/src/views/inline_equation_editable.ts +70 -0
  251. package/src/views/inline_footnote.ts +264 -0
  252. package/src/views/inline_footnote_editable.ts +20 -0
  253. package/src/views/keyword.ts +127 -0
  254. package/src/views/keyword_group.ts +109 -0
  255. package/src/views/link.ts +27 -0
  256. package/src/views/link_editable.ts +169 -0
  257. package/src/views/list.ts +46 -0
  258. package/src/views/list_item.ts +26 -0
  259. package/src/views/paragraph.ts +26 -0
  260. package/src/views/paragraph_editable.ts +21 -0
  261. package/src/views/placeholder.ts +35 -0
  262. package/src/views/placeholder_element.ts +38 -0
  263. package/src/views/placeholder_element_editable.ts +21 -0
  264. package/src/views/pullquote_element.ts +106 -0
  265. package/src/views/pullquote_element_editable.ts +24 -0
  266. package/src/views/quote_image_editable.ts +84 -0
  267. package/src/views/section.ts +64 -0
  268. package/src/views/section_label.ts +20 -0
  269. package/src/views/section_title.ts +87 -0
  270. package/src/views/section_title_editable.ts +21 -0
  271. package/src/views/subtitle.ts +42 -0
  272. package/src/views/subtitle_editable.ts +20 -0
  273. package/src/views/subtitles.ts +34 -0
  274. package/src/views/subtitles_editable.ts +21 -0
  275. package/src/views/supplement.ts +236 -0
  276. package/src/views/supplement_editable.ts +20 -0
  277. package/src/views/supplements.ts +65 -0
  278. package/src/views/supplements_editable.ts +21 -0
  279. package/src/views/table_cell.ts +150 -0
  280. package/src/views/table_element.ts +33 -0
  281. package/src/views/table_element_editable.ts +21 -0
  282. package/src/views/table_element_footer.ts +41 -0
  283. package/src/views/title.ts +46 -0
  284. package/src/views/title_editable.ts +20 -0
  285. package/src/views/trans_graphical_abstract.ts +55 -0
  286. package/src/views/trans_graphical_abstract_editable.ts +21 -0
  287. package/src/views/translated_abstract.ts +53 -0
  288. package/src/views/translated_abstract_editable.ts +21 -0
  289. package/dist/cjs/lib/statistics.js +0 -38
@@ -0,0 +1,642 @@
1
+ /*!
2
+ * © 2025 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
+
17
+ import {
18
+ AddIcon,
19
+ AuthorPlaceholderIcon,
20
+ CloseButton,
21
+ FormSubtitle,
22
+ ModalBody,
23
+ ModalContainer,
24
+ ModalHeader,
25
+ ModalSidebar,
26
+ ModalSidebarHeader,
27
+ ModalSidebarTitle,
28
+ outlineStyle,
29
+ ScrollableModalContent,
30
+ SidebarContent,
31
+ StyledModal,
32
+ InspectorTabs,
33
+ InspectorTabPanel,
34
+ InspectorTabPanels,
35
+ } from '@manuscripts/style-guide'
36
+ import { generateNodeID, schema } from '@manuscripts/transform'
37
+ import { cloneDeep, isEqual, omit } from 'lodash'
38
+ import React, {
39
+ useCallback,
40
+ useEffect,
41
+ useReducer,
42
+ useRef,
43
+ useState,
44
+ } from 'react'
45
+ import styled from 'styled-components'
46
+
47
+ import { arrayReducer } from '../../lib/array-reducer'
48
+ import {
49
+ AffiliationAttrs,
50
+ authorComparator,
51
+ ContributorAttrs,
52
+ } from '../../lib/authors'
53
+ import { normalizeAuthor } from '../../lib/normalize'
54
+ import { ConfirmationDialog, DialogType } from '../dialog/ConfirmationDialog'
55
+ import FormFooter from '../form/FormFooter'
56
+ import { FormPlaceholder } from '../form/FormPlaceholder'
57
+ import { ModalFormActions, ModalFormSaveButton } from '../form/ModalFormActions'
58
+ import { ModalTabs } from '../authors-affiliations/ModalTabs'
59
+ import { AffiliationsPanel } from '../affiliations/AffiliationsPanel'
60
+ import { AuthorDetailsForm, FormActions } from './AuthorDetailsForm'
61
+ import { AuthorList } from './AuthorList'
62
+ import { CreditContributionsCheckboxes } from './CreditDrawer'
63
+ import { useManageAffiliations } from './useManageAffiliations'
64
+ import { useManageCredit } from './useManageCredit'
65
+
66
+ const MODAL_ON_CLOSE_NOTIFY_DELAY_MS = 220
67
+
68
+ export const authorsReducer = arrayReducer<ContributorAttrs>(
69
+ (a, b) => a.id === b.id
70
+ )
71
+
72
+ export interface AuthorsModalProps {
73
+ author?: ContributorAttrs
74
+ authors: ContributorAttrs[]
75
+ affiliations: AffiliationAttrs[]
76
+ onSaveAuthor: (author: ContributorAttrs) => void
77
+ onDeleteAuthor: (author: ContributorAttrs) => void
78
+ addNewAuthor?: boolean
79
+ onOpenAffiliationsModal?: () => void
80
+ onClose?: () => void
81
+ }
82
+
83
+ export const AuthorsModal: React.FC<AuthorsModalProps> = ({
84
+ authors: $authors,
85
+ affiliations: $affiliations,
86
+ author,
87
+ onSaveAuthor,
88
+ onDeleteAuthor,
89
+ addNewAuthor = false,
90
+ onOpenAffiliationsModal,
91
+ onClose,
92
+ }) => {
93
+ const [isOpen, setOpen] = useState(true)
94
+ const prevIsOpenRef = useRef(true)
95
+ useEffect(() => {
96
+ if (prevIsOpenRef.current && !isOpen) {
97
+ prevIsOpenRef.current = isOpen
98
+ const id = window.setTimeout(() => {
99
+ onClose?.()
100
+ }, MODAL_ON_CLOSE_NOTIFY_DELAY_MS)
101
+ return () => window.clearTimeout(id)
102
+ }
103
+ prevIsOpenRef.current = isOpen
104
+ }, [isOpen, onClose])
105
+ const [isDisableSave, setDisableSave] = useState(true)
106
+ const [isEmailRequired, setEmailRequired] = useState(false)
107
+ const [showConfirmationDialog, setShowConfirmationDialog] = useState(false)
108
+ const [
109
+ showRequiredFieldConfirmationDialog,
110
+ setShowRequiredFieldConfirmationDialog,
111
+ ] = useState(false)
112
+ const [lastSavedAuthor, setLastSavedAuthor] = useState<string | null>(null)
113
+ const [showingDeleteDialog, setShowDeleteDialog] = useState(false)
114
+ const [newAuthor, setNewAuthor] = useState(false)
115
+ const [unSavedChanges, setUnSavedChanges] = useState(false)
116
+ const [nextAuthor, setNextAuthor] = useState<ContributorAttrs | null>(null)
117
+ const [isSwitchingAuthor, setIsSwitchingAuthor] = useState(false)
118
+ const [isCreatingNewAuthor, setIsCreatingNewAuthor] = useState(false)
119
+
120
+ const [authorHasError, setAuthorDetailsTabHasError] = useState(false)
121
+ const [authorDetailsUnsavedContinue, setAuthorDetailsUnsavedContinue] =
122
+ useState(false)
123
+ const [authorDetailsRequiredContinue, setAuthorDetailsRequiredContinue] =
124
+ useState(false)
125
+ const [authorTabIndex, setAuthorTabIndex] = useState(0)
126
+
127
+ const valuesRef = useRef<ContributorAttrs>(undefined)
128
+ const actionsRef = useRef<FormActions>(undefined)
129
+ const authorFormRef = useRef<HTMLFormElement>(null)
130
+ const [authors, dispatchAuthors] = useReducer(
131
+ authorsReducer,
132
+ $authors.sort(authorComparator)
133
+ )
134
+
135
+ useEffect(() => {
136
+ if (addNewAuthor) {
137
+ addAuthor()
138
+ }
139
+ // eslint-disable-next-line react-hooks/exhaustive-deps
140
+ }, [addNewAuthor])
141
+
142
+ const [selection, setSelection] = useState(author)
143
+
144
+ const {
145
+ selectedAffiliations,
146
+ setSelectedAffiliations,
147
+ selectAffiliation,
148
+ affiliations,
149
+ } = useManageAffiliations(selection, $affiliations)
150
+
151
+ useEffect(() => {
152
+ const currentAuthor = selection
153
+ const relevantAffiliations = affiliations.filter((item) =>
154
+ currentAuthor?.affiliationIDs?.includes(item.id)
155
+ )
156
+ setSelectedAffiliations(relevantAffiliations)
157
+ // eslint-disable-next-line react-hooks/exhaustive-deps
158
+ }, [])
159
+
160
+ useEffect(() => {
161
+ if (!unSavedChanges) {
162
+ setAuthorDetailsUnsavedContinue(false)
163
+ setAuthorDetailsRequiredContinue(false)
164
+ }
165
+ }, [unSavedChanges])
166
+
167
+ useEffect(() => {
168
+ setAuthorDetailsUnsavedContinue(false)
169
+ setAuthorDetailsRequiredContinue(false)
170
+ if (selection?.id) {
171
+ setAuthorTabIndex(0)
172
+ }
173
+ }, [selection?.id])
174
+
175
+ const selectAuthor = (author: ContributorAttrs) => {
176
+ if (author.id === selection?.id) {
177
+ return
178
+ }
179
+ const values = valuesRef.current
180
+ setIsCreatingNewAuthor(false)
181
+
182
+ if (values && selection) {
183
+ const normalizedSelection = normalizeAuthor(selection)
184
+ const normalizedValues = normalizeAuthor(values)
185
+
186
+ const hasChanges = !isEqual(normalizedSelection, normalizedValues)
187
+
188
+ if (hasChanges && !isDisableSave) {
189
+ setShowConfirmationDialog(true)
190
+ setNextAuthor(author)
191
+ } else if (hasChanges && isDisableSave) {
192
+ setShowRequiredFieldConfirmationDialog(true)
193
+ setNextAuthor(author)
194
+ } else {
195
+ updateAffiliationSelection(author)
196
+ setSelection(author)
197
+ setNewAuthor(false)
198
+ }
199
+ } else {
200
+ updateAffiliationSelection(author)
201
+ setSelection(author)
202
+ setNewAuthor(false)
203
+ }
204
+ }
205
+ const updateAffiliationSelection = (author: ContributorAttrs) => {
206
+ const relevantAffiliations = affiliations.filter((item) =>
207
+ author.affiliationIDs?.includes(item.id)
208
+ )
209
+ setSelectedAffiliations(relevantAffiliations)
210
+ }
211
+ const close = () => {
212
+ if (unSavedChanges) {
213
+ if (isDisableSave) {
214
+ setShowRequiredFieldConfirmationDialog(true)
215
+ } else {
216
+ setShowConfirmationDialog(true)
217
+ }
218
+ } else {
219
+ setShowRequiredFieldConfirmationDialog(false)
220
+ setLastSavedAuthor(null)
221
+ setOpen(false)
222
+ setSelection(undefined)
223
+ }
224
+ }
225
+
226
+ const cancel = () => {
227
+ resetAuthor()
228
+ if (nextAuthor) {
229
+ const nextAuthorAffiliations = nextAuthor.affiliationIDs || []
230
+ setSelectedAffiliations(
231
+ affiliations.filter((item) => nextAuthorAffiliations.includes(item.id))
232
+ )
233
+ setSelection(nextAuthor)
234
+ setNextAuthor(null)
235
+ setNewAuthor(false)
236
+ setIsCreatingNewAuthor(false)
237
+ setUnSavedChanges(false)
238
+ } else if (newAuthor && unSavedChanges && !isSwitchingAuthor) {
239
+ setNewAuthor(false)
240
+ setIsCreatingNewAuthor(false)
241
+ setOpen(false)
242
+ } else if (isCreatingNewAuthor) {
243
+ createNewAuthor()
244
+ setIsCreatingNewAuthor(false)
245
+ }
246
+ setShowConfirmationDialog(false)
247
+ setShowRequiredFieldConfirmationDialog(false)
248
+ }
249
+
250
+ const saveAuthor = (values: ContributorAttrs | undefined) => {
251
+ if (!values || !selection) {
252
+ return
253
+ }
254
+ const author = {
255
+ ...selection,
256
+ ...values,
257
+ }
258
+
259
+ onSaveAuthor(author)
260
+ setLastSavedAuthor(author.id)
261
+ setTimeout(() => {
262
+ setLastSavedAuthor(null)
263
+ }, 3200)
264
+ setUnSavedChanges(false)
265
+ setSelection(author)
266
+ setShowConfirmationDialog(false)
267
+ setNewAuthor(false)
268
+ setIsCreatingNewAuthor(false)
269
+ dispatchAuthors({
270
+ type: 'update',
271
+ items: [author],
272
+ })
273
+ }
274
+
275
+ const moveAuthor = useCallback(
276
+ (from: ContributorAttrs, to: ContributorAttrs, shift: number) => {
277
+ const copy = cloneDeep(authors)
278
+ const order = copy.map((a, i) => {
279
+ if (a.id === from.id) {
280
+ if (to.priority) {
281
+ return to.priority + shift
282
+ } else {
283
+ return authors.findIndex((i) => i === to) + shift
284
+ }
285
+ }
286
+ return i
287
+ })
288
+ copy.sort((a, b) => order[copy.indexOf(a)] - order[copy.indexOf(b)])
289
+ copy.forEach((a, i) => {
290
+ if (a.priority !== i) {
291
+ a.priority = i
292
+ onSaveAuthor(a)
293
+ }
294
+ })
295
+ dispatchAuthors({
296
+ type: 'set',
297
+ state: copy,
298
+ })
299
+ },
300
+ [authors, dispatchAuthors, onSaveAuthor]
301
+ )
302
+ const createNewAuthor = () => {
303
+ const author = createEmptyAuthor(authors.length)
304
+ setIsSwitchingAuthor(!!selection)
305
+ setSelectedAffiliations([])
306
+ setSelectedCreditRoles([])
307
+ setSelection(author)
308
+ setNewAuthor(true)
309
+ }
310
+
311
+ const addAuthor = () => {
312
+ const values = valuesRef.current
313
+ setIsSwitchingAuthor(!!selection)
314
+ setIsCreatingNewAuthor(true)
315
+ if (
316
+ values &&
317
+ selection &&
318
+ !isEqual(normalizeAuthor(values), normalizeAuthor(selection))
319
+ ) {
320
+ if (isDisableSave) {
321
+ setShowRequiredFieldConfirmationDialog(true)
322
+ } else {
323
+ setShowConfirmationDialog(true)
324
+ }
325
+ setNextAuthor(null)
326
+ } else {
327
+ createNewAuthor()
328
+ }
329
+ }
330
+
331
+ const deleteAuthor = () => {
332
+ if (!selection) {
333
+ return
334
+ }
335
+ onDeleteAuthor(selection)
336
+ setSelection(undefined)
337
+ setUnSavedChanges(false)
338
+ dispatchAuthors({
339
+ type: 'delete',
340
+ item: selection,
341
+ })
342
+ }
343
+
344
+ const resetAuthor = () => {
345
+ actionsRef.current?.reset()
346
+ const selectedAffs = selection?.affiliationIDs || []
347
+ setSelectedAffiliations(
348
+ affiliations.filter((item) => selectedAffs.includes(item.id))
349
+ )
350
+ setShowConfirmationDialog(false)
351
+ setShowRequiredFieldConfirmationDialog(false)
352
+ setUnSavedChanges(false)
353
+ if (!isCreatingNewAuthor && !nextAuthor) {
354
+ setLastSavedAuthor(null)
355
+ setOpen(false)
356
+ } else if (isCreatingNewAuthor) {
357
+ createNewAuthor()
358
+ }
359
+ setIsCreatingNewAuthor(false)
360
+ }
361
+
362
+ const changeAuthor = (values: ContributorAttrs) => {
363
+ const normalized = omit(
364
+ normalizeAuthor(selection as ContributorAttrs),
365
+ 'priority'
366
+ )
367
+ const updatedValues = omit(normalizeAuthor(values), 'priority')
368
+
369
+ const isSameAuthor = updatedValues.id === normalized.id
370
+ const hasChanges = !isEqual(updatedValues, normalized)
371
+ setUnSavedChanges(isSameAuthor && hasChanges)
372
+
373
+ valuesRef.current = { ...updatedValues, priority: values.priority }
374
+
375
+ const { given, family, email, isCorresponding } = values
376
+ const isNameFilled = given?.length || family?.length
377
+
378
+ if (hasChanges && isNameFilled) {
379
+ if (isCorresponding) {
380
+ setDisableSave(!email?.length)
381
+ } else {
382
+ setDisableSave(false)
383
+ }
384
+ } else {
385
+ setDisableSave(true)
386
+ }
387
+
388
+ setEmailRequired(isCorresponding)
389
+ }
390
+
391
+ const {
392
+ selectCreditRole,
393
+ selectedCreditRoles,
394
+ setSelectedCreditRoles,
395
+ vocabTermItems,
396
+ } = useManageCredit(selection)
397
+
398
+ const newEntity =
399
+ newAuthor ||
400
+ (isCreatingNewAuthor &&
401
+ !showConfirmationDialog &&
402
+ !showRequiredFieldConfirmationDialog)
403
+
404
+ return (
405
+ <StyledModal
406
+ isOpen={isOpen}
407
+ onRequestClose={() => close()}
408
+ shouldCloseOnOverlayClick={true}
409
+ >
410
+ <ModalContainer data-cy="authors-modal">
411
+ <ModalHeader>
412
+ <CloseButton onClick={() => close()} data-cy="modal-close-button" />
413
+ </ModalHeader>
414
+ <StyledModalBody>
415
+ <ModalSidebar data-cy="authors-sidebar">
416
+ <StyledModalSidebarHeader>
417
+ <ModalSidebarTitle>Manage Authors</ModalSidebarTitle>
418
+ </StyledModalSidebarHeader>
419
+ <StyledSidebarContent>
420
+ <AddAuthorButton
421
+ data-cy="add-author-button"
422
+ onClick={addAuthor}
423
+ data-active={isCreatingNewAuthor || newAuthor}
424
+ >
425
+ <AddIcon width={18} height={18} />
426
+ <ActionTitle>New Author</ActionTitle>
427
+ </AddAuthorButton>
428
+ <AuthorList
429
+ author={selection}
430
+ authors={authors}
431
+ onSelect={selectAuthor}
432
+ onDelete={() => setShowDeleteDialog((prev) => !prev)}
433
+ moveAuthor={moveAuthor}
434
+ lastSavedAuthor={lastSavedAuthor}
435
+ />
436
+ </StyledSidebarContent>
437
+ </ModalSidebar>
438
+ <StyledScrollableModalContent data-cy="author-modal-content">
439
+ {selection ? (
440
+ <>
441
+ <AuthorTabs
442
+ selectedIndex={authorTabIndex}
443
+ onChange={setAuthorTabIndex}
444
+ >
445
+ <ModalFormActions
446
+ type="author"
447
+ onDelete={deleteAuthor}
448
+ showingDeleteDialog={showingDeleteDialog}
449
+ showDeleteDialog={() =>
450
+ setShowDeleteDialog((prev) => !prev)
451
+ }
452
+ />
453
+ <ModalTabs
454
+ tabLabels={[
455
+ 'Author Details',
456
+ ...(onOpenAffiliationsModal ? ['Affiliations'] : []),
457
+ 'Contributions',
458
+ ]}
459
+ tabErrorIndicators={[
460
+ authorHasError,
461
+ ...(onOpenAffiliationsModal ? [false] : []),
462
+ false,
463
+ ]}
464
+ tabWarningIndicators={[
465
+ authorDetailsUnsavedContinue && !authorHasError,
466
+ ...(onOpenAffiliationsModal ? [false] : []),
467
+ false,
468
+ ]}
469
+ />
470
+ <InspectorTabPanels>
471
+ <AuthorTabPanel>
472
+ <AuthorDetailsForm
473
+ values={normalizeAuthor(selection)}
474
+ onChange={changeAuthor}
475
+ onSave={saveAuthor}
476
+ actionsRef={actionsRef}
477
+ isEmailRequired={isEmailRequired}
478
+ selectedAffiliations={selectedAffiliations.map(
479
+ (a) => a.id
480
+ )}
481
+ authorFormRef={authorFormRef}
482
+ selectedCreditRoles={selectedCreditRoles}
483
+ newEntity={newEntity}
484
+ onAuthorDetailsTabErrorChange={
485
+ setAuthorDetailsTabHasError
486
+ }
487
+ unsavedContinueActive={authorDetailsUnsavedContinue}
488
+ requiredContinueActive={authorDetailsRequiredContinue}
489
+ />
490
+ </AuthorTabPanel>
491
+ {onOpenAffiliationsModal && (
492
+ <AuthorTabPanel>
493
+ <AffiliationsPanel
494
+ items={affiliations}
495
+ selectedItems={selectedAffiliations}
496
+ onSelect={selectAffiliation}
497
+ onOpenAffiliationsModal={onOpenAffiliationsModal}
498
+ />
499
+ </AuthorTabPanel>
500
+ )}
501
+ <AuthorTabPanel>
502
+ <ContributionsDescriptionSubtitle>
503
+ Select the roles this author contributed to according to
504
+ the CRediT taxonomy
505
+ </ContributionsDescriptionSubtitle>
506
+ <CreditContributionsCheckboxes
507
+ items={vocabTermItems}
508
+ selectedItems={selectedCreditRoles.map((r) => ({
509
+ id: r.vocabTerm,
510
+ }))}
511
+ onSelect={selectCreditRole}
512
+ />
513
+ </AuthorTabPanel>
514
+ </InspectorTabPanels>
515
+ </AuthorTabs>
516
+ <ConfirmationDialog
517
+ isOpen={showRequiredFieldConfirmationDialog}
518
+ onPrimary={() => {
519
+ setShowRequiredFieldConfirmationDialog(false)
520
+ setNextAuthor(null)
521
+ setAuthorDetailsRequiredContinue(true)
522
+ }}
523
+ onSecondary={cancel}
524
+ type={DialogType.REQUIRED}
525
+ entityType="author"
526
+ />
527
+ <ConfirmationDialog
528
+ isOpen={showConfirmationDialog}
529
+ onPrimary={() => {
530
+ setShowConfirmationDialog(false)
531
+ setNextAuthor(null)
532
+ setAuthorDetailsUnsavedContinue(true)
533
+ }}
534
+ onSecondary={cancel}
535
+ type={DialogType.SAVE}
536
+ entityType="author"
537
+ />
538
+ </>
539
+ ) : (
540
+ <FormPlaceholder
541
+ type="author"
542
+ title="Author Details"
543
+ message="Select an author from the list to display their details here."
544
+ placeholderIcon={<AuthorPlaceholderIcon />}
545
+ />
546
+ )}
547
+ </StyledScrollableModalContent>
548
+ </StyledModalBody>
549
+ <FormFooter
550
+ onCancel={close}
551
+ primaryAction={
552
+ selection ? (
553
+ <ModalFormSaveButton
554
+ form="author-details-form"
555
+ isDisableSave={isDisableSave}
556
+ onSubmitForm={() => actionsRef.current?.submitForm?.()}
557
+ />
558
+ ) : undefined
559
+ }
560
+ />
561
+ </ModalContainer>
562
+ </StyledModal>
563
+ )
564
+ }
565
+
566
+ function createEmptyAuthor(priority: number): ContributorAttrs {
567
+ return {
568
+ id: generateNodeID(schema.nodes.contributor),
569
+ role: '',
570
+ affiliationIDs: [],
571
+ degrees: [],
572
+ given: '',
573
+ family: '',
574
+ email: '',
575
+ suffix: '',
576
+ isCorresponding: false,
577
+ ORCID: '',
578
+ priority,
579
+ isJointContributor: false,
580
+ correspIDs: [],
581
+ footnoteIDs: [],
582
+ prefix: '',
583
+ creditRoles: [],
584
+ }
585
+ }
586
+
587
+ const AddAuthorButton = styled.button`
588
+ background: none;
589
+ border: none;
590
+ margin: 0;
591
+ font: inherit;
592
+ color: inherit;
593
+ width: 100%;
594
+ display: flex;
595
+ align-items: center;
596
+ padding: 12px 8px 12px 12px;
597
+ cursor: pointer;
598
+ &[data-active='true'] {
599
+ background: ${(props) => props.theme.colors.background.fifth};
600
+ border: 1px solid ${(props) => props.theme.colors.border.primary};
601
+ padding: 11px 8px 11px 12px;
602
+ border-left: 0;
603
+ border-right: 0;
604
+ }
605
+ ${outlineStyle}
606
+ `
607
+
608
+ const ActionTitle = styled.div`
609
+ padding-left: ${(props) => props.theme.grid.unit * 2}px;
610
+ `
611
+
612
+ const AuthorTabs = styled(InspectorTabs)`
613
+ position: relative;
614
+ `
615
+ const AuthorTabPanel = styled(InspectorTabPanel).attrs({
616
+ tabIndex: -1,
617
+ unmount: false,
618
+ })`
619
+ margin-top: ${(props) => props.theme.grid.unit * 4}px;
620
+ `
621
+
622
+ const ContributionsDescriptionSubtitle = styled(FormSubtitle)`
623
+ font-size: ${(props) => props.theme.font.size.small};
624
+ line-height: ${(props) => props.theme.font.lineHeight.normal};
625
+ `
626
+
627
+ const StyledSidebarContent = styled(SidebarContent)`
628
+ padding: 8px;
629
+ `
630
+
631
+ const StyledModalBody = styled(ModalBody)`
632
+ position: relative;
633
+ height: calc(90vh - 40px);
634
+ `
635
+
636
+ const StyledModalSidebarHeader = styled(ModalSidebarHeader)`
637
+ margin-bottom: 12px;
638
+ `
639
+
640
+ const StyledScrollableModalContent = styled(ScrollableModalContent)`
641
+ padding: 45px 16px 16px;
642
+ `
@@ -0,0 +1,69 @@
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 { InfoCircleIcon } from '@manuscripts/style-guide'
17
+ import React from 'react'
18
+
19
+ import {
20
+ GenericPanel,
21
+ ListItem,
22
+ ListItems,
23
+ useListSelectedIds,
24
+ } from '../authors-affiliations/GenericPanel'
25
+
26
+ export interface AuthorsPanelProps {
27
+ items: { id: string; label: string }[]
28
+ selectedItems?: { id: string }[]
29
+ onSelect: (id: string) => void
30
+ openAuthorsModal: () => void
31
+ }
32
+
33
+ export const AuthorsPanel: React.FC<AuthorsPanelProps> = ({
34
+ items,
35
+ selectedItems = [],
36
+ onSelect,
37
+ openAuthorsModal,
38
+ }) => {
39
+ const selectedIds = useListSelectedIds(selectedItems)
40
+
41
+ return (
42
+ <GenericPanel
43
+ createLabel="New Author"
44
+ onCreate={openAuthorsModal}
45
+ createDataCy="add-authors-link"
46
+ emptyDataCy="authors-panel-empty"
47
+ isEmpty={items.length === 0}
48
+ emptyIcon={<InfoCircleIcon />}
49
+ emptyMessage={
50
+ <>
51
+ There are no authors attributed yet!
52
+ <br />
53
+ Click &lsquo;+ New Author&rsquo;
54
+ </>
55
+ }
56
+ >
57
+ <ListItems data-cy="authors-panel">
58
+ {items.map((item) => (
59
+ <ListItem
60
+ key={item.id}
61
+ selected={selectedIds.has(item.id)}
62
+ onClick={() => onSelect(item.id)}
63
+ primary={item.label}
64
+ />
65
+ ))}
66
+ </ListItems>
67
+ </GenericPanel>
68
+ )
69
+ }