@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.
- package/dist/cjs/commands.js +45 -10
- package/dist/cjs/components/form/FormFooter.js +2 -2
- package/dist/cjs/components/keyboard-shortcuts-modal/KeyboardShortcutsModal.js +4 -2
- package/dist/cjs/components/references/CitationEditor.js +13 -19
- package/dist/cjs/components/references/ImportBibliography.js +77 -0
- package/dist/cjs/components/references/ImportBibliographyForm.js +1 -1
- package/dist/cjs/components/references/ImportBibliographyModal.js +12 -81
- package/dist/cjs/components/references/ImportReferencesConfirmation.js +113 -0
- package/dist/cjs/components/references/ImportSuccessPlaceholder.js +56 -0
- package/dist/cjs/components/references/ReferenceSearch.js +2 -5
- package/dist/cjs/components/references/ReferencesEditor.js +18 -2
- package/dist/cjs/components/references/ReferencesModal.js +86 -33
- package/dist/cjs/components/views/CrossReferenceItems.js +139 -52
- package/dist/cjs/components/views/DeleteSupplementDialog.js +1 -1
- package/dist/cjs/lib/supplements.js +9 -1
- package/dist/cjs/lib/view.js +39 -1
- package/dist/cjs/menus.js +35 -0
- package/dist/cjs/plugins/objects.js +1 -1
- package/dist/cjs/toolbar.js +6 -0
- package/dist/cjs/versions.js +1 -1
- package/dist/cjs/views/bibliography_element.js +1 -2
- package/dist/cjs/views/citation_editable.js +2 -29
- package/dist/cjs/views/cross_reference.js +12 -2
- package/dist/cjs/views/cross_reference_editable.js +95 -4
- package/dist/cjs/views/figure_editable.js +3 -0
- package/dist/es/commands.js +43 -9
- package/dist/es/components/form/FormFooter.js +2 -2
- package/dist/es/components/keyboard-shortcuts-modal/KeyboardShortcutsModal.js +4 -2
- package/dist/es/components/references/CitationEditor.js +13 -19
- package/dist/es/components/references/ImportBibliography.js +70 -0
- package/dist/es/components/references/ImportBibliographyForm.js +1 -1
- package/dist/es/components/references/ImportBibliographyModal.js +13 -79
- package/dist/es/components/references/ImportReferencesConfirmation.js +73 -0
- package/dist/es/components/references/ImportSuccessPlaceholder.js +49 -0
- package/dist/es/components/references/ReferenceSearch.js +3 -6
- package/dist/es/components/references/ReferencesEditor.js +19 -3
- package/dist/es/components/references/ReferencesModal.js +88 -35
- package/dist/es/components/views/CrossReferenceItems.js +141 -54
- package/dist/es/components/views/DeleteSupplementDialog.js +2 -2
- package/dist/es/lib/supplements.js +8 -1
- package/dist/es/lib/view.js +36 -0
- package/dist/es/menus.js +36 -1
- package/dist/es/plugins/objects.js +1 -1
- package/dist/es/toolbar.js +8 -2
- package/dist/es/versions.js +1 -1
- package/dist/es/views/bibliography_element.js +2 -3
- package/dist/es/views/citation_editable.js +3 -30
- package/dist/es/views/cross_reference.js +12 -2
- package/dist/es/views/cross_reference_editable.js +96 -5
- package/dist/es/views/figure_editable.js +3 -0
- package/dist/types/commands.d.ts +1 -0
- package/dist/types/components/form/FormFooter.d.ts +2 -1
- package/dist/types/components/references/CitationEditor.d.ts +1 -1
- package/dist/types/components/references/ImportBibliography.d.ts +7 -0
- package/dist/types/components/references/ImportReferencesConfirmation.d.ts +8 -0
- package/dist/types/components/references/ImportSuccessPlaceholder.d.ts +5 -0
- package/dist/types/components/references/ReferenceSearch.d.ts +0 -1
- package/dist/types/components/references/ReferencesEditor.d.ts +4 -1
- package/dist/types/components/references/ReferencesModal.d.ts +1 -0
- package/dist/types/components/views/CrossReferenceItems.d.ts +3 -0
- package/dist/types/lib/supplements.d.ts +1 -0
- package/dist/types/lib/view.d.ts +3 -1
- package/dist/types/versions.d.ts +1 -1
- package/dist/types/views/citation_editable.d.ts +0 -1
- package/dist/types/views/cross_reference_editable.d.ts +6 -0
- package/package.json +4 -4
- package/src/commands.ts +52 -10
- package/src/components/ChangeHandlingForm.tsx +4 -2
- package/src/components/form/FormFooter.tsx +3 -1
- package/src/components/keyboard-shortcuts-modal/KeyboardShortcutsModal.tsx +6 -7
- package/src/components/references/CitationEditor.tsx +16 -25
- package/src/components/references/ImportBibliography.tsx +119 -0
- package/src/components/references/ImportBibliographyForm.tsx +1 -1
- package/src/components/references/ImportBibliographyModal.tsx +34 -110
- package/src/components/references/ImportReferencesConfirmation.tsx +133 -0
- package/src/components/references/ImportSuccessPlaceholder.tsx +93 -0
- package/src/components/references/ReferenceSearch.tsx +0 -7
- package/src/components/references/ReferencesEditor.tsx +35 -6
- package/src/components/references/ReferencesModal.tsx +164 -78
- package/src/components/toolbar/InsertEmbedDialog.tsx +6 -2
- package/src/components/toolbar/type-selector/styles.ts +2 -1
- package/src/components/views/CrossReferenceItems.tsx +227 -100
- package/src/components/views/DeleteSupplementDialog.tsx +3 -6
- package/src/lib/footnotes.ts +3 -1
- package/src/lib/supplements.ts +13 -1
- package/src/lib/utils.ts +15 -4
- package/src/lib/view.ts +65 -0
- package/src/menus.tsx +39 -0
- package/src/plugins/add-subtitle.ts +18 -16
- package/src/plugins/objects.ts +2 -6
- package/src/toolbar.tsx +9 -0
- package/src/versions.ts +1 -1
- package/src/views/bibliography_element.ts +3 -4
- package/src/views/citation_editable.ts +5 -50
- package/src/views/cross_reference.ts +17 -3
- package/src/views/cross_reference_editable.ts +123 -11
- package/src/views/figure_editable.ts +4 -0
- package/src/views/supplement.ts +0 -1
- 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 {
|
|
17
|
-
|
|
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 =
|
|
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(
|
|
203
|
-
}, [selectionIndex,
|
|
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(
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
<
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
<
|
|
317
|
-
|
|
318
|
-
{
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
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
|
-
|
|
97
|
-
|
|
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>>> =
|
|
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 {
|