@manuscripts/body-editor 3.19.0 → 3.19.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/components/cross-ref-check-modal/CrossRefWarningModal.js +202 -52
- package/dist/cjs/components/cross-ref-check-modal/openModal.js +8 -2
- package/dist/cjs/components/references/ReferenceForm/ReferenceForm.js +22 -40
- package/dist/cjs/components/references/ReferenceForm/styled-components.js +1 -13
- package/dist/cjs/components/references/ReferencesEditor.js +26 -1
- package/dist/cjs/components/references/ReferencesModal.js +71 -7
- package/dist/cjs/lib/utils.js +12 -1
- package/dist/cjs/menus.js +12 -0
- package/dist/cjs/plugins/cross-references.js +37 -14
- package/dist/cjs/versions.js +1 -1
- package/dist/cjs/views/ReactSubView.js +1 -0
- package/dist/cjs/views/cross_reference.js +13 -2
- package/dist/cjs/views/cross_reference_editable.js +1 -4
- package/dist/es/components/cross-ref-check-modal/CrossRefWarningModal.js +202 -52
- package/dist/es/components/cross-ref-check-modal/openModal.js +9 -3
- package/dist/es/components/references/ReferenceForm/ReferenceForm.js +21 -40
- package/dist/es/components/references/ReferenceForm/styled-components.js +0 -12
- package/dist/es/components/references/ReferencesEditor.js +21 -0
- package/dist/es/components/references/ReferencesModal.js +73 -9
- package/dist/es/lib/utils.js +11 -1
- package/dist/es/menus.js +12 -0
- package/dist/es/plugins/cross-references.js +37 -15
- package/dist/es/versions.js +1 -1
- package/dist/es/views/ReactSubView.js +1 -0
- package/dist/es/views/cross_reference.js +13 -2
- package/dist/es/views/cross_reference_editable.js +1 -4
- package/dist/types/components/authors-affiliations/AuthorsAndAffiliationsModals.d.ts +1 -1
- package/dist/types/components/cross-ref-check-modal/CrossRefWarningModal.d.ts +4 -2
- package/dist/types/components/cross-ref-check-modal/openModal.d.ts +2 -2
- package/dist/types/components/references/ReferenceForm/ReferenceForm.d.ts +3 -3
- package/dist/types/components/references/ReferenceForm/styled-components.d.ts +0 -1
- package/dist/types/components/references/ReferencesEditor.d.ts +3 -0
- package/dist/types/lib/utils.d.ts +4 -0
- package/dist/types/plugins/cross-references.d.ts +2 -0
- package/dist/types/versions.d.ts +1 -1
- package/dist/types/views/ReactSubView.d.ts +5 -2
- package/dist/types/views/cross_reference.d.ts +1 -0
- package/package.json +2 -2
- package/src/components/cross-ref-check-modal/CrossRefWarningModal.tsx +315 -83
- package/src/components/cross-ref-check-modal/openModal.ts +14 -4
- package/src/components/references/ReferenceForm/ReferenceForm.tsx +21 -82
- package/src/components/references/ReferenceForm/styled-components.ts +0 -13
- package/src/components/references/ReferencesEditor.tsx +38 -0
- package/src/components/references/ReferencesModal.tsx +116 -14
- package/src/lib/utils.ts +24 -0
- package/src/lib/view.ts +1 -0
- package/src/menus.tsx +12 -0
- package/src/plugins/cross-references.ts +41 -13
- package/src/versions.ts +1 -1
- package/src/views/ReactSubView.tsx +6 -3
- package/src/views/cross_reference.ts +14 -2
- package/src/views/cross_reference_editable.ts +1 -4
- package/styles/Editor.css +12 -0
|
@@ -13,52 +13,111 @@
|
|
|
13
13
|
* See the License for the specific language governing permissions and
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
|
+
import { ArrowUpIcon, AttentionRedIcon, ButtonGroup, IconButton, ModalContainer, PrimaryButton, RadioButton, StyledModal, TertiaryButton, WebLinkIcon, withFocusTrap, } from '@manuscripts/style-guide';
|
|
17
|
+
import { schema } from '@manuscripts/transform';
|
|
16
18
|
import React, { useState } from 'react';
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
+
import { getSurroundingText } from '../../lib/utils';
|
|
20
|
+
import { nodeTypeIcon } from '../../node-type-icons';
|
|
19
21
|
import styled from 'styled-components';
|
|
20
22
|
export const CrossRefWarningModal = ({ onClose, xrefs, onConfirm, selectAndScrollTo }) => {
|
|
21
23
|
const [isOpen, setIsOpen] = useState(true);
|
|
24
|
+
const [deleteOption, setDeleteOption] = useState('delete-without-ref');
|
|
22
25
|
const handleClose = () => {
|
|
23
26
|
setIsOpen(false);
|
|
24
27
|
onClose();
|
|
25
28
|
};
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
const [showRef, setShowRef] = useState(false);
|
|
30
|
+
const [isScrolling, setIsScrolling] = useState(false);
|
|
31
|
+
const handleSelectAndScrollTo = ($pos) => {
|
|
32
|
+
setIsScrolling(true);
|
|
33
|
+
selectAndScrollTo($pos);
|
|
34
|
+
};
|
|
35
|
+
const toggleReferenceList = () => setShowRef(!showRef);
|
|
36
|
+
const onChangeDeleteOption = (e) => {
|
|
37
|
+
const { value } = e.target;
|
|
38
|
+
setDeleteOption(value);
|
|
39
|
+
};
|
|
40
|
+
const references = xrefs.flatMap((xrefGroup) => xrefGroup.xrefs.map((xref) => [...xref]));
|
|
41
|
+
const elementLabel = xrefs.length === 1 ? xrefs[0].label : `${xrefs.length} elements`;
|
|
42
|
+
const locationWord = references.length === 1 ? 'location' : 'locations';
|
|
43
|
+
const pronoun = xrefs.length === 1 ? 'it' : 'them';
|
|
44
|
+
return (React.createElement(Modal, { isOpen: isOpen, "$isScrolling": isScrolling, onRequestClose: () => handleClose(), shouldCloseOnOverlayClick: false, hideOverlay: true },
|
|
45
|
+
React.createElement(Container, { "$isScrolling": isScrolling, "data-cy": "cross-reference-warning-modal" },
|
|
30
46
|
React.createElement(Body, null,
|
|
31
47
|
React.createElement(Title, null,
|
|
32
|
-
React.createElement(
|
|
33
|
-
"
|
|
34
|
-
React.createElement("p", null,
|
|
35
|
-
|
|
36
|
-
|
|
48
|
+
React.createElement(AttentionRedIcon, { width: 24, height: 24 }),
|
|
49
|
+
"Confirm Deletion"),
|
|
50
|
+
React.createElement("p", null,
|
|
51
|
+
React.createElement("b", null, elementLabel),
|
|
52
|
+
" ",
|
|
53
|
+
xrefs.length === 1 ? 'is' : 'are',
|
|
54
|
+
" actively referenced in",
|
|
55
|
+
' ',
|
|
56
|
+
React.createElement("b", null,
|
|
57
|
+
references.length,
|
|
58
|
+
" ",
|
|
59
|
+
locationWord),
|
|
60
|
+
' ',
|
|
61
|
+
"in your document. Deleting ",
|
|
62
|
+
pronoun,
|
|
63
|
+
" will break the following cross-references:"),
|
|
64
|
+
React.createElement("div", null,
|
|
65
|
+
React.createElement(ToggleHeader, null,
|
|
66
|
+
React.createElement(SecondaryBoldHeading, null, showRef
|
|
67
|
+
? 'Hide locations'
|
|
68
|
+
: `Show ${references.length} location`),
|
|
69
|
+
React.createElement(ToggleButton, { onClick: toggleReferenceList }, showRef ? React.createElement(ArrowUpIcon, null) : React.createElement(ArrowDownIcon, null))),
|
|
70
|
+
showRef && (React.createElement(ListWrapper, null, xrefs.length === 1 ? (React.createElement(XrefGroupDisplay, { label: xrefs[0].label, xrefs: xrefs[0].xrefs, selectAndScrollTo: handleSelectAndScrollTo })) : (React.createElement(XrefGroupsDisplay, { xrefsGroup: xrefs, selectAndScrollTo: handleSelectAndScrollTo }))))),
|
|
71
|
+
React.createElement(SelectorContainer, null,
|
|
72
|
+
React.createElement(RadioButton, { name: 'delete-option', id: 'delete-without-ref-option', value: 'delete-without-ref', label: 'Delete but keep referenced text', checked: deleteOption === 'delete-without-ref', onChange: onChangeDeleteOption }),
|
|
73
|
+
React.createElement(RadioButton, { name: 'delete-option', id: 'delete-with-ref-option', value: 'delete-with-ref', label: 'Delete & remove references', checked: deleteOption === 'delete-with-ref', onChange: onChangeDeleteOption })),
|
|
74
|
+
React.createElement(ButtonGroup, null,
|
|
37
75
|
React.createElement(TertiaryButton, { type: "button", onClick: () => handleClose() }, "Cancel"),
|
|
38
76
|
React.createElement(PrimaryButton, { "$danger": true, type: "button", onClick: () => {
|
|
39
|
-
onConfirm();
|
|
77
|
+
onConfirm(deleteOption);
|
|
40
78
|
setIsOpen(false);
|
|
41
|
-
} }, "Delete
|
|
79
|
+
} }, "Delete"))))));
|
|
42
80
|
};
|
|
43
|
-
const XrefGroupDisplay = ({
|
|
44
|
-
return (React.createElement(
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
81
|
+
const XrefGroupDisplay = ({ label, xrefs, selectAndScrollTo }) => {
|
|
82
|
+
return (React.createElement(ReferencesList, null, xrefs.map(([xrefNode, pos], i) => {
|
|
83
|
+
const { leftHandText, rightHandText } = getSurroundingText(pos, xrefNode);
|
|
84
|
+
const derivedLabel = xrefNode.attrs.label || label || '[cross-ref]';
|
|
85
|
+
return (React.createElement(ListItem, { key: i },
|
|
86
|
+
React.createElement(XRefTextContainer, null,
|
|
87
|
+
React.createElement(XRefAdjacentText, { "$direction": 'rtl' }, leftHandText),
|
|
88
|
+
React.createElement(XRefLabel, null, derivedLabel),
|
|
89
|
+
React.createElement(XRefAdjacentText, { "$direction": 'ltr' }, rightHandText)),
|
|
90
|
+
React.createElement(ScrollButton, { "$mini": true, onClick: () => selectAndScrollTo(pos) }, "Show")));
|
|
91
|
+
})));
|
|
92
|
+
};
|
|
93
|
+
const XrefGroupsDisplay = ({ xrefsGroup, selectAndScrollTo }) => {
|
|
94
|
+
return (React.createElement(ReferencesList, null, xrefsGroup.map(({ referenced, label, caption, xrefs }, i) => {
|
|
95
|
+
const icon = getReferencedIcon(referenced.type);
|
|
96
|
+
return (React.createElement("div", { key: i },
|
|
97
|
+
React.createElement(DarkSecondaryBoldHeading, null,
|
|
98
|
+
icon ? React.createElement(ItemIcon, null, icon) : null,
|
|
99
|
+
`${label}:`,
|
|
100
|
+
React.createElement(CaptionTitle, { "$direction": 'ltr' }, caption)),
|
|
101
|
+
React.createElement(XrefGroupDisplay, { label: label, xrefs: xrefs, selectAndScrollTo: selectAndScrollTo })));
|
|
102
|
+
})));
|
|
103
|
+
};
|
|
104
|
+
const getReferencedIcon = (type) => {
|
|
105
|
+
if (type === schema.nodes.supplement) {
|
|
106
|
+
return React.createElement(WebLinkIcon, { className: "file-icon" });
|
|
107
|
+
}
|
|
108
|
+
return nodeTypeIcon(schema.nodes[type.name]);
|
|
50
109
|
};
|
|
51
110
|
const Container = styled(ModalContainer) `
|
|
52
111
|
position: absolute;
|
|
53
|
-
top: 1rem;
|
|
54
|
-
left: 50
|
|
55
|
-
|
|
56
|
-
max-height: calc(50vh - 2rem);
|
|
112
|
+
top: ${({ $isScrolling }) => ($isScrolling ? '1rem' : '30%')};
|
|
113
|
+
left: ${({ $isScrolling }) => $isScrolling ? 'calc(100% - 556px - 0rem)' : '50%'};
|
|
114
|
+
max-height: calc(60vh - 2rem);
|
|
57
115
|
min-height: 280px;
|
|
58
|
-
transform: translate(-50%, 0);
|
|
59
|
-
max-width:
|
|
116
|
+
transform: ${({ $isScrolling }) => $isScrolling ? 'translate(0, 0)' : 'translate(-50%, 0)'};
|
|
117
|
+
max-width: 556px;
|
|
60
118
|
transition:
|
|
61
119
|
top 0.2s,
|
|
120
|
+
left 0.2s,
|
|
62
121
|
transform 0.2s;
|
|
63
122
|
`;
|
|
64
123
|
const Modal = styled(StyledModal) `
|
|
@@ -86,6 +145,8 @@ const Modal = styled(StyledModal) `
|
|
|
86
145
|
right: 0;
|
|
87
146
|
bottom: 0;
|
|
88
147
|
background: rgba(0, 0, 0, 0.2);
|
|
148
|
+
opacity: ${({ $isScrolling }) => ($isScrolling ? 0.15 : 1)};
|
|
149
|
+
transform: opacity 0.2s;
|
|
89
150
|
}
|
|
90
151
|
h3 {
|
|
91
152
|
font-size: 16px;
|
|
@@ -95,11 +156,11 @@ const Modal = styled(StyledModal) `
|
|
|
95
156
|
margin: 0.5em 0;
|
|
96
157
|
}
|
|
97
158
|
`;
|
|
98
|
-
const Body = styled.div `
|
|
159
|
+
const Body = withFocusTrap(styled.div `
|
|
99
160
|
margin: 1.5rem;
|
|
100
161
|
display: flex;
|
|
101
162
|
flex-flow: column;
|
|
102
|
-
|
|
163
|
+
`);
|
|
103
164
|
const Title = styled.h2 `
|
|
104
165
|
font-size: 18px;
|
|
105
166
|
font-weight: 700;
|
|
@@ -108,35 +169,124 @@ const Title = styled.h2 `
|
|
|
108
169
|
color: #353535;
|
|
109
170
|
svg {
|
|
110
171
|
vertical-align: text-top;
|
|
172
|
+
margin-right: 8px;
|
|
111
173
|
}
|
|
112
174
|
`;
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
175
|
+
const ToggleHeader = styled.div `
|
|
176
|
+
display: flex;
|
|
177
|
+
justify-content: space-between;
|
|
178
|
+
align-items: center;
|
|
179
|
+
padding: 6px 3px 6px 8px;
|
|
180
|
+
border-radius: 4px 4px 0 0;
|
|
181
|
+
border: 1px solid #e2e2e2;
|
|
117
182
|
background: #f2f2f2;
|
|
183
|
+
`;
|
|
184
|
+
const ListWrapper = styled.div `
|
|
118
185
|
border: 1px solid #e2e2e2;
|
|
119
|
-
border-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
186
|
+
border-top: none;
|
|
187
|
+
max-height: 21vh;
|
|
188
|
+
min-height: 100px;
|
|
189
|
+
overflow-y: auto;
|
|
190
|
+
padding: 12px;
|
|
191
|
+
`;
|
|
192
|
+
const ToggleButton = styled(IconButton) `
|
|
193
|
+
height: 24px;
|
|
194
|
+
&&:not([disabled]):focus-visible {
|
|
195
|
+
outline: 2px solid ${(props) => props.theme.colors.outline.focus};
|
|
196
|
+
outline-offset: 2px;
|
|
197
|
+
}
|
|
198
|
+
svg {
|
|
199
|
+
width: 12px;
|
|
200
|
+
height: 7px;
|
|
132
201
|
}
|
|
133
202
|
`;
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
203
|
+
const SecondaryHeading = styled.div `
|
|
204
|
+
font-size: 14px;
|
|
205
|
+
font-style: normal;
|
|
206
|
+
font-weight: 700;
|
|
207
|
+
line-height: 24px;
|
|
208
|
+
color: #6e6e6e;
|
|
209
|
+
user-select: none;
|
|
137
210
|
`;
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
211
|
+
const SecondaryBoldHeading = styled(SecondaryHeading) `
|
|
212
|
+
font-weight: 700;
|
|
213
|
+
color: #6e6e6e;
|
|
214
|
+
`;
|
|
215
|
+
const DarkSecondaryBoldHeading = styled(SecondaryBoldHeading) `
|
|
216
|
+
display: flex;
|
|
217
|
+
padding: 0 4px;
|
|
218
|
+
margin-bottom: 10px;
|
|
219
|
+
border-radius: 4px;
|
|
220
|
+
color: #353535;
|
|
221
|
+
background: #f2f2f2;
|
|
222
|
+
`;
|
|
223
|
+
const ItemIcon = styled.span `
|
|
224
|
+
display: flex;
|
|
225
|
+
padding-right: 8px;
|
|
226
|
+
svg {
|
|
227
|
+
align-self: center;
|
|
228
|
+
}
|
|
229
|
+
`;
|
|
230
|
+
const ReferencesList = styled.ul `
|
|
231
|
+
display: flex;
|
|
232
|
+
gap: 10px;
|
|
233
|
+
flex-direction: column;
|
|
234
|
+
padding: 0;
|
|
235
|
+
margin: 0;
|
|
236
|
+
list-style: none;
|
|
237
|
+
`;
|
|
238
|
+
const ListItem = styled.li `
|
|
239
|
+
display: flex;
|
|
240
|
+
justify-content: space-between;
|
|
241
|
+
align-items: center;
|
|
242
|
+
border-bottom: solid 1px #e2e2e2;
|
|
243
|
+
&:last-child {
|
|
244
|
+
border-bottom: none;
|
|
245
|
+
padding: 0;
|
|
246
|
+
}
|
|
247
|
+
`;
|
|
248
|
+
const ScrollButton = styled(TertiaryButton) `
|
|
249
|
+
margin: 0;
|
|
250
|
+
padding: 4px;
|
|
251
|
+
font-size: 14px;
|
|
252
|
+
line-height: 24px;
|
|
253
|
+
text-decoration-line: underline;
|
|
254
|
+
`;
|
|
255
|
+
const XRefTextContainer = styled.div `
|
|
256
|
+
display: flex;
|
|
257
|
+
color: #6e6e6e;
|
|
258
|
+
font-size: 12px;
|
|
259
|
+
font-style: normal;
|
|
260
|
+
font-weight: 400;
|
|
261
|
+
line-height: 16px;
|
|
262
|
+
`;
|
|
263
|
+
const XRefLabel = styled.span `
|
|
264
|
+
border-radius: 4px;
|
|
265
|
+
border: 1px solid #fe8f1f;
|
|
266
|
+
background: #fff9e5;
|
|
267
|
+
mix-blend-mode: darken;
|
|
268
|
+
`;
|
|
269
|
+
const XRefAdjacentText = styled.span `
|
|
270
|
+
display: block;
|
|
271
|
+
max-width: 150px;
|
|
272
|
+
white-space: nowrap;
|
|
273
|
+
overflow: hidden;
|
|
274
|
+
text-overflow: ellipsis;
|
|
275
|
+
direction: ${(props) => props.$direction};
|
|
276
|
+
`;
|
|
277
|
+
const CaptionTitle = styled(XRefAdjacentText) `
|
|
278
|
+
max-width: 120px;
|
|
279
|
+
margin-left: 4px;
|
|
280
|
+
`;
|
|
281
|
+
const SelectorContainer = styled.div `
|
|
282
|
+
display: flex;
|
|
283
|
+
gap: 24px;
|
|
284
|
+
padding: 8px 12px 4px 12px;
|
|
285
|
+
margin: 24px 0;
|
|
286
|
+
border-radius: 4px;
|
|
287
|
+
border: 1px solid #fe8f1f;
|
|
288
|
+
background: #fff9e5;
|
|
289
|
+
`;
|
|
290
|
+
const ArrowDownIcon = styled(ArrowUpIcon) `
|
|
291
|
+
transform: rotate(180deg);
|
|
142
292
|
`;
|
|
@@ -15,14 +15,20 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import { getEditorProps } from '../../plugins/editor-props';
|
|
17
17
|
import ReactSubView from '../../views/ReactSubView';
|
|
18
|
-
import { CrossRefWarningModal } from './CrossRefWarningModal';
|
|
18
|
+
import { CrossRefWarningModal, } from './CrossRefWarningModal';
|
|
19
19
|
export const openCrossRefWarningModal = (view, xrefGroups, onConfirm, onClose, selectAndScrollTo) => {
|
|
20
20
|
const { state } = view;
|
|
21
21
|
const props = getEditorProps(state);
|
|
22
22
|
const componentProps = {
|
|
23
23
|
xrefs: xrefGroups,
|
|
24
|
-
onConfirm
|
|
25
|
-
|
|
24
|
+
onConfirm: (deleteOption) => {
|
|
25
|
+
dialog.unmount();
|
|
26
|
+
onConfirm(deleteOption);
|
|
27
|
+
},
|
|
28
|
+
onClose: () => {
|
|
29
|
+
dialog.unmount();
|
|
30
|
+
onClose();
|
|
31
|
+
},
|
|
26
32
|
selectAndScrollTo,
|
|
27
33
|
};
|
|
28
34
|
const dialog = ReactSubView(props, CrossRefWarningModal, componentProps, state.doc, () => 0, view);
|
|
@@ -13,19 +13,33 @@
|
|
|
13
13
|
* See the License for the specific language governing permissions and
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
|
-
import { AddAuthorIcon,
|
|
16
|
+
import { AddAuthorIcon, SelectField, Label, FormRow, YearField, } from '@manuscripts/style-guide';
|
|
17
17
|
import { Field, FieldArray, Formik } from 'formik';
|
|
18
18
|
import React, { useEffect, useRef, useState } from 'react';
|
|
19
19
|
import { bibliographyItemTypes } from '../../../lib/references';
|
|
20
20
|
import { shouldRenderField } from '../../../lib/utils';
|
|
21
21
|
import { ChangeHandlingForm } from '../../ChangeHandlingForm';
|
|
22
22
|
import { PersonDropDown } from './PersonDropDown';
|
|
23
|
-
import {
|
|
23
|
+
import { Button, FormFields, ReferenceTextArea, ReferenceTextField, } from './styled-components';
|
|
24
24
|
const bibliographyItemTypeOptions = bibliographyItemTypes.map((i) => ({
|
|
25
25
|
label: i[1],
|
|
26
26
|
value: i[0],
|
|
27
27
|
}));
|
|
28
|
-
export const
|
|
28
|
+
export const validateReference = (values) => {
|
|
29
|
+
const errors = {};
|
|
30
|
+
if (values.type === 'literal') {
|
|
31
|
+
if (!values.literal?.trim()) {
|
|
32
|
+
errors.literal = 'Literal is required for unstructured references';
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
if (!values.title?.trim()) {
|
|
37
|
+
errors.title = 'Title is required';
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return errors;
|
|
41
|
+
};
|
|
42
|
+
export const ReferenceForm = ({ values, onChange, onSave, actionsRef, }) => {
|
|
29
43
|
const fieldsRef = useRef(null);
|
|
30
44
|
const formRef = useRef(null);
|
|
31
45
|
const [newAuthorIndex, setNewAuthorIndex] = useState();
|
|
@@ -35,52 +49,19 @@ export const ReferenceForm = ({ values, showDelete, onChange, onDelete, onCancel
|
|
|
35
49
|
fieldsRef.current.scrollTop = 0;
|
|
36
50
|
}
|
|
37
51
|
}, [values]);
|
|
38
|
-
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
|
|
39
|
-
const validateReference = (values) => {
|
|
40
|
-
const errors = {};
|
|
41
|
-
if (values.type === 'literal') {
|
|
42
|
-
if (!values.literal?.trim()) {
|
|
43
|
-
errors.literal = 'Literal is required for unstructured references';
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
else {
|
|
47
|
-
if (!values.title?.trim()) {
|
|
48
|
-
errors.title = 'Title is required';
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
return errors;
|
|
52
|
-
};
|
|
53
52
|
if (actionsRef && !actionsRef.current) {
|
|
54
53
|
actionsRef.current = {
|
|
55
54
|
reset: () => {
|
|
56
55
|
formRef.current?.resetForm();
|
|
57
56
|
},
|
|
57
|
+
submit: () => {
|
|
58
|
+
formRef.current?.submitForm();
|
|
59
|
+
},
|
|
60
|
+
isDirty: () => formRef.current?.dirty ?? false,
|
|
58
61
|
};
|
|
59
62
|
}
|
|
60
63
|
return (React.createElement(Formik, { initialValues: values, onSubmit: onSave, enableReinitialize: true, innerRef: formRef, validate: validateReference }, (formik) => {
|
|
61
64
|
return (React.createElement(ChangeHandlingForm, { onChange: onChange },
|
|
62
|
-
React.createElement(Dialog, { isOpen: showDeleteDialog, category: Category.confirmation, header: "Delete Reference", message: "Are you sure you want to delete this reference from the list?", actions: {
|
|
63
|
-
secondary: {
|
|
64
|
-
action: () => {
|
|
65
|
-
onDelete();
|
|
66
|
-
setShowDeleteDialog(false);
|
|
67
|
-
},
|
|
68
|
-
title: 'Delete',
|
|
69
|
-
},
|
|
70
|
-
primary: {
|
|
71
|
-
action: () => setShowDeleteDialog(false),
|
|
72
|
-
title: 'Cancel',
|
|
73
|
-
},
|
|
74
|
-
} }),
|
|
75
|
-
React.createElement(Actions, null,
|
|
76
|
-
React.createElement(ButtonGroup, null,
|
|
77
|
-
React.createElement(IconButton, { as: "a", href: `https://doi.org/${formik.values.DOI}`, target: '_blank' },
|
|
78
|
-
React.createElement(LinkIcon, null)),
|
|
79
|
-
React.createElement(DeleteButton, { "$defaultColor": true, disabled: !showDelete, "data-tooltip-content": "Unable to delete because the item is used in the document", "data-tooltip-hidden": showDelete, onClick: () => setShowDeleteDialog(true) },
|
|
80
|
-
React.createElement(DeleteIcon, null))),
|
|
81
|
-
React.createElement(ButtonGroup, null,
|
|
82
|
-
React.createElement(SecondaryButton, { onClick: onCancel }, "Cancel"),
|
|
83
|
-
React.createElement(PrimaryButton, { type: "submit", disabled: !formik.isValid || !formik.dirty }, "Save"))),
|
|
84
65
|
React.createElement(FormFields, { ref: fieldsRef },
|
|
85
66
|
React.createElement(FormRow, null,
|
|
86
67
|
React.createElement(Label, { htmlFor: 'citation-item-type' }, "Type"),
|
|
@@ -13,18 +13,6 @@ export const Button = styled(IconButton).attrs({
|
|
|
13
13
|
mask: none;
|
|
14
14
|
}
|
|
15
15
|
`;
|
|
16
|
-
export const Actions = styled.div `
|
|
17
|
-
flex-shrink: 1;
|
|
18
|
-
display: flex;
|
|
19
|
-
justify-content: space-between;
|
|
20
|
-
align-items: center;
|
|
21
|
-
|
|
22
|
-
.tooltip {
|
|
23
|
-
max-width: ${(props) => props.theme.grid.unit * 39}px;
|
|
24
|
-
padding: ${(props) => props.theme.grid.unit * 2}px;
|
|
25
|
-
border-radius: 6px;
|
|
26
|
-
}
|
|
27
|
-
`;
|
|
28
16
|
export const ReferenceTextField = styled(TextField) `
|
|
29
17
|
padding: ${(props) => props.theme.grid.unit * 2}px
|
|
30
18
|
${(props) => props.theme.grid.unit * 3}px;
|
|
@@ -3,6 +3,10 @@ import React, { useEffect, useReducer, useState } from 'react';
|
|
|
3
3
|
import { attrsReducer } from '../../lib/array-reducer';
|
|
4
4
|
import { cleanItemValues } from '../../lib/utils';
|
|
5
5
|
import { ReferencesModal } from './ReferencesModal';
|
|
6
|
+
import { getEditorProps } from '../../plugins/editor-props';
|
|
7
|
+
import { deleteNode, saveBibliographyItem } from '../../lib/view';
|
|
8
|
+
import ReactSubView from '../../views/ReactSubView';
|
|
9
|
+
import { getBibliographyPluginState } from '../../plugins/bibliography';
|
|
6
10
|
const itemsReducer = attrsReducer();
|
|
7
11
|
export const ReferencesEditor = (props) => {
|
|
8
12
|
const [isOpen, setOpen] = useState(true);
|
|
@@ -39,3 +43,20 @@ export const ReferencesEditor = (props) => {
|
|
|
39
43
|
};
|
|
40
44
|
return (React.createElement(ReferencesModal, { isOpen: isOpen, onCancel: () => setOpen(false), items: items, item: selectedItem, citationCounts: props.citationCounts, onSave: handleSave, onDelete: handleDelete, handleImport: handleImport }));
|
|
41
45
|
};
|
|
46
|
+
export const openReferencesEditor = (state, _, view) => {
|
|
47
|
+
if (!view) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
const props = getEditorProps(state);
|
|
51
|
+
const bib = getBibliographyPluginState(view.state);
|
|
52
|
+
const componentProps = {
|
|
53
|
+
items: bib ? Array.from(bib.bibliographyItems.values()) : [],
|
|
54
|
+
citationCounts: new Map(bib?.citationCounts),
|
|
55
|
+
onDelete: (item) => deleteNode(view, item.id),
|
|
56
|
+
onSave: (item) => saveBibliographyItem(view, item),
|
|
57
|
+
};
|
|
58
|
+
const referencesEditor = ReactSubView(props, ReferencesEditor, componentProps, state.doc, () => 0, view);
|
|
59
|
+
view.focus();
|
|
60
|
+
document.body.appendChild(referencesEditor);
|
|
61
|
+
return true;
|
|
62
|
+
};
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { AddIcon, AddAuthorIcon, Category, CitationCountIcon, CloseButton, Dialog, ModalBody, ModalContainer, ModalHeader, ModalSidebar, ModalSidebarHeader, ModalSidebarTitle, ScrollableModalContent, SidebarContent, StyledModal, useScrollDetection, withListNavigation, withNavigableListItem, } from '@manuscripts/style-guide';
|
|
1
|
+
import { AddIcon, AddAuthorIcon, Category, CitationCountIcon, CloseButton, Dialog, ModalBody, ModalContainer, ModalHeader, ModalSidebar, ModalSidebarHeader, ModalSidebarTitle, ScrollableModalContent, SidebarContent, StyledModal, useScrollDetection, withListNavigation, withNavigableListItem, ButtonGroup, DeleteIcon, LinkIcon, outlineStyle, } from '@manuscripts/style-guide';
|
|
2
|
+
import { Button, IconButton } from '@manuscripts/style-guide/mui';
|
|
2
3
|
import { generateNodeID, schema, } from '@manuscripts/transform';
|
|
3
4
|
import isEqual from 'lodash/isEqual';
|
|
4
5
|
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
|
5
|
-
import styled from 'styled-components';
|
|
6
|
-
import { ReferenceForm, } from './ReferenceForm/ReferenceForm';
|
|
6
|
+
import styled, { css } from 'styled-components';
|
|
7
|
+
import { ReferenceForm, validateReference, } from './ReferenceForm/ReferenceForm';
|
|
7
8
|
import { ReferenceLine } from './ReferenceLine';
|
|
8
9
|
import { ImportBibliographyModal } from './ImportBibliographyModal';
|
|
9
10
|
import { ImportSuccessPlaceholder } from './ImportSuccessPlaceholder';
|
|
@@ -20,6 +21,7 @@ export const ReferencesModal = ({ isOpen, onCancel, items, item, citationCounts,
|
|
|
20
21
|
const valuesRef = useRef(undefined);
|
|
21
22
|
const [selection, setSelection] = useState();
|
|
22
23
|
const [isNew, setIsNew] = useState(false);
|
|
24
|
+
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
|
|
23
25
|
const selectionRef = useRef(null);
|
|
24
26
|
const sortedItems = useMemo(() => [...items].sort((a, b) => {
|
|
25
27
|
const aUncited = (citationCounts.get(a.id) ?? 0) === 0;
|
|
@@ -84,6 +86,10 @@ export const ReferencesModal = ({ isOpen, onCancel, items, item, citationCounts,
|
|
|
84
86
|
const extraKeys = allKeys.filter((key) => !basicProps.includes(key));
|
|
85
87
|
return extraKeys.length > 0;
|
|
86
88
|
};
|
|
89
|
+
const canDeleteSelectedItem = (selection &&
|
|
90
|
+
!citationCounts.get(selection.id) &&
|
|
91
|
+
isNewItem(selection, ['id', 'type'])) ??
|
|
92
|
+
false;
|
|
87
93
|
const handleSave = (values) => {
|
|
88
94
|
if (!values || !selection) {
|
|
89
95
|
return;
|
|
@@ -122,8 +128,13 @@ export const ReferencesModal = ({ isOpen, onCancel, items, item, citationCounts,
|
|
|
122
128
|
setIsNew(false);
|
|
123
129
|
setSelection(item);
|
|
124
130
|
};
|
|
131
|
+
const [canSave, setCanSave] = useState(false);
|
|
125
132
|
const handleChange = (values) => {
|
|
126
133
|
valuesRef.current = values;
|
|
134
|
+
const errors = validateReference(values);
|
|
135
|
+
const isValid = Object.keys(errors).length === 0;
|
|
136
|
+
const dirty = actionsRef.current?.isDirty() ?? false;
|
|
137
|
+
setCanSave(isValid && dirty);
|
|
127
138
|
};
|
|
128
139
|
const handleImportSave = (data) => {
|
|
129
140
|
handleImport(data);
|
|
@@ -144,6 +155,19 @@ export const ReferencesModal = ({ isOpen, onCancel, items, item, citationCounts,
|
|
|
144
155
|
title: 'Save',
|
|
145
156
|
},
|
|
146
157
|
} }),
|
|
158
|
+
React.createElement(Dialog, { isOpen: showDeleteDialog, category: Category.confirmation, header: "Delete Reference", message: "Are you sure you want to delete this reference from the list?", actions: {
|
|
159
|
+
secondary: {
|
|
160
|
+
action: () => {
|
|
161
|
+
handleDelete();
|
|
162
|
+
setShowDeleteDialog(false);
|
|
163
|
+
},
|
|
164
|
+
title: 'Delete',
|
|
165
|
+
},
|
|
166
|
+
primary: {
|
|
167
|
+
action: () => setShowDeleteDialog(false),
|
|
168
|
+
title: 'Cancel',
|
|
169
|
+
},
|
|
170
|
+
} }),
|
|
147
171
|
React.createElement(ReferencesModalContainer, { "data-cy": 'references-editor' },
|
|
148
172
|
React.createElement(ModalHeader, null,
|
|
149
173
|
React.createElement(CloseButton, { "data-cy": "modal-close-button", onClick: onCancel })),
|
|
@@ -166,18 +190,25 @@ export const ReferencesModal = ({ isOpen, onCancel, items, item, citationCounts,
|
|
|
166
190
|
React.createElement(AddIcon, null),
|
|
167
191
|
React.createElement("span", null, "New Reference")),
|
|
168
192
|
React.createElement(ReferencesInnerWrapper, null,
|
|
169
|
-
React.createElement(ExistingReferencesHeading, null, "Existing References"),
|
|
193
|
+
sortedItems.length ? (React.createElement(ExistingReferencesHeading, null, "Existing References")) : null,
|
|
170
194
|
sortedItems.slice(startIndex, endIndex + 1).map((item) => (React.createElement(ReferenceButton, { key: item.id, id: item.id, className: isSelected(item) ? 'selected' : '', onClick: () => handleItemClick(item), ref: isSelected(item) ? selectionRef : null },
|
|
171
195
|
React.createElement(IconContainer, null,
|
|
172
196
|
React.createElement(CitationCountIconStyled, null),
|
|
173
197
|
(citationCounts.get(item.id) || 0) > 0 ? (React.createElement(CitationCount, { "data-tooltip-content": "Number of times used in the document" }, citationCounts.get(item.id))) : (React.createElement(CitationCount, { className: "unused" }, "0"))),
|
|
174
|
-
React.createElement(ReferenceLine, { showUncited: !citationCounts.get(item.id), item: item })
|
|
198
|
+
React.createElement(ReferenceLine, { showUncited: !citationCounts.get(item.id), item: item }),
|
|
199
|
+
isSelected(item) && (React.createElement(ButtonGroup, null,
|
|
200
|
+
React.createElement(IconButton, { component: 'a', href: `https://doi.org/${item.DOI}`, target: '_blank', size: 'small' },
|
|
201
|
+
React.createElement(DOILinkIcon, { fill: '#6E6E6E' })),
|
|
202
|
+
React.createElement(IconButton, { disabled: !canDeleteSelectedItem, size: 'small', onClick: () => setShowDeleteDialog(true) },
|
|
203
|
+
React.createElement(DeleteIcon, { fill: '#6E6E6E' }))))))))),
|
|
175
204
|
React.createElement(ImportFromFileFooter, null,
|
|
176
205
|
React.createElement(ImportFromFileButton, { type: "button", "data-cy": "import-from-file-button", onClick: () => setImporting(true) },
|
|
177
206
|
React.createElement(AddAuthorIcon, null),
|
|
178
207
|
"Import from file"))),
|
|
179
|
-
React.createElement(
|
|
180
|
-
|
|
208
|
+
React.createElement(ReferencesContent, null, importSuccessCount !== null ? (React.createElement(ImportSuccessPlaceholder, { count: importSuccessCount })) : (selection && (React.createElement(ReferenceForm, { values: normalizeBlblioItem(selection), onChange: handleChange, onSave: handleSave, actionsRef: actionsRef }))))),
|
|
209
|
+
React.createElement(Footer, null,
|
|
210
|
+
React.createElement(TertiaryButton, { variant: "tertiary", onClick: onCancel }, "Close"),
|
|
211
|
+
React.createElement(Button, { disabled: !canSave, onClick: () => actionsRef.current?.submit() }, "Save Changes"))))));
|
|
181
212
|
};
|
|
182
213
|
const ReferencesModalContainer = styled(ModalContainer) `
|
|
183
214
|
min-width: 960px;
|
|
@@ -185,15 +216,19 @@ const ReferencesModalContainer = styled(ModalContainer) `
|
|
|
185
216
|
const ReferencesSidebar = styled(ModalSidebar) `
|
|
186
217
|
width: 70%;
|
|
187
218
|
`;
|
|
219
|
+
const ReferencesContent = styled(ScrollableModalContent) `
|
|
220
|
+
padding-top: 32px;
|
|
221
|
+
`;
|
|
188
222
|
const ReferencesSidebarContent = styled(SidebarContent) `
|
|
189
223
|
overflow-y: auto;
|
|
224
|
+
padding-top: 8px;
|
|
190
225
|
`;
|
|
191
226
|
const ReferencesInnerWrapper = withListNavigation(styled.div `
|
|
192
227
|
width: 100%;
|
|
193
228
|
padding: 12px 0;
|
|
194
229
|
`);
|
|
195
230
|
const CitationCountIconStyled = styled(CitationCountIcon) ``;
|
|
196
|
-
const
|
|
231
|
+
const referenceButtonStyles = css `
|
|
197
232
|
cursor: pointer;
|
|
198
233
|
display: flex;
|
|
199
234
|
width: 100%;
|
|
@@ -205,6 +240,7 @@ const ReferenceButton = withNavigableListItem(styled.button `
|
|
|
205
240
|
border: none;
|
|
206
241
|
border-radius: 0;
|
|
207
242
|
text-align: inherit;
|
|
243
|
+
color: #353535;
|
|
208
244
|
|
|
209
245
|
padding: ${(props) => props.theme.grid.unit * 4}px 0;
|
|
210
246
|
border-top: 1px solid transparent;
|
|
@@ -229,6 +265,12 @@ const ReferenceButton = withNavigableListItem(styled.button `
|
|
|
229
265
|
padding: ${(props) => props.theme.grid.unit * 2}px;
|
|
230
266
|
border-radius: 6px;
|
|
231
267
|
}
|
|
268
|
+
`;
|
|
269
|
+
const ReferenceButton = withNavigableListItem(styled.div.attrs({
|
|
270
|
+
role: 'button',
|
|
271
|
+
tabIndex: 0,
|
|
272
|
+
}) `
|
|
273
|
+
${referenceButtonStyles}
|
|
232
274
|
`);
|
|
233
275
|
const IconContainer = styled.div `
|
|
234
276
|
padding-right: ${(props) => props.theme.grid.unit * 5}px;
|
|
@@ -251,10 +293,14 @@ const CitationCount = styled.div `
|
|
|
251
293
|
background-color: #fe8f1f;
|
|
252
294
|
}
|
|
253
295
|
`;
|
|
254
|
-
const NewReferenceButton = styled
|
|
296
|
+
const NewReferenceButton = styled.button `
|
|
297
|
+
${referenceButtonStyles}
|
|
298
|
+
|
|
255
299
|
svg {
|
|
256
300
|
margin-right: 8px;
|
|
257
301
|
}
|
|
302
|
+
|
|
303
|
+
${outlineStyle}
|
|
258
304
|
`;
|
|
259
305
|
const ExistingReferencesHeading = styled.h3 `
|
|
260
306
|
font-size: 18px;
|
|
@@ -291,3 +337,21 @@ const ImportFromFileButton = styled.button `
|
|
|
291
337
|
fill: #6e6e6e;
|
|
292
338
|
}
|
|
293
339
|
`;
|
|
340
|
+
const Footer = styled.div `
|
|
341
|
+
display: flex;
|
|
342
|
+
justify-content: flex-end;
|
|
343
|
+
padding: 16px 32px;
|
|
344
|
+
gap: 16px;
|
|
345
|
+
`;
|
|
346
|
+
const TertiaryButton = styled(Button) `
|
|
347
|
+
&.MuiButton-textSecondary {
|
|
348
|
+
color: #0d79d0;
|
|
349
|
+
|
|
350
|
+
&:hover {
|
|
351
|
+
background-color: rgba(13, 121, 208, 0.04);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
`;
|
|
355
|
+
const DOILinkIcon = styled(LinkIcon) `
|
|
356
|
+
transform: rotate(-45deg);
|
|
357
|
+
`;
|