@manuscripts/body-editor 3.12.66 → 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,284 @@
1
+ /*!
2
+ * © 2023 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 { Category, Dialog, PlusIcon } from '@manuscripts/style-guide'
18
+ import { isDeleted } from '@manuscripts/track-changes-plugin'
19
+ import {
20
+ generateNodeID,
21
+ ManuscriptEditorView,
22
+ ManuscriptNode,
23
+ schema,
24
+ } from '@manuscripts/transform'
25
+
26
+ import { TextSelection } from 'prosemirror-state'
27
+ import React, {
28
+ ChangeEvent,
29
+ useCallback,
30
+ useEffect,
31
+ useRef,
32
+ useState,
33
+ } from 'react'
34
+ import styled from 'styled-components'
35
+
36
+ const AddNewKeyword = styled.div`
37
+ position: relative;
38
+ display: inline-block;
39
+ `
40
+
41
+ const KeywordField = styled.input`
42
+ font-size: 13px;
43
+ margin: 0;
44
+ padding: 0;
45
+ border: none;
46
+ height: 16px;
47
+ width: 100px;
48
+
49
+ &:focus {
50
+ outline: none;
51
+ }
52
+ `
53
+
54
+ const NewKeywordButton = styled.button`
55
+ font-size: 13px;
56
+ color: #6e6e6e;
57
+ background: transparent;
58
+ border: none;
59
+ margin: 0;
60
+ padding: 0;
61
+ cursor: pointer;
62
+
63
+ &:focus-visible {
64
+ outline: 4px solid ${(props) => props.theme.colors.outline.focus};
65
+ outline-offset: 2px;
66
+ border-radius: 3px;
67
+ }
68
+ `
69
+
70
+ const CreateKeywordButtonWrapper = styled.div`
71
+ position: absolute;
72
+ left: 0;
73
+ top: 24px;
74
+ background: #fff;
75
+ border: 1px solid #e2e2e2;
76
+ border-radius: 8px;
77
+ padding: 8px 0;
78
+ `
79
+
80
+ const CreateKeywordButton = styled.button`
81
+ font-size: 12px;
82
+ white-space: nowrap;
83
+ color: #353535;
84
+ background: #f2fbfc;
85
+ border: 1px solid #f2f2f2;
86
+ border-width: 1px 0;
87
+ padding: 9px 13px;
88
+ cursor: pointer;
89
+ `
90
+
91
+ const PlusIconWrapper = styled.span`
92
+ padding: 0 8px 0 0;
93
+ `
94
+
95
+ interface KeywordEntry {
96
+ id: string
97
+ contents: string
98
+ }
99
+
100
+ export const AddKeywordInline: React.FC<{
101
+ viewProps: {
102
+ view: ManuscriptEditorView
103
+ getPos: () => number
104
+ node: ManuscriptNode
105
+ }
106
+ getUpdatedNode: () => ManuscriptNode
107
+ }> = ({ viewProps, getUpdatedNode }) => {
108
+ const nodeRef = useRef<HTMLDivElement>(null)
109
+ const [newKeyword, setNewKeyword] = useState<string>('')
110
+ const [isAddingNewKeyword, setIsAddingNewKeyword] = useState<boolean>(false)
111
+ const [isExistingKeywordError, setIsExistingKeywordError] =
112
+ useState<boolean>(false)
113
+ const { getPos, view } = viewProps
114
+ const getKeywords = (node: ManuscriptNode) => {
115
+ const keywords: KeywordEntry[] = []
116
+ node.content.descendants((descNode) => {
117
+ if (
118
+ descNode.type === descNode.type.schema.nodes.keyword &&
119
+ !isDeleted(descNode)
120
+ ) {
121
+ keywords.push({
122
+ id: descNode.attrs.id,
123
+ contents: descNode.textContent,
124
+ })
125
+ }
126
+ })
127
+ return keywords
128
+ }
129
+
130
+ const handleClickOutside = useCallback(
131
+ (event: Event) => {
132
+ if (
133
+ nodeRef.current &&
134
+ !nodeRef.current.contains(event.target as Node) &&
135
+ isAddingNewKeyword
136
+ ) {
137
+ handleCancel()
138
+ }
139
+ },
140
+ [isAddingNewKeyword]
141
+ )
142
+
143
+ useEffect(() => {
144
+ if (isAddingNewKeyword) {
145
+ document.addEventListener('mousedown', handleClickOutside)
146
+ } else {
147
+ document.removeEventListener('mousedown', handleClickOutside)
148
+ }
149
+
150
+ return () => {
151
+ document.removeEventListener('mousedown', handleClickOutside)
152
+ }
153
+ }, [handleClickOutside, isAddingNewKeyword])
154
+
155
+ const isValidNewKeyword = () => {
156
+ return newKeyword.trim().length > 0
157
+ }
158
+
159
+ const isExistingKeyword = () => {
160
+ const keywordClean = newKeyword.trim()
161
+ const keywords: KeywordEntry[] = getKeywords(getUpdatedNode())
162
+ return keywords.some((keyword) => keyword.contents === keywordClean)
163
+ }
164
+
165
+ const handleCancel = () => {
166
+ setIsAddingNewKeyword(false)
167
+ setNewKeyword('')
168
+ }
169
+
170
+ const handleAddKeyword = () => {
171
+ if (!isExistingKeyword() && isValidNewKeyword()) {
172
+ const node = getUpdatedNode()
173
+ const keywordNode = schema.nodes.keyword.create(
174
+ {
175
+ id: generateNodeID(schema.nodes.keyword),
176
+ },
177
+ node.type.schema.text(newKeyword)
178
+ )
179
+ const nodePosition = getPos() + node.nodeSize - 1
180
+ view.dispatch(view.state.tr.insert(nodePosition, keywordNode))
181
+
182
+ setIsAddingNewKeyword(false)
183
+ setNewKeyword('')
184
+ } else {
185
+ handleCancel()
186
+ setIsExistingKeywordError(true)
187
+ }
188
+ }
189
+
190
+ const KeywordInput: React.FC = () => {
191
+ const handleInputChange = (event: ChangeEvent<HTMLInputElement>) => {
192
+ setNewKeyword(event.target.value.trimStart())
193
+ return
194
+ }
195
+
196
+ return (
197
+ <KeywordField
198
+ value={newKeyword}
199
+ onChange={handleInputChange}
200
+ onFocus={() =>
201
+ view.dispatch(
202
+ view.state.tr.setSelection(TextSelection.create(view.state.doc, 0))
203
+ )
204
+ }
205
+ onKeyDown={(e) => {
206
+ e.stopPropagation()
207
+ if (e.key === 'Enter') {
208
+ e.preventDefault() // Prevent focus loss
209
+ handleAddKeyword()
210
+ } else if (e.key === 'Escape') {
211
+ handleCancel()
212
+ }
213
+ }}
214
+ onKeyUp={(e) => {
215
+ e.stopPropagation()
216
+ }}
217
+ onKeyPress={(e) => {
218
+ e.stopPropagation()
219
+ }}
220
+ autoFocus
221
+ />
222
+ )
223
+ }
224
+
225
+ const CreateKeywordButtonElement: React.FC = () => {
226
+ return (
227
+ <CreateKeywordButtonWrapper>
228
+ <CreateKeywordButton
229
+ onClick={() => {
230
+ handleAddKeyword()
231
+ }}
232
+ >
233
+ <PlusIconWrapper>
234
+ <PlusIcon />
235
+ </PlusIconWrapper>
236
+ {`Create keyword "${newKeyword}"`}
237
+ </CreateKeywordButton>
238
+ </CreateKeywordButtonWrapper>
239
+ )
240
+ }
241
+
242
+ const actions = {
243
+ primary: {
244
+ action: () => {
245
+ setIsExistingKeywordError(false)
246
+ },
247
+ title: 'OK',
248
+ },
249
+ }
250
+
251
+ return (
252
+ <AddNewKeyword ref={nodeRef}>
253
+ {!isAddingNewKeyword && (
254
+ <NewKeywordButton
255
+ tabIndex={-1}
256
+ className="keyword-add"
257
+ onClick={(e) => {
258
+ e.preventDefault()
259
+ setIsAddingNewKeyword(true)
260
+ }}
261
+ onKeyDown={(e) => {
262
+ if (e.key === 'Enter') {
263
+ e.preventDefault()
264
+ setIsAddingNewKeyword(true)
265
+ }
266
+ }}
267
+ >
268
+ New keyword...
269
+ </NewKeywordButton>
270
+ )}
271
+ {isAddingNewKeyword && <KeywordInput />}
272
+ {isAddingNewKeyword && isValidNewKeyword() && (
273
+ <CreateKeywordButtonElement />
274
+ )}
275
+ <Dialog
276
+ isOpen={isExistingKeywordError}
277
+ actions={actions}
278
+ category={Category.confirmation}
279
+ header={'Keyword already exists'}
280
+ message={`You can’t add this keyword because it already exists. You can add another one.`}
281
+ />
282
+ </AddNewKeyword>
283
+ )
284
+ }
@@ -0,0 +1,52 @@
1
+ /*!
2
+ * © 2023 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 { Category, Dialog } from '@manuscripts/style-guide'
17
+ import React, { useState } from 'react'
18
+
19
+ export interface DeleteKeywordDialogProps {
20
+ keyword: string
21
+ handleDelete: () => void
22
+ }
23
+
24
+ export const DeleteKeywordDialog: React.FC<DeleteKeywordDialogProps> = ({
25
+ keyword,
26
+ handleDelete,
27
+ }) => {
28
+ const [isOpen, setOpen] = useState(true)
29
+
30
+ return (
31
+ <Dialog
32
+ isOpen={isOpen}
33
+ actions={{
34
+ primary: {
35
+ action: () => setOpen(false),
36
+ title: 'Cancel',
37
+ },
38
+ secondary: {
39
+ action: () => {
40
+ setOpen(false)
41
+ handleDelete()
42
+ },
43
+ title: 'Delete',
44
+ },
45
+ }}
46
+ category={Category.confirmation}
47
+ header={'Delete Keyword'}
48
+ message={`Are you sure you want to delete "${keyword}" keyword?
49
+ `}
50
+ />
51
+ )
52
+ }
@@ -0,0 +1,65 @@
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
+ import {
17
+ AddedIcon,
18
+ AddIcon,
19
+ Drawer,
20
+ DrawerIcon,
21
+ DrawerItemLabel,
22
+ DrawerItemsList,
23
+ DrawerLabelContainer,
24
+ DrawerListItem,
25
+ DrawerProps,
26
+ } from '@manuscripts/style-guide'
27
+ import React from 'react'
28
+
29
+ interface GenericDrawerProps {
30
+ items: { id: string; label: string }[]
31
+ selectedItems: {
32
+ id: string
33
+ }[]
34
+ onSelect: (id: string) => void
35
+ }
36
+
37
+ export const GenericDrawer: React.FC<
38
+ GenericDrawerProps & Omit<DrawerProps, 'children'>
39
+ > = ({ items, selectedItems = [], onSelect, ...drawerProps }) => {
40
+ return (
41
+ <Drawer {...drawerProps}>
42
+ <DrawerItemsList>
43
+ {items.map((item) => (
44
+ <DrawerListItem
45
+ data-cy="item"
46
+ key={item.id}
47
+ selected={selectedItems?.map((a) => a.id).includes(item.id)}
48
+ onClick={() => onSelect(item.id)}
49
+ >
50
+ <DrawerIcon>
51
+ {selectedItems?.map((a) => a.id).includes(item.id) ? (
52
+ <AddedIcon width={22} height={22} />
53
+ ) : (
54
+ <AddIcon width={22} height={22} />
55
+ )}
56
+ </DrawerIcon>
57
+ <DrawerLabelContainer>
58
+ <DrawerItemLabel>{item.label}</DrawerItemLabel>
59
+ </DrawerLabelContainer>
60
+ </DrawerListItem>
61
+ ))}
62
+ </DrawerItemsList>
63
+ </Drawer>
64
+ )
65
+ }
@@ -0,0 +1,125 @@
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
+ DrawerProps,
19
+ outlineStyle,
20
+ SelectedItemsBox,
21
+ IconButton,
22
+ } from '@manuscripts/style-guide'
23
+ import React from 'react'
24
+ import styled from 'styled-components'
25
+
26
+ import { PartialExcept } from '../../types'
27
+
28
+ type Base = {
29
+ id: string
30
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
31
+ } & Record<string, any>
32
+
33
+ interface DrawerGroupProps<T extends Base> {
34
+ removeItem: (id: string) => void
35
+ selectedItems: PartialExcept<T, 'id'>[]
36
+ items: T[]
37
+ showDrawer: boolean
38
+ setShowDrawer: (on: boolean) => void
39
+ onSelect: (id: string) => void
40
+ title: string
41
+ labelField: keyof T
42
+ Drawer: React.ComponentType<
43
+ EmbeddedDrawerProps<T> & Omit<DrawerProps, 'children'>
44
+ >
45
+ cy: string
46
+ buttonText: string
47
+ Icon?: React.ReactNode
48
+ }
49
+
50
+ interface EmbeddedDrawerProps<T extends Base> {
51
+ items: T[]
52
+ selectedItems: DrawerGroupProps<T>['selectedItems']
53
+ onSelect: (id: string) => void
54
+ }
55
+
56
+ export function DrawerGroup<T extends Base>({
57
+ removeItem,
58
+ selectedItems,
59
+ onSelect,
60
+ items,
61
+ showDrawer,
62
+ setShowDrawer,
63
+ title,
64
+ labelField,
65
+ Drawer,
66
+ cy,
67
+ Icon,
68
+ buttonText,
69
+ }: DrawerGroupProps<T>) {
70
+ return (
71
+ <>
72
+ <AssignButton
73
+ onClick={() => setShowDrawer(true)}
74
+ data-cy={cy + 'assign-button'}
75
+ >
76
+ {Icon}
77
+ {buttonText}
78
+ </AssignButton>
79
+ <SelectedItemsBox
80
+ data-cy={cy + '-selected-items'}
81
+ items={selectedItems.map((i) => ({
82
+ id: String(i.id),
83
+ label: String((i as T)[labelField as keyof T] ?? ''),
84
+ }))}
85
+ onRemove={removeItem}
86
+ placeholder={`No ${title} assigned`}
87
+ />
88
+ {showDrawer && (
89
+ <Drawer
90
+ items={items}
91
+ selectedItems={selectedItems}
92
+ title={title}
93
+ onSelect={onSelect}
94
+ onBack={() => setShowDrawer(false)}
95
+ width="100%"
96
+ />
97
+ )}
98
+ </>
99
+ )
100
+ }
101
+
102
+ export const AssignButton = styled(IconButton)`
103
+ color: ${(props) => props.theme.colors.brand.default};
104
+ background: none;
105
+ border: none;
106
+ cursor: pointer;
107
+ padding: 0;
108
+ font: ${(props) => props.theme.font.weight.normal}
109
+ ${(props) => props.theme.font.size.normal}
110
+ ${(props) => props.theme.font.family.sans};
111
+ gap: 4px;
112
+ width: auto;
113
+ height: 24px;
114
+
115
+ &:disabled {
116
+ color: #c9c9c9 !important;
117
+ background-color: unset !important;
118
+ border: unset;
119
+ }
120
+
121
+ &:hover {
122
+ opacity: 0.8;
123
+ }
124
+ ${outlineStyle}
125
+ `