@manuscripts/body-editor 3.16.0 → 3.16.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/dist/cjs/commands.js +45 -10
  2. package/dist/cjs/components/form/FormFooter.js +2 -2
  3. package/dist/cjs/components/keyboard-shortcuts-modal/KeyboardShortcutsModal.js +4 -2
  4. package/dist/cjs/components/references/CitationEditor.js +13 -19
  5. package/dist/cjs/components/references/ImportBibliography.js +77 -0
  6. package/dist/cjs/components/references/ImportBibliographyForm.js +1 -1
  7. package/dist/cjs/components/references/ImportBibliographyModal.js +12 -81
  8. package/dist/cjs/components/references/ImportReferencesConfirmation.js +113 -0
  9. package/dist/cjs/components/references/ImportSuccessPlaceholder.js +56 -0
  10. package/dist/cjs/components/references/ReferenceSearch.js +2 -5
  11. package/dist/cjs/components/references/ReferencesEditor.js +18 -2
  12. package/dist/cjs/components/references/ReferencesModal.js +86 -33
  13. package/dist/cjs/components/views/CrossReferenceItems.js +139 -52
  14. package/dist/cjs/components/views/DeleteSupplementDialog.js +1 -1
  15. package/dist/cjs/lib/supplements.js +9 -1
  16. package/dist/cjs/lib/view.js +39 -1
  17. package/dist/cjs/menus.js +35 -0
  18. package/dist/cjs/plugins/objects.js +1 -1
  19. package/dist/cjs/toolbar.js +6 -0
  20. package/dist/cjs/versions.js +1 -1
  21. package/dist/cjs/views/bibliography_element.js +1 -2
  22. package/dist/cjs/views/citation_editable.js +2 -29
  23. package/dist/cjs/views/cross_reference.js +12 -2
  24. package/dist/cjs/views/cross_reference_editable.js +95 -4
  25. package/dist/cjs/views/figure_editable.js +3 -0
  26. package/dist/es/commands.js +43 -9
  27. package/dist/es/components/form/FormFooter.js +2 -2
  28. package/dist/es/components/keyboard-shortcuts-modal/KeyboardShortcutsModal.js +4 -2
  29. package/dist/es/components/references/CitationEditor.js +13 -19
  30. package/dist/es/components/references/ImportBibliography.js +70 -0
  31. package/dist/es/components/references/ImportBibliographyForm.js +1 -1
  32. package/dist/es/components/references/ImportBibliographyModal.js +13 -79
  33. package/dist/es/components/references/ImportReferencesConfirmation.js +73 -0
  34. package/dist/es/components/references/ImportSuccessPlaceholder.js +49 -0
  35. package/dist/es/components/references/ReferenceSearch.js +3 -6
  36. package/dist/es/components/references/ReferencesEditor.js +19 -3
  37. package/dist/es/components/references/ReferencesModal.js +88 -35
  38. package/dist/es/components/views/CrossReferenceItems.js +141 -54
  39. package/dist/es/components/views/DeleteSupplementDialog.js +2 -2
  40. package/dist/es/lib/supplements.js +8 -1
  41. package/dist/es/lib/view.js +36 -0
  42. package/dist/es/menus.js +36 -1
  43. package/dist/es/plugins/objects.js +1 -1
  44. package/dist/es/toolbar.js +8 -2
  45. package/dist/es/versions.js +1 -1
  46. package/dist/es/views/bibliography_element.js +2 -3
  47. package/dist/es/views/citation_editable.js +3 -30
  48. package/dist/es/views/cross_reference.js +12 -2
  49. package/dist/es/views/cross_reference_editable.js +96 -5
  50. package/dist/es/views/figure_editable.js +3 -0
  51. package/dist/types/commands.d.ts +1 -0
  52. package/dist/types/components/form/FormFooter.d.ts +2 -1
  53. package/dist/types/components/references/CitationEditor.d.ts +1 -1
  54. package/dist/types/components/references/ImportBibliography.d.ts +7 -0
  55. package/dist/types/components/references/ImportReferencesConfirmation.d.ts +8 -0
  56. package/dist/types/components/references/ImportSuccessPlaceholder.d.ts +5 -0
  57. package/dist/types/components/references/ReferenceSearch.d.ts +0 -1
  58. package/dist/types/components/references/ReferencesEditor.d.ts +4 -1
  59. package/dist/types/components/references/ReferencesModal.d.ts +1 -0
  60. package/dist/types/components/views/CrossReferenceItems.d.ts +3 -0
  61. package/dist/types/lib/supplements.d.ts +1 -0
  62. package/dist/types/lib/view.d.ts +3 -1
  63. package/dist/types/versions.d.ts +1 -1
  64. package/dist/types/views/citation_editable.d.ts +0 -1
  65. package/dist/types/views/cross_reference_editable.d.ts +6 -0
  66. package/package.json +4 -4
  67. package/src/commands.ts +52 -10
  68. package/src/components/ChangeHandlingForm.tsx +4 -2
  69. package/src/components/form/FormFooter.tsx +3 -1
  70. package/src/components/keyboard-shortcuts-modal/KeyboardShortcutsModal.tsx +6 -7
  71. package/src/components/references/CitationEditor.tsx +16 -25
  72. package/src/components/references/ImportBibliography.tsx +119 -0
  73. package/src/components/references/ImportBibliographyForm.tsx +1 -1
  74. package/src/components/references/ImportBibliographyModal.tsx +34 -110
  75. package/src/components/references/ImportReferencesConfirmation.tsx +133 -0
  76. package/src/components/references/ImportSuccessPlaceholder.tsx +93 -0
  77. package/src/components/references/ReferenceSearch.tsx +0 -7
  78. package/src/components/references/ReferencesEditor.tsx +35 -6
  79. package/src/components/references/ReferencesModal.tsx +164 -78
  80. package/src/components/toolbar/InsertEmbedDialog.tsx +6 -2
  81. package/src/components/toolbar/type-selector/styles.ts +2 -1
  82. package/src/components/views/CrossReferenceItems.tsx +227 -100
  83. package/src/components/views/DeleteSupplementDialog.tsx +3 -6
  84. package/src/lib/footnotes.ts +3 -1
  85. package/src/lib/supplements.ts +13 -1
  86. package/src/lib/utils.ts +15 -4
  87. package/src/lib/view.ts +65 -0
  88. package/src/menus.tsx +39 -0
  89. package/src/plugins/add-subtitle.ts +18 -16
  90. package/src/plugins/objects.ts +2 -6
  91. package/src/toolbar.tsx +9 -0
  92. package/src/versions.ts +1 -1
  93. package/src/views/bibliography_element.ts +3 -4
  94. package/src/views/citation_editable.ts +5 -50
  95. package/src/views/cross_reference.ts +17 -3
  96. package/src/views/cross_reference_editable.ts +123 -11
  97. package/src/views/figure_editable.ts +4 -0
  98. package/src/views/supplement.ts +0 -1
  99. package/src/views/supplements.ts +2 -2
