@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.
Files changed (53) hide show
  1. package/dist/cjs/components/cross-ref-check-modal/CrossRefWarningModal.js +202 -52
  2. package/dist/cjs/components/cross-ref-check-modal/openModal.js +8 -2
  3. package/dist/cjs/components/references/ReferenceForm/ReferenceForm.js +22 -40
  4. package/dist/cjs/components/references/ReferenceForm/styled-components.js +1 -13
  5. package/dist/cjs/components/references/ReferencesEditor.js +26 -1
  6. package/dist/cjs/components/references/ReferencesModal.js +71 -7
  7. package/dist/cjs/lib/utils.js +12 -1
  8. package/dist/cjs/menus.js +12 -0
  9. package/dist/cjs/plugins/cross-references.js +37 -14
  10. package/dist/cjs/versions.js +1 -1
  11. package/dist/cjs/views/ReactSubView.js +1 -0
  12. package/dist/cjs/views/cross_reference.js +13 -2
  13. package/dist/cjs/views/cross_reference_editable.js +1 -4
  14. package/dist/es/components/cross-ref-check-modal/CrossRefWarningModal.js +202 -52
  15. package/dist/es/components/cross-ref-check-modal/openModal.js +9 -3
  16. package/dist/es/components/references/ReferenceForm/ReferenceForm.js +21 -40
  17. package/dist/es/components/references/ReferenceForm/styled-components.js +0 -12
  18. package/dist/es/components/references/ReferencesEditor.js +21 -0
  19. package/dist/es/components/references/ReferencesModal.js +73 -9
  20. package/dist/es/lib/utils.js +11 -1
  21. package/dist/es/menus.js +12 -0
  22. package/dist/es/plugins/cross-references.js +37 -15
  23. package/dist/es/versions.js +1 -1
  24. package/dist/es/views/ReactSubView.js +1 -0
  25. package/dist/es/views/cross_reference.js +13 -2
  26. package/dist/es/views/cross_reference_editable.js +1 -4
  27. package/dist/types/components/authors-affiliations/AuthorsAndAffiliationsModals.d.ts +1 -1
  28. package/dist/types/components/cross-ref-check-modal/CrossRefWarningModal.d.ts +4 -2
  29. package/dist/types/components/cross-ref-check-modal/openModal.d.ts +2 -2
  30. package/dist/types/components/references/ReferenceForm/ReferenceForm.d.ts +3 -3
  31. package/dist/types/components/references/ReferenceForm/styled-components.d.ts +0 -1
  32. package/dist/types/components/references/ReferencesEditor.d.ts +3 -0
  33. package/dist/types/lib/utils.d.ts +4 -0
  34. package/dist/types/plugins/cross-references.d.ts +2 -0
  35. package/dist/types/versions.d.ts +1 -1
  36. package/dist/types/views/ReactSubView.d.ts +5 -2
  37. package/dist/types/views/cross_reference.d.ts +1 -0
  38. package/package.json +2 -2
  39. package/src/components/cross-ref-check-modal/CrossRefWarningModal.tsx +315 -83
  40. package/src/components/cross-ref-check-modal/openModal.ts +14 -4
  41. package/src/components/references/ReferenceForm/ReferenceForm.tsx +21 -82
  42. package/src/components/references/ReferenceForm/styled-components.ts +0 -13
  43. package/src/components/references/ReferencesEditor.tsx +38 -0
  44. package/src/components/references/ReferencesModal.tsx +116 -14
  45. package/src/lib/utils.ts +24 -0
  46. package/src/lib/view.ts +1 -0
  47. package/src/menus.tsx +12 -0
  48. package/src/plugins/cross-references.ts +41 -13
  49. package/src/versions.ts +1 -1
  50. package/src/views/ReactSubView.tsx +6 -3
  51. package/src/views/cross_reference.ts +14 -2
  52. package/src/views/cross_reference_editable.ts +1 -4
  53. 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 { AttentionOrangeIcon, CloseButton, ModalContainer, ModalHeader, PrimaryButton, StyledModal, TertiaryButton, TextButton, } from '@manuscripts/style-guide';
18
- import { startCase } from 'lodash';
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
- return (React.createElement(Modal, { isOpen: isOpen, onRequestClose: () => handleClose(), shouldCloseOnOverlayClick: false, hideOverlay: true },
27
- React.createElement(Container, { "data-cy": "cross-reference-warning-modal" },
28
- React.createElement(ModalHeader, null,
29
- React.createElement(CloseButton, { onClick: () => handleClose(), "data-cy": "modal-close-button" })),
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(AttentionOrangeIcon, { width: 24, height: 22 }),
33
- " Delete referenced content?"),
34
- React.createElement("p", null, "You are deleting content referenced elsewhere in the document:"),
35
- React.createElement(ScrolableItems, null, xrefs.map((group, i) => (React.createElement(XrefGroupDisplay, { key: i, group: group, selectAndScrollTo: selectAndScrollTo })))),
36
- React.createElement(Actions, null,
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 & remove citation"))))));
79
+ } }, "Delete"))))));
42
80
  };
43
- const XrefGroupDisplay = ({ group, selectAndScrollTo }) => {
44
- return (React.createElement("div", null,
45
- React.createElement("h3", null, group.label),
46
- React.createElement(ReferencesList, null, group.xrefs.map(([, pos], i) => {
47
- return (React.createElement("li", { key: i },
48
- React.createElement(TextButton, { onClick: () => selectAndScrollTo(pos) }, `${startCase(pos.parent.type.name)} - ${pos.parent.textContent}`)));
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
- right: 0;
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: 480px;
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 ReferencesList = styled.ul `
114
- padding: 8px;
115
- margin-left: 0;
116
- list-style: none;
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-radius: 3px;
120
-
121
- ${TextButton} {
122
- margin-left: 0;
123
- text-decoration: underline;
124
- &:hover {
125
- text-decoration: none;
126
- }
127
- display: block;
128
- max-width: 100%;
129
- overflow: hidden;
130
- color: #353535;
131
- text-overflow: ellipsis;
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 Actions = styled.footer `
135
- text-align: right;
136
- padding-top: 1rem;
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 ScrolableItems = styled.div `
139
- max-height: 16vh;
140
- min-height: 100px;
141
- overflow-y: auto;
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
- onClose,
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, ButtonGroup, Category, DeleteIcon, Dialog, IconButton, LinkIcon, PrimaryButton, SecondaryButton, SelectField, Label, FormRow, YearField, } from '@manuscripts/style-guide';
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 { Actions, Button, DeleteButton, FormFields, ReferenceTextArea, ReferenceTextField, } from './styled-components';
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 ReferenceForm = ({ values, showDelete, onChange, onDelete, onCancel, onSave, actionsRef, }) => {
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(ScrollableModalContent, null, importSuccessCount !== null ? (React.createElement(ImportSuccessPlaceholder, { count: importSuccessCount })) : (selection && (React.createElement(ReferenceForm, { values: normalizeBlblioItem(selection), showDelete: !citationCounts.get(selection.id) &&
180
- isNewItem(selection, ['id', 'type']), onChange: handleChange, onCancel: onCancel, onDelete: handleDelete, onSave: handleSave, actionsRef: actionsRef })))))))));
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 ReferenceButton = withNavigableListItem(styled.button `
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(ReferenceButton) `
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
+ `;