@@ -0,0 +1,133 @@
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 {
17
+ CheckboxField,
18
+ CheckboxLabel,
19
+ FormActionsBar,
20
+ ModalTitle,
21
+ PrimaryButton,
22
+ SecondaryButton,
23
+ } from '@manuscripts/style-guide'
24
+ import { BibliographyItemAttrs } from '@manuscripts/transform'
25
+ import React, { useState } from 'react'
26
+ import styled from 'styled-components'
27
+
28
+ import { ReferenceLine } from './ReferenceLine'
29
+
30
+ export interface ImportReferencesConfirmationProps {
31
+ items: BibliographyItemAttrs[]
32
+ onCancel: () => void
33
+ onConfirm: (items: BibliographyItemAttrs[]) => void
34
+ }
35
+
36
+ export const ImportReferencesConfirmation: React.FC<
37
+ ImportReferencesConfirmationProps
38
+ > = ({ items, onCancel, onConfirm }) => {
39
+ const [selected, setSelected] = useState(() => items.map(() => true))
40
+
41
+ const selectedCount = selected.filter(Boolean).length
42
+ const countLabel = selectedCount === 1 ? 'reference' : 'references'
43
+
44
+ const toggleItem = (index: number) => {
45
+ setSelected((current) =>
46
+ current.map((value, i) => (i === index ? !value : value))
47
+ )
48
+ }
49
+
50
+ const handleConfirm = () => {
51
+ onConfirm(items.filter((_, index) => selected[index]))
52
+ }
53
+
54
+ return (
55
+ <>
56
+ <Title>Import References</Title>
57
+ <Subtitle>
58
+ Importing {selectedCount} {countLabel}
59
+ </Subtitle>
60
+ <List>
61
+ {items.map((item, index) => (
62
+ <ListItem key={index}>
63
+ <ItemCheckbox>
64
+ <CheckboxField
65
+ checked={selected[index]}
66
+ aria-label={`Select reference ${index + 1}`}
67
+ onChange={() => toggleItem(index)}
68
+ />
69
+ <div />
70
+ </ItemCheckbox>
71
+ <ReferenceLine item={item} />
72
+ </ListItem>
73
+ ))}
74
+ </List>
75
+ <FormActionsBar>
76
+ <SecondaryButton type="button" onClick={onCancel}>
77
+ Cancel
78
+ </SecondaryButton>
79
+ <PrimaryButton
80
+ type="button"
81
+ disabled={selectedCount === 0}
82
+ onClick={handleConfirm}
83
+ >
84
+ Import {selectedCount} {countLabel}
85
+ </PrimaryButton>
86
+ </FormActionsBar>
87
+ </>
88
+ )
89
+ }
90
+
91
+ const Title = styled(ModalTitle)`
92
+ border-bottom: 1px solid ${(props) => props.theme.colors.border.secondary};
93
+ margin: 0 -${(props) => 6 * props.theme.grid.unit}px 20px;
94
+ padding: 0 ${(props) => 6 * props.theme.grid.unit}px
95
+ ${(props) => props.theme.grid.unit * 4}px;
96
+ `
97
+
98
+ const Subtitle = styled.p`
99
+ color: ${(props) => props.theme.colors.text.secondary};
100
+ font-weight: ${(props) => props.theme.font.weight.bold};
101
+ margin: 0 0 16px;
102
+ font-size: 14px;
103
+ `
104
+
105
+ const List = styled.div`
106
+ display: flex;
107
+ flex-direction: column;
108
+ margin-bottom: ${(props) => props.theme.grid.unit * 5}px;
109
+ overflow-y: auto;
110
+ `
111
+
112
+ const ListItem = styled.div`
113
+ border-bottom: 1px solid ${(props) => props.theme.colors.border.secondary};
114
+ display: flex;
115
+ align-items: flex-start;
116
+ gap: ${(props) => props.theme.grid.unit * 3}px;
117
+ padding: ${(props) => props.theme.grid.unit * 3}px 0;
118
+ `
119
+
120
+ const ItemCheckbox = styled(CheckboxLabel)`
121
+ flex-shrink: 0;
122
+
123
+ > div {
124
+ margin: 0;
125
+
126
+ &::before {
127
+ width: 18px;
128
+ height: 18px;
129
+ border-radius: 4px;
130
+ margin: 0;
131
+ }
132
+ }
133
+ `
@@ -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 React from 'react'
17
+ import styled from 'styled-components'
18
+
19
+ export interface ImportSuccessPlaceholderProps {
20
+ count: number
21
+ }
22
+
23
+ export const ImportSuccessPlaceholder: React.FC<
24
+ ImportSuccessPlaceholderProps
25
+ > = ({ count }) => {
26
+ const countLabel = count === 1 ? 'reference' : 'references'
27
+
28
+ return (
29
+ <Container data-cy="import-success-placeholder">
30
+ <IconCircle>
31
+ <SuccessCheckIcon />
32
+ </IconCircle>
33
+ <Title>Successfully Imported</Title>
34
+ <Message>
35
+ Your library has been updated with {count} new {countLabel}.
36
+ </Message>
37
+ </Container>
38
+ )
39
+ }
40
+
41
+ const SuccessCheckIcon = () => (
42
+ <svg
43
+ width="32"
44
+ height="32"
45
+ viewBox="0 0 32 32"
46
+ fill="none"
47
+ xmlns="http://www.w3.org/2000/svg"
48
+ aria-hidden="true"
49
+ >
50
+ <path
51
+ d="M26.6656 8L12.0004 22.6656L5.3344 15.9994"
52
+ stroke="#36B260"
53
+ strokeWidth="3"
54
+ strokeLinecap="round"
55
+ />
56
+ </svg>
57
+ )
58
+
59
+ const Container = styled.div`
60
+ display: flex;
61
+ flex-direction: column;
62
+ align-items: center;
63
+ justify-content: center;
64
+ gap: ${(props) => props.theme.grid.unit * 4}px;
65
+ height: 100%;
66
+ padding: 40px;
67
+ box-sizing: border-box;
68
+ text-align: center;
69
+ `
70
+
71
+ const IconCircle = styled.div`
72
+ display: flex;
73
+ align-items: center;
74
+ justify-content: center;
75
+ width: 64px;
76
+ height: 64px;
77
+ border-radius: 32px;
78
+ background: #f6ffed;
79
+ `
80
+
81
+ const Title = styled.h2`
82
+ margin: 0;
83
+ font-size: 24px;
84
+ line-height: 32px;
85
+ letter-spacing: -0.37px;
86
+ color: ${(props) => props.theme.colors.text.primary};
87
+ `
88
+
89
+ const Message = styled.p`
90
+ margin: 0;
91
+ line-height: ${(props) => props.theme.font.lineHeight.large};
92
+ color: ${(props) => props.theme.colors.text.secondary};
93
+ `
@@ -20,7 +20,6 @@ import {
20
20
  IconTextButton,
21
21
  PrimaryButton,
22
22
  SecondaryButton,
23
- UploadIcon,
24
23
  withFocusTrap,
25
24
  } from '@manuscripts/style-guide'
26
25
  import { BibliographyItemAttrs } from '@manuscripts/transform'
@@ -74,7 +73,6 @@ export const ReferenceSearch: React.FC<{
74
73
  sources: BibliographyItemSource[]
75
74
  items: BibliographyItemAttrs[]
76
75
  onAdd: () => void
77
- onImport: () => void
78
76
  onCite: (items: BibliographyItemAttrs[]) => void
79
77
  onCancel: () => void
80
78
  }> = ({
@@ -82,7 +80,6 @@ export const ReferenceSearch: React.FC<{
82
80
  sources,
83
81
  items,
84
82
  onAdd,
85
- onImport,
86
83
  onCite,
87
84
  onCancel,
88
85
  }) => {
@@ -146,10 +143,6 @@ export const ReferenceSearch: React.FC<{
146
143
  <AddNewIcon />
147
144
  Add new
148
145
  </IconTextButton>
149
- <IconTextButton onClick={onImport}>
150
- <UploadIcon />
151
- Import new
152
- </IconTextButton>
153
146
  </AddReferenceActions>
154
147
  <ButtonGroup>
155
148
  <SecondaryButton onClick={onCancel}>Close</SecondaryButton>
@@ -13,8 +13,12 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  */
16
- import { BibliographyItemAttrs } from '@manuscripts/transform'
17
- import React, { useReducer, useState } from 'react'
16
+ import {
17
+ BibliographyItemAttrs,
18
+ generateNodeID,
19
+ schema,
20
+ } from '@manuscripts/transform'
21
+ import React, { useEffect, useReducer, useState } from 'react'
18
22
 
19
23
  import { attrsReducer } from '../../lib/array-reducer'
20
24
  import { cleanItemValues } from '../../lib/utils'
@@ -22,18 +26,25 @@ import { ReferencesModal, ReferencesModalProps } from './ReferencesModal'
22
26
 
23
27
  export type ReferencesEditorProps = Omit<
24
28
  ReferencesModalProps,
25
- 'isOpen' | 'onCancel'
26
- >
29
+ 'isOpen' | 'onCancel' | 'handleImport' | 'onSave'
30
+ > & {
31
+ onSave: (item: BibliographyItemAttrs[]) => void
32
+ }
27
33
 
28
34
  const itemsReducer = attrsReducer<BibliographyItemAttrs>()
29
35
 
30
36
  export const ReferencesEditor: React.FC<ReferencesEditorProps> = (props) => {
31
37
  const [isOpen, setOpen] = useState(true)
32
38
  const [items, dispatch] = useReducer(itemsReducer, props.items)
39
+ const [selectedItem, setSelectedItem] = useState(props.item)
40
+
41
+ useEffect(() => {
42
+ setSelectedItem(props.item)
43
+ }, [props.item])
33
44
 
34
45
  const handleSave = (item: BibliographyItemAttrs) => {
35
46
  const cleanedItem = cleanItemValues(item)
36
- props.onSave(cleanedItem)
47
+ props.onSave([cleanedItem])
37
48
  dispatch({
38
49
  type: 'update',
39
50
  items: [cleanedItem],
@@ -48,14 +59,32 @@ export const ReferencesEditor: React.FC<ReferencesEditorProps> = (props) => {
48
59
  })
49
60
  }
50
61
 
62
+ const handleImport = (data: BibliographyItemAttrs[]) => {
63
+ const newItems = data.map((item) =>
64
+ cleanItemValues({
65
+ ...item,
66
+ id: generateNodeID(schema.nodes.bibliography_item),
67
+ })
68
+ )
69
+
70
+ props.onSave(newItems)
71
+
72
+ dispatch({
73
+ type: 'set',
74
+ state: [...items, ...newItems],
75
+ })
76
+ }
77
+
51
78
  return (
52
79
  <ReferencesModal
53
- {...props}
54
80
  isOpen={isOpen}
55
81
  onCancel={() => setOpen(false)}
56
82
  items={items}
83
+ item={selectedItem}
84
+ citationCounts={props.citationCounts}
57
85
  onSave={handleSave}
58
86
  onDelete={handleDelete}
87
+ handleImport={handleImport}
59
88
  />
60
89
  )
61
90
  }
@@ -14,6 +14,7 @@
14
14
  * limitations under the License.
15
15
  */
16
16
  import {
17
+ AddAuthorIcon,
17
18
  Category,
18
19
  CitationCountIcon,
19
20
  CloseButton,
@@ -33,7 +34,7 @@ import {
33
34
  } from '@manuscripts/style-guide'
34
35
  import { BibliographyItemAttrs } from '@manuscripts/transform'
35
36
  import isEqual from 'lodash/isEqual'
36
- import React, { useEffect, useRef, useState } from 'react'
37
+ import React, { useEffect, useMemo, useRef, useState } from 'react'
37
38
  import styled from 'styled-components'
38
39
 
39
40
  import {
@@ -41,6 +42,8 @@ import {
41
42
  ReferenceFormActions,
42
43
  } from './ReferenceForm/ReferenceForm'
43
44
  import { ReferenceLine } from './ReferenceLine'
45
+ import { ImportBibliographyModal } from './ImportBibliographyModal'
46
+ import { ImportSuccessPlaceholder } from './ImportSuccessPlaceholder'
44
47
 
45
48
  const ReferencesModalContainer = styled(ModalContainer)`
46
49
  min-width: 960px;
@@ -54,6 +57,35 @@ const ReferencesSidebarContent = styled(SidebarContent)`
54
57
  overflow-y: auto;
55
58
  `
56
59
 
60
+ const ImportFromFileFooter = styled.div`
61
+ padding: 16px;
62
+ `
63
+
64
+ const ImportFromFileButton = styled.button`
65
+ display: flex;
66
+ width: 100%;
67
+ padding: 8px 24px;
68
+ justify-content: center;
69
+ align-items: center;
70
+ gap: 8px;
71
+ border-radius: 4px;
72
+ border: 1px dashed #c9c9c9;
73
+ background: #fafafa;
74
+ cursor: pointer;
75
+ font-family: ${(props) => props.theme.font.family.sans};
76
+ font-size: 14px;
77
+ color: #353535;
78
+
79
+ svg {
80
+ width: 20px;
81
+ height: 20px;
82
+ }
83
+
84
+ svg rect {
85
+ fill: #6e6e6e;
86
+ }
87
+ `
88
+
57
89
  const ReferencesInnerWrapper = withListNavigation(styled.div`
58
90
  width: 100%;
59
91
  padding: 12px 0;
@@ -155,6 +187,7 @@ export interface ReferencesModalProps {
155
187
  citationCounts: Map<string, number>
156
188
  onSave: (item: BibliographyItemAttrs) => void
157
189
  onDelete: (item: BibliographyItemAttrs) => void
190
+ handleImport: (data: BibliographyItemAttrs[]) => void
158
191
  }
159
192
 
160
193
  export const ReferencesModal: React.FC<ReferencesModalProps> = ({
@@ -165,16 +198,36 @@ export const ReferencesModal: React.FC<ReferencesModalProps> = ({
165
198
  citationCounts,
166
199
  onSave,
167
200
  onDelete,
201
+ handleImport,
168
202
  }) => {
203
+ const [importing, setImporting] = useState(false)
204
+ const [importSuccessCount, setImportSuccessCount] = useState<number | null>(
205
+ null
206
+ )
169
207
  const [confirm, setConfirm] = useState(false)
170
208
  const valuesRef = useRef<BibliographyItemAttrs>(undefined)
171
209
 
172
210
  const [selection, setSelection] = useState<BibliographyItemAttrs>()
173
211
  const selectionRef = useRef<HTMLDivElement>(null)
212
+
213
+ const sortedItems = useMemo(
214
+ () =>
215
+ [...items].sort((a, b) => {
216
+ const aUncited = (citationCounts.get(a.id) ?? 0) === 0
217
+ const bUncited = (citationCounts.get(b.id) ?? 0) === 0
218
+
219
+ if (aUncited === bUncited) {
220
+ return 0
221
+ }
222
+ return aUncited ? 1 : -1
223
+ }),
224
+ [items, citationCounts]
225
+ )
226
+
174
227
  const isSelected = (item: BibliographyItemAttrs) => {
175
228
  return item.id === selection?.id
176
229
  }
177
- const selectionIndex = items.findIndex(isSelected)
230
+ const selectionIndex = sortedItems.findIndex(isSelected)
178
231
 
179
232
  useEffect(() => {
180
233
  setSelection(item)
@@ -199,8 +252,8 @@ export const ReferencesModal: React.FC<ReferencesModalProps> = ({
199
252
  useEffect(() => {
200
253
  const base = Math.max(0, selectionIndex - selectionTopOffset)
201
254
  setStartIndex(base)
202
- setEndIndex(Math.min(items.length - 1, base + pageSize))
203
- }, [selectionIndex, items])
255
+ setEndIndex(Math.min(sortedItems.length - 1, base + pageSize))
256
+ }, [selectionIndex, sortedItems])
204
257
 
205
258
  useEffect(() => {
206
259
  if (triggers.top) {
@@ -209,12 +262,12 @@ export const ReferencesModal: React.FC<ReferencesModalProps> = ({
209
262
  setEndIndex(Math.min(newFirst + dropLimit, endIndex))
210
263
  }
211
264
  if (triggers.bottom) {
212
- const newLast = Math.min(items.length - 1, endIndex + pageSize)
265
+ const newLast = Math.min(sortedItems.length - 1, endIndex + pageSize)
213
266
  setEndIndex(newLast)
214
267
  setStartIndex(Math.max(newLast - dropLimit, startIndex))
215
268
  }
216
269
  // eslint-disable-next-line react-hooks/exhaustive-deps
217
- }, [triggers, items])
270
+ }, [triggers, sortedItems])
218
271
 
219
272
  const actionsRef = useRef<ReferenceFormActions>(undefined)
220
273
 
@@ -260,7 +313,10 @@ export const ReferencesModal: React.FC<ReferencesModalProps> = ({
260
313
  setSelection(undefined)
261
314
  }
262
315
 
316
+ const clearImportSuccess = () => setImportSuccessCount(null)
317
+
263
318
  const handleItemClick = (item: BibliographyItemAttrs) => {
319
+ clearImportSuccess()
264
320
  const values = valuesRef.current
265
321
  if (values && selection && !isEqual(values, normalize(selection))) {
266
322
  setConfirm(true)
@@ -272,81 +328,111 @@ export const ReferencesModal: React.FC<ReferencesModalProps> = ({
272
328
  const handleChange = (values: BibliographyItemAttrs) => {
273
329
  valuesRef.current = values
274
330
  }
275
- if (items.length <= 0) {
331
+
332
+ const handleImportSave = (data: BibliographyItemAttrs[]) => {
333
+ handleImport(data)
334
+ setImporting(false)
335
+ setSelection(undefined)
336
+ setImportSuccessCount(data.length)
337
+ }
338
+
339
+ if (sortedItems.length <= 0) {
276
340
  return <></>
277
341
  }
278
342
 
279
343
  return (
280
- <StyledModal isOpen={isOpen} onRequestClose={onCancel}>
281
- <Dialog
282
- isOpen={confirm}
283
- category={Category.confirmation}
284
- header="You've made changes to this option"
285
- message="Would you like to save or discard your changes?"
286
- actions={{
287
- secondary: {
288
- action: () => reset(),
289
- title: 'Discard',
290
- },
291
- primary: {
292
- action: () => handleSave(valuesRef.current),
293
- title: 'Save',
294
- },
295
- }}
296
- />
297
- <ReferencesModalContainer data-cy={'references-editor'}>
298
- <ModalHeader>
299
- <CloseButton onClick={onCancel} />
300
- </ModalHeader>
301
- <ModalBody>
302
- <ReferencesSidebar>
303
- <ModalSidebarHeader>
304
- <ModalSidebarTitle>References</ModalSidebarTitle>
305
- </ModalSidebarHeader>
306
- <ReferencesSidebarContent ref={ref}>
307
- <ReferencesInnerWrapper>
308
- {items.slice(startIndex, endIndex + 1).map((item) => (
309
- <ReferenceButton
310
- key={item.id}
311
- id={item.id}
312
- className={isSelected(item) ? 'selected' : ''}
313
- onClick={() => handleItemClick(item)}
314
- ref={isSelected(item) ? selectionRef : null}
315
- >
316
- <IconContainer>
317
- <CitationCountIcon />
318
- {(citationCounts.get(item.id) || 0) > 0 ? (
319
- <CitationCount data-tooltip-content="Number of times used in the document">
320
- {citationCounts.get(item.id)}
321
- </CitationCount>
322
- ) : (
323
- <CitationCount className="unused">0</CitationCount>
324
- )}
325
- </IconContainer>
326
- <ReferenceLine item={item} />
327
- </ReferenceButton>
328
- ))}
329
- </ReferencesInnerWrapper>
330
- </ReferencesSidebarContent>
331
- </ReferencesSidebar>
332
- <ScrollableModalContent>
333
- {selection && (
334
- <ReferenceForm
335
- values={normalize(selection)}
336
- showDelete={
337
- !citationCounts.get(selection.id) &&
338
- isNewItem(selection, ['id', 'type']) // disable the delete button for the new citations
339
- }
340
- onChange={handleChange}
341
- onCancel={onCancel}
342
- onDelete={handleDelete}
343
- onSave={handleSave}
344
- actionsRef={actionsRef}
345
- />
346
- )}
347
- </ScrollableModalContent>
348
- </ModalBody>
349
- </ReferencesModalContainer>
350
- </StyledModal>
344
+ <>
345
+ {importing && (
346
+ <ImportBibliographyModal
347
+ onCancel={() => setImporting(false)}
348
+ onSave={handleImportSave}
349
+ />
350
+ )}
351
+ <StyledModal isOpen={isOpen} onRequestClose={onCancel}>
352
+ <Dialog
353
+ isOpen={confirm}
354
+ category={Category.confirmation}
355
+ header="You've made changes to this option"
356
+ message="Would you like to save or discard your changes?"
357
+ actions={{
358
+ secondary: {
359
+ action: () => reset(),
360
+ title: 'Discard',
361
+ },
362
+ primary: {
363
+ action: () => handleSave(valuesRef.current),
364
+ title: 'Save',
365
+ },
366
+ }}
367
+ />
368
+ <ReferencesModalContainer data-cy={'references-editor'}>
369
+ <ModalHeader>
370
+ <CloseButton data-cy="modal-close-button" onClick={onCancel} />
371
+ </ModalHeader>
372
+ <ModalBody>
373
+ <ReferencesSidebar>
374
+ <ModalSidebarHeader>
375
+ <ModalSidebarTitle>References</ModalSidebarTitle>
376
+ </ModalSidebarHeader>
377
+ <ReferencesSidebarContent ref={ref}>
378
+ <ReferencesInnerWrapper>
379
+ {sortedItems.slice(startIndex, endIndex + 1).map((item) => (
380
+ <ReferenceButton
381
+ key={item.id}
382
+ id={item.id}
383
+ className={isSelected(item) ? 'selected' : ''}
384
+ onClick={() => handleItemClick(item)}
385
+ ref={isSelected(item) ? selectionRef : null}
386
+ >
387
+ <IconContainer>
388
+ <CitationCountIcon />
389
+ {(citationCounts.get(item.id) || 0) > 0 ? (
390
+ <CitationCount data-tooltip-content="Number of times used in the document">
391
+ {citationCounts.get(item.id)}
392
+ </CitationCount>
393
+ ) : (
394
+ <CitationCount className="unused">0</CitationCount>
395
+ )}
396
+ </IconContainer>
397
+ <ReferenceLine item={item} />
398
+ </ReferenceButton>
399
+ ))}
400
+ </ReferencesInnerWrapper>
401
+ </ReferencesSidebarContent>
402
+ <ImportFromFileFooter>
403
+ <ImportFromFileButton
404
+ type="button"
405
+ data-cy="import-from-file-button"
406
+ onClick={() => setImporting(true)}
407
+ >
408
+ <AddAuthorIcon />
409
+ Import from file
410
+ </ImportFromFileButton>
411
+ </ImportFromFileFooter>
412
+ </ReferencesSidebar>
413
+ <ScrollableModalContent>
414
+ {importSuccessCount !== null ? (
415
+ <ImportSuccessPlaceholder count={importSuccessCount} />
416
+ ) : (
417
+ selection && (
418
+ <ReferenceForm
419
+ values={normalize(selection)}
420
+ showDelete={
421
+ !citationCounts.get(selection.id) &&
422
+ isNewItem(selection, ['id', 'type']) // disable the delete button for the new citations
423
+ }
424
+ onChange={handleChange}
425
+ onCancel={onCancel}
426
+ onDelete={handleDelete}
427
+ onSave={handleSave}
428
+ actionsRef={actionsRef}
429
+ />
430
+ )
431
+ )}
432
+ </ScrollableModalContent>
433
+ </ModalBody>
434
+ </ReferencesModalContainer>
435
+ </StyledModal>
436
+ </>
351
437
  )
352
438
  }
@@ -93,8 +93,12 @@ export const InsertEmbedDialog: React.FC<InsertEmbedDialogProps> = ({
93
93
  () => {
94
94
  debounce(async () => {
95
95
  if (url && allowedHref(url)) {
96
- const html = await getEditorProps(state).fetchOEmbedHtml(url, 368, 217)
97
- setOEmbedHTML(html)
96
+ const html = await getEditorProps(state).fetchOEmbedHtml(
97
+ url,
98
+ 368,
99
+ 217
100
+ )
101
+ setOEmbedHTML(html)
98
102
  }
99
103
  })
100
104
  },
@@ -18,7 +18,8 @@ import styled from 'styled-components'
18
18
 
19
19
  import { Option } from './TypeSelector'
20
20
 
21
- const TypeSelect: React.ComponentType<Props<Option, false, GroupBase<Option>>> = Select
21
+ const TypeSelect: React.ComponentType<Props<Option, false, GroupBase<Option>>> =
22
+ Select
22
23
 
23
24
  export const StyledSelect = styled(TypeSelect)`
24
25
  .type-selector__control:hover {