@manuscripts/body-editor 3.16.0 → 3.16.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/dist/cjs/commands.js +45 -10
  2. package/dist/cjs/components/form/FormFooter.js +2 -2
  3. package/dist/cjs/components/keyboard-shortcuts-modal/KeyboardShortcutsModal.js +4 -2
  4. package/dist/cjs/components/references/CitationEditor.js +13 -19
  5. package/dist/cjs/components/references/ImportBibliography.js +77 -0
  6. package/dist/cjs/components/references/ImportBibliographyForm.js +1 -1
  7. package/dist/cjs/components/references/ImportBibliographyModal.js +12 -81
  8. package/dist/cjs/components/references/ImportReferencesConfirmation.js +113 -0
  9. package/dist/cjs/components/references/ImportSuccessPlaceholder.js +56 -0
  10. package/dist/cjs/components/references/ReferenceSearch.js +2 -5
  11. package/dist/cjs/components/references/ReferencesEditor.js +18 -2
  12. package/dist/cjs/components/references/ReferencesModal.js +86 -33
  13. package/dist/cjs/components/views/CrossReferenceItems.js +139 -52
  14. package/dist/cjs/components/views/DeleteSupplementDialog.js +1 -1
  15. package/dist/cjs/lib/supplements.js +9 -1
  16. package/dist/cjs/lib/view.js +39 -1
  17. package/dist/cjs/menus.js +35 -0
  18. package/dist/cjs/plugins/objects.js +1 -1
  19. package/dist/cjs/toolbar.js +6 -0
  20. package/dist/cjs/versions.js +1 -1
  21. package/dist/cjs/views/bibliography_element.js +1 -2
  22. package/dist/cjs/views/citation_editable.js +2 -29
  23. package/dist/cjs/views/cross_reference.js +12 -2
  24. package/dist/cjs/views/cross_reference_editable.js +95 -4
  25. package/dist/cjs/views/figure_editable.js +3 -0
  26. package/dist/es/commands.js +43 -9
  27. package/dist/es/components/form/FormFooter.js +2 -2
  28. package/dist/es/components/keyboard-shortcuts-modal/KeyboardShortcutsModal.js +4 -2
  29. package/dist/es/components/references/CitationEditor.js +13 -19
  30. package/dist/es/components/references/ImportBibliography.js +70 -0
  31. package/dist/es/components/references/ImportBibliographyForm.js +1 -1
  32. package/dist/es/components/references/ImportBibliographyModal.js +13 -79
  33. package/dist/es/components/references/ImportReferencesConfirmation.js +73 -0
  34. package/dist/es/components/references/ImportSuccessPlaceholder.js +49 -0
  35. package/dist/es/components/references/ReferenceSearch.js +3 -6
  36. package/dist/es/components/references/ReferencesEditor.js +19 -3
  37. package/dist/es/components/references/ReferencesModal.js +88 -35
  38. package/dist/es/components/views/CrossReferenceItems.js +141 -54
  39. package/dist/es/components/views/DeleteSupplementDialog.js +2 -2
  40. package/dist/es/lib/supplements.js +8 -1
  41. package/dist/es/lib/view.js +36 -0
  42. package/dist/es/menus.js +36 -1
  43. package/dist/es/plugins/objects.js +1 -1
  44. package/dist/es/toolbar.js +8 -2
  45. package/dist/es/versions.js +1 -1
  46. package/dist/es/views/bibliography_element.js +2 -3
  47. package/dist/es/views/citation_editable.js +3 -30
  48. package/dist/es/views/cross_reference.js +12 -2
  49. package/dist/es/views/cross_reference_editable.js +96 -5
  50. package/dist/es/views/figure_editable.js +3 -0
  51. package/dist/types/commands.d.ts +1 -0
  52. package/dist/types/components/form/FormFooter.d.ts +2 -1
  53. package/dist/types/components/references/CitationEditor.d.ts +1 -1
  54. package/dist/types/components/references/ImportBibliography.d.ts +7 -0
  55. package/dist/types/components/references/ImportReferencesConfirmation.d.ts +8 -0
  56. package/dist/types/components/references/ImportSuccessPlaceholder.d.ts +5 -0
  57. package/dist/types/components/references/ReferenceSearch.d.ts +0 -1
  58. package/dist/types/components/references/ReferencesEditor.d.ts +4 -1
  59. package/dist/types/components/references/ReferencesModal.d.ts +1 -0
  60. package/dist/types/components/views/CrossReferenceItems.d.ts +3 -0
  61. package/dist/types/lib/supplements.d.ts +1 -0
  62. package/dist/types/lib/view.d.ts +3 -1
  63. package/dist/types/versions.d.ts +1 -1
  64. package/dist/types/views/citation_editable.d.ts +0 -1
  65. package/dist/types/views/cross_reference_editable.d.ts +6 -0
  66. package/package.json +4 -4
  67. package/src/commands.ts +52 -10
  68. package/src/components/ChangeHandlingForm.tsx +4 -2
  69. package/src/components/form/FormFooter.tsx +3 -1
  70. package/src/components/keyboard-shortcuts-modal/KeyboardShortcutsModal.tsx +6 -7
  71. package/src/components/references/CitationEditor.tsx +16 -25
  72. package/src/components/references/ImportBibliography.tsx +119 -0
  73. package/src/components/references/ImportBibliographyForm.tsx +1 -1
  74. package/src/components/references/ImportBibliographyModal.tsx +34 -110
  75. package/src/components/references/ImportReferencesConfirmation.tsx +133 -0
  76. package/src/components/references/ImportSuccessPlaceholder.tsx +93 -0
  77. package/src/components/references/ReferenceSearch.tsx +0 -7
  78. package/src/components/references/ReferencesEditor.tsx +35 -6
  79. package/src/components/references/ReferencesModal.tsx +164 -78
  80. package/src/components/toolbar/InsertEmbedDialog.tsx +6 -2
  81. package/src/components/toolbar/type-selector/styles.ts +2 -1
  82. package/src/components/views/CrossReferenceItems.tsx +227 -100
  83. package/src/components/views/DeleteSupplementDialog.tsx +3 -6
  84. package/src/lib/footnotes.ts +3 -1
  85. package/src/lib/supplements.ts +13 -1
  86. package/src/lib/utils.ts +15 -4
  87. package/src/lib/view.ts +65 -0
  88. package/src/menus.tsx +39 -0
  89. package/src/plugins/add-subtitle.ts +18 -16
  90. package/src/plugins/objects.ts +2 -6
  91. package/src/toolbar.tsx +9 -0
  92. package/src/versions.ts +1 -1
  93. package/src/views/bibliography_element.ts +3 -4
  94. package/src/views/citation_editable.ts +5 -50
  95. package/src/views/cross_reference.ts +17 -3
  96. package/src/views/cross_reference_editable.ts +123 -11
  97. package/src/views/figure_editable.ts +4 -0
  98. package/src/views/supplement.ts +0 -1
  99. package/src/views/supplements.ts +2 -2
@@ -0,0 +1,73 @@
1
+ import { CheckboxField, CheckboxLabel, FormActionsBar, ModalTitle, PrimaryButton, SecondaryButton, } from '@manuscripts/style-guide';
2
+ import React, { useState } from 'react';
3
+ import styled from 'styled-components';
4
+ import { ReferenceLine } from './ReferenceLine';
5
+ export const ImportReferencesConfirmation = ({ items, onCancel, onConfirm }) => {
6
+ const [selected, setSelected] = useState(() => items.map(() => true));
7
+ const selectedCount = selected.filter(Boolean).length;
8
+ const countLabel = selectedCount === 1 ? 'reference' : 'references';
9
+ const toggleItem = (index) => {
10
+ setSelected((current) => current.map((value, i) => (i === index ? !value : value)));
11
+ };
12
+ const handleConfirm = () => {
13
+ onConfirm(items.filter((_, index) => selected[index]));
14
+ };
15
+ return (React.createElement(React.Fragment, null,
16
+ React.createElement(Title, null, "Import References"),
17
+ React.createElement(Subtitle, null,
18
+ "Importing ",
19
+ selectedCount,
20
+ " ",
21
+ countLabel),
22
+ React.createElement(List, null, items.map((item, index) => (React.createElement(ListItem, { key: index },
23
+ React.createElement(ItemCheckbox, null,
24
+ React.createElement(CheckboxField, { checked: selected[index], "aria-label": `Select reference ${index + 1}`, onChange: () => toggleItem(index) }),
25
+ React.createElement("div", null)),
26
+ React.createElement(ReferenceLine, { item: item }))))),
27
+ React.createElement(FormActionsBar, null,
28
+ React.createElement(SecondaryButton, { type: "button", onClick: onCancel }, "Cancel"),
29
+ React.createElement(PrimaryButton, { type: "button", disabled: selectedCount === 0, onClick: handleConfirm },
30
+ "Import ",
31
+ selectedCount,
32
+ " ",
33
+ countLabel))));
34
+ };
35
+ const Title = styled(ModalTitle) `
36
+ border-bottom: 1px solid ${(props) => props.theme.colors.border.secondary};
37
+ margin: 0 -${(props) => 6 * props.theme.grid.unit}px 20px;
38
+ padding: 0 ${(props) => 6 * props.theme.grid.unit}px
39
+ ${(props) => props.theme.grid.unit * 4}px;
40
+ `;
41
+ const Subtitle = styled.p `
42
+ color: ${(props) => props.theme.colors.text.secondary};
43
+ font-weight: ${(props) => props.theme.font.weight.bold};
44
+ margin: 0 0 16px;
45
+ font-size: 14px;
46
+ `;
47
+ const List = styled.div `
48
+ display: flex;
49
+ flex-direction: column;
50
+ margin-bottom: ${(props) => props.theme.grid.unit * 5}px;
51
+ overflow-y: auto;
52
+ `;
53
+ const ListItem = styled.div `
54
+ border-bottom: 1px solid ${(props) => props.theme.colors.border.secondary};
55
+ display: flex;
56
+ align-items: flex-start;
57
+ gap: ${(props) => props.theme.grid.unit * 3}px;
58
+ padding: ${(props) => props.theme.grid.unit * 3}px 0;
59
+ `;
60
+ const ItemCheckbox = styled(CheckboxLabel) `
61
+ flex-shrink: 0;
62
+
63
+ > div {
64
+ margin: 0;
65
+
66
+ &::before {
67
+ width: 18px;
68
+ height: 18px;
69
+ border-radius: 4px;
70
+ margin: 0;
71
+ }
72
+ }
73
+ `;
@@ -0,0 +1,49 @@
1
+ import React from 'react';
2
+ import styled from 'styled-components';
3
+ export const ImportSuccessPlaceholder = ({ count }) => {
4
+ const countLabel = count === 1 ? 'reference' : 'references';
5
+ return (React.createElement(Container, { "data-cy": "import-success-placeholder" },
6
+ React.createElement(IconCircle, null,
7
+ React.createElement(SuccessCheckIcon, null)),
8
+ React.createElement(Title, null, "Successfully Imported"),
9
+ React.createElement(Message, null,
10
+ "Your library has been updated with ",
11
+ count,
12
+ " new ",
13
+ countLabel,
14
+ ".")));
15
+ };
16
+ const SuccessCheckIcon = () => (React.createElement("svg", { width: "32", height: "32", viewBox: "0 0 32 32", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true" },
17
+ React.createElement("path", { d: "M26.6656 8L12.0004 22.6656L5.3344 15.9994", stroke: "#36B260", strokeWidth: "3", strokeLinecap: "round" })));
18
+ const Container = styled.div `
19
+ display: flex;
20
+ flex-direction: column;
21
+ align-items: center;
22
+ justify-content: center;
23
+ gap: ${(props) => props.theme.grid.unit * 4}px;
24
+ height: 100%;
25
+ padding: 40px;
26
+ box-sizing: border-box;
27
+ text-align: center;
28
+ `;
29
+ const IconCircle = styled.div `
30
+ display: flex;
31
+ align-items: center;
32
+ justify-content: center;
33
+ width: 64px;
34
+ height: 64px;
35
+ border-radius: 32px;
36
+ background: #f6ffed;
37
+ `;
38
+ const Title = styled.h2 `
39
+ margin: 0;
40
+ font-size: 24px;
41
+ line-height: 32px;
42
+ letter-spacing: -0.37px;
43
+ color: ${(props) => props.theme.colors.text.primary};
44
+ `;
45
+ const Message = styled.p `
46
+ margin: 0;
47
+ line-height: ${(props) => props.theme.font.lineHeight.large};
48
+ color: ${(props) => props.theme.colors.text.secondary};
49
+ `;
@@ -13,7 +13,7 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  */
16
- import { AddNewIcon, ButtonGroup, IconTextButton, PrimaryButton, SecondaryButton, UploadIcon, withFocusTrap, } from '@manuscripts/style-guide';
16
+ import { AddNewIcon, ButtonGroup, IconTextButton, PrimaryButton, SecondaryButton, withFocusTrap, } from '@manuscripts/style-guide';
17
17
  import debounce from 'lodash/debounce';
18
18
  import React, { useState } from 'react';
19
19
  import styled from 'styled-components';
@@ -50,7 +50,7 @@ const AddReferenceActions = styled(ButtonGroup) `
50
50
  }
51
51
  }
52
52
  `;
53
- export const ReferenceSearch = ({ query: initialQuery, sources, items, onAdd, onImport, onCite, onCancel, }) => {
53
+ export const ReferenceSearch = ({ query: initialQuery, sources, items, onAdd, onCite, onCancel, }) => {
54
54
  const [query, setQuery] = useState(initialQuery || '');
55
55
  const [selections, setSelections] = useState(new Map());
56
56
  const toggleSelection = (item) => {
@@ -86,10 +86,7 @@ export const ReferenceSearch = ({ query: initialQuery, sources, items, onAdd, on
86
86
  React.createElement(AddReferenceActions, null,
87
87
  React.createElement(IconTextButton, { onClick: onAdd },
88
88
  React.createElement(AddNewIcon, null),
89
- "Add new"),
90
- React.createElement(IconTextButton, { onClick: onImport },
91
- React.createElement(UploadIcon, null),
92
- "Import new")),
89
+ "Add new")),
93
90
  React.createElement(ButtonGroup, null,
94
91
  React.createElement(SecondaryButton, { onClick: onCancel }, "Close"),
95
92
  React.createElement(PrimaryButton, { onClick: handleClick, disabled: selections.size === 0 }, "Cite")))));
@@ -1,4 +1,5 @@
1
- import React, { useReducer, useState } from 'react';
1
+ import { generateNodeID, schema, } from '@manuscripts/transform';
2
+ import React, { useEffect, useReducer, useState } from 'react';
2
3
  import { attrsReducer } from '../../lib/array-reducer';
3
4
  import { cleanItemValues } from '../../lib/utils';
4
5
  import { ReferencesModal } from './ReferencesModal';
@@ -6,9 +7,13 @@ const itemsReducer = attrsReducer();
6
7
  export const ReferencesEditor = (props) => {
7
8
  const [isOpen, setOpen] = useState(true);
8
9
  const [items, dispatch] = useReducer(itemsReducer, props.items);
10
+ const [selectedItem, setSelectedItem] = useState(props.item);
11
+ useEffect(() => {
12
+ setSelectedItem(props.item);
13
+ }, [props.item]);
9
14
  const handleSave = (item) => {
10
15
  const cleanedItem = cleanItemValues(item);
11
- props.onSave(cleanedItem);
16
+ props.onSave([cleanedItem]);
12
17
  dispatch({
13
18
  type: 'update',
14
19
  items: [cleanedItem],
@@ -21,5 +26,16 @@ export const ReferencesEditor = (props) => {
21
26
  item: item,
22
27
  });
23
28
  };
24
- return (React.createElement(ReferencesModal, { ...props, isOpen: isOpen, onCancel: () => setOpen(false), items: items, onSave: handleSave, onDelete: handleDelete }));
29
+ const handleImport = (data) => {
30
+ const newItems = data.map((item) => cleanItemValues({
31
+ ...item,
32
+ id: generateNodeID(schema.nodes.bibliography_item),
33
+ }));
34
+ props.onSave(newItems);
35
+ dispatch({
36
+ type: 'set',
37
+ state: [...items, ...newItems],
38
+ });
39
+ };
40
+ return (React.createElement(ReferencesModal, { isOpen: isOpen, onCancel: () => setOpen(false), items: items, item: selectedItem, citationCounts: props.citationCounts, onSave: handleSave, onDelete: handleDelete, handleImport: handleImport }));
25
41
  };
@@ -1,9 +1,11 @@
1
- import { Category, CitationCountIcon, CloseButton, Dialog, ModalBody, ModalContainer, ModalHeader, ModalSidebar, ModalSidebarHeader, ModalSidebarTitle, ScrollableModalContent, SidebarContent, StyledModal, useScrollDetection, withListNavigation, withNavigableListItem, } from '@manuscripts/style-guide';
1
+ import { AddAuthorIcon, Category, CitationCountIcon, CloseButton, Dialog, ModalBody, ModalContainer, ModalHeader, ModalSidebar, ModalSidebarHeader, ModalSidebarTitle, ScrollableModalContent, SidebarContent, StyledModal, useScrollDetection, withListNavigation, withNavigableListItem, } from '@manuscripts/style-guide';
2
2
  import isEqual from 'lodash/isEqual';
3
- import React, { useEffect, useRef, useState } from 'react';
3
+ import React, { useEffect, useMemo, useRef, useState } from 'react';
4
4
  import styled from 'styled-components';
5
5
  import { ReferenceForm, } from './ReferenceForm/ReferenceForm';
6
6
  import { ReferenceLine } from './ReferenceLine';
7
+ import { ImportBibliographyModal } from './ImportBibliographyModal';
8
+ import { ImportSuccessPlaceholder } from './ImportSuccessPlaceholder';
7
9
  const ReferencesModalContainer = styled(ModalContainer) `
8
10
  min-width: 960px;
9
11
  `;
@@ -13,6 +15,33 @@ const ReferencesSidebar = styled(ModalSidebar) `
13
15
  const ReferencesSidebarContent = styled(SidebarContent) `
14
16
  overflow-y: auto;
15
17
  `;
18
+ const ImportFromFileFooter = styled.div `
19
+ padding: 16px;
20
+ `;
21
+ const ImportFromFileButton = styled.button `
22
+ display: flex;
23
+ width: 100%;
24
+ padding: 8px 24px;
25
+ justify-content: center;
26
+ align-items: center;
27
+ gap: 8px;
28
+ border-radius: 4px;
29
+ border: 1px dashed #c9c9c9;
30
+ background: #fafafa;
31
+ cursor: pointer;
32
+ font-family: ${(props) => props.theme.font.family.sans};
33
+ font-size: 14px;
34
+ color: #353535;
35
+
36
+ svg {
37
+ width: 20px;
38
+ height: 20px;
39
+ }
40
+
41
+ svg rect {
42
+ fill: #6e6e6e;
43
+ }
44
+ `;
16
45
  const ReferencesInnerWrapper = withListNavigation(styled.div `
17
46
  width: 100%;
18
47
  padding: 12px 0;
@@ -100,15 +129,25 @@ const normalize = (item) => ({
100
129
  accessed: item.accessed,
101
130
  comment: item.comment || '',
102
131
  });
103
- export const ReferencesModal = ({ isOpen, onCancel, items, item, citationCounts, onSave, onDelete, }) => {
132
+ export const ReferencesModal = ({ isOpen, onCancel, items, item, citationCounts, onSave, onDelete, handleImport, }) => {
133
+ const [importing, setImporting] = useState(false);
134
+ const [importSuccessCount, setImportSuccessCount] = useState(null);
104
135
  const [confirm, setConfirm] = useState(false);
105
136
  const valuesRef = useRef(undefined);
106
137
  const [selection, setSelection] = useState();
107
138
  const selectionRef = useRef(null);
139
+ const sortedItems = useMemo(() => [...items].sort((a, b) => {
140
+ const aUncited = (citationCounts.get(a.id) ?? 0) === 0;
141
+ const bUncited = (citationCounts.get(b.id) ?? 0) === 0;
142
+ if (aUncited === bUncited) {
143
+ return 0;
144
+ }
145
+ return aUncited ? 1 : -1;
146
+ }), [items, citationCounts]);
108
147
  const isSelected = (item) => {
109
148
  return item.id === selection?.id;
110
149
  };
111
- const selectionIndex = items.findIndex(isSelected);
150
+ const selectionIndex = sortedItems.findIndex(isSelected);
112
151
  useEffect(() => {
113
152
  setSelection(item);
114
153
  }, [item]);
@@ -126,8 +165,8 @@ export const ReferencesModal = ({ isOpen, onCancel, items, item, citationCounts,
126
165
  useEffect(() => {
127
166
  const base = Math.max(0, selectionIndex - selectionTopOffset);
128
167
  setStartIndex(base);
129
- setEndIndex(Math.min(items.length - 1, base + pageSize));
130
- }, [selectionIndex, items]);
168
+ setEndIndex(Math.min(sortedItems.length - 1, base + pageSize));
169
+ }, [selectionIndex, sortedItems]);
131
170
  useEffect(() => {
132
171
  if (triggers.top) {
133
172
  const newFirst = Math.max(0, startIndex - pageSize);
@@ -135,11 +174,11 @@ export const ReferencesModal = ({ isOpen, onCancel, items, item, citationCounts,
135
174
  setEndIndex(Math.min(newFirst + dropLimit, endIndex));
136
175
  }
137
176
  if (triggers.bottom) {
138
- const newLast = Math.min(items.length - 1, endIndex + pageSize);
177
+ const newLast = Math.min(sortedItems.length - 1, endIndex + pageSize);
139
178
  setEndIndex(newLast);
140
179
  setStartIndex(Math.max(newLast - dropLimit, startIndex));
141
180
  }
142
- }, [triggers, items]);
181
+ }, [triggers, sortedItems]);
143
182
  const actionsRef = useRef(undefined);
144
183
  const reset = () => {
145
184
  actionsRef.current?.reset();
@@ -173,7 +212,9 @@ export const ReferencesModal = ({ isOpen, onCancel, items, item, citationCounts,
173
212
  onDelete(selection);
174
213
  setSelection(undefined);
175
214
  };
215
+ const clearImportSuccess = () => setImportSuccessCount(null);
176
216
  const handleItemClick = (item) => {
217
+ clearImportSuccess();
177
218
  const values = valuesRef.current;
178
219
  if (values && selection && !isEqual(values, normalize(selection))) {
179
220
  setConfirm(true);
@@ -184,33 +225,45 @@ export const ReferencesModal = ({ isOpen, onCancel, items, item, citationCounts,
184
225
  const handleChange = (values) => {
185
226
  valuesRef.current = values;
186
227
  };
187
- if (items.length <= 0) {
228
+ const handleImportSave = (data) => {
229
+ handleImport(data);
230
+ setImporting(false);
231
+ setSelection(undefined);
232
+ setImportSuccessCount(data.length);
233
+ };
234
+ if (sortedItems.length <= 0) {
188
235
  return React.createElement(React.Fragment, null);
189
236
  }
190
- return (React.createElement(StyledModal, { isOpen: isOpen, onRequestClose: onCancel },
191
- React.createElement(Dialog, { isOpen: confirm, category: Category.confirmation, header: "You've made changes to this option", message: "Would you like to save or discard your changes?", actions: {
192
- secondary: {
193
- action: () => reset(),
194
- title: 'Discard',
195
- },
196
- primary: {
197
- action: () => handleSave(valuesRef.current),
198
- title: 'Save',
199
- },
200
- } }),
201
- React.createElement(ReferencesModalContainer, { "data-cy": 'references-editor' },
202
- React.createElement(ModalHeader, null,
203
- React.createElement(CloseButton, { onClick: onCancel })),
204
- React.createElement(ModalBody, null,
205
- React.createElement(ReferencesSidebar, null,
206
- React.createElement(ModalSidebarHeader, null,
207
- React.createElement(ModalSidebarTitle, null, "References")),
208
- React.createElement(ReferencesSidebarContent, { ref: ref },
209
- React.createElement(ReferencesInnerWrapper, null, items.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 },
210
- React.createElement(IconContainer, null,
211
- React.createElement(CitationCountIcon, null),
212
- (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"))),
213
- React.createElement(ReferenceLine, { item: item }))))))),
214
- React.createElement(ScrollableModalContent, null, selection && (React.createElement(ReferenceForm, { values: normalize(selection), showDelete: !citationCounts.get(selection.id) &&
215
- isNewItem(selection, ['id', 'type']), onChange: handleChange, onCancel: onCancel, onDelete: handleDelete, onSave: handleSave, actionsRef: actionsRef })))))));
237
+ return (React.createElement(React.Fragment, null,
238
+ importing && (React.createElement(ImportBibliographyModal, { onCancel: () => setImporting(false), onSave: handleImportSave })),
239
+ React.createElement(StyledModal, { isOpen: isOpen, onRequestClose: onCancel },
240
+ React.createElement(Dialog, { isOpen: confirm, category: Category.confirmation, header: "You've made changes to this option", message: "Would you like to save or discard your changes?", actions: {
241
+ secondary: {
242
+ action: () => reset(),
243
+ title: 'Discard',
244
+ },
245
+ primary: {
246
+ action: () => handleSave(valuesRef.current),
247
+ title: 'Save',
248
+ },
249
+ } }),
250
+ React.createElement(ReferencesModalContainer, { "data-cy": 'references-editor' },
251
+ React.createElement(ModalHeader, null,
252
+ React.createElement(CloseButton, { "data-cy": "modal-close-button", onClick: onCancel })),
253
+ React.createElement(ModalBody, null,
254
+ React.createElement(ReferencesSidebar, null,
255
+ React.createElement(ModalSidebarHeader, null,
256
+ React.createElement(ModalSidebarTitle, null, "References")),
257
+ React.createElement(ReferencesSidebarContent, { ref: ref },
258
+ React.createElement(ReferencesInnerWrapper, null, 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 },
259
+ React.createElement(IconContainer, null,
260
+ React.createElement(CitationCountIcon, null),
261
+ (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"))),
262
+ React.createElement(ReferenceLine, { item: item })))))),
263
+ React.createElement(ImportFromFileFooter, null,
264
+ React.createElement(ImportFromFileButton, { type: "button", "data-cy": "import-from-file-button", onClick: () => setImporting(true) },
265
+ React.createElement(AddAuthorIcon, null),
266
+ "Import from file"))),
267
+ React.createElement(ScrollableModalContent, null, importSuccessCount !== null ? (React.createElement(ImportSuccessPlaceholder, { count: importSuccessCount })) : (selection && (React.createElement(ReferenceForm, { values: normalize(selection), showDelete: !citationCounts.get(selection.id) &&
268
+ isNewItem(selection, ['id', 'type']), onChange: handleChange, onCancel: onCancel, onDelete: handleDelete, onSave: handleSave, actionsRef: actionsRef })))))))));
216
269
  };
@@ -13,74 +13,122 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  */
16
- import { ButtonGroup, PrimaryButton, SecondaryButton, TextArea, withFocusTrap, withListNavigation, withNavigableListItem, } from '@manuscripts/style-guide';
17
- import React, { useEffect, useRef, useState } from 'react';
16
+ import { CloseButton, ExpandableSection, PrimaryButton, withListNavigation, withNavigableListItem, FileUnknownIcon, getFileIcon, ArrowDownIcon, StyledModal, ModalContainer, ModalHeader, ModalBody, WebLinkIcon, } from '@manuscripts/style-guide';
17
+ import { schema } from '@manuscripts/transform';
18
+ import React, { useCallback, useEffect, useRef, useState } from 'react';
18
19
  import styled from 'styled-components';
19
- const Container = withFocusTrap(styled.div `
20
- padding: ${(props) => props.theme.grid.unit * 3}px
21
- ${(props) => props.theme.grid.unit * 4}px;
22
- display: flex;
23
- flex-direction: column;
24
- max-height: 60vh;
25
- overflow: hidden;
26
- `);
27
- const Actions = styled.div `
28
- display: flex;
29
- align-items: center;
30
- justify-content: flex-end;
31
- flex-shrink: 0;
32
- `;
20
+ import { allowedHref } from '../../lib/url';
21
+ import { nodeTypeIcon } from '../../node-type-icons';
22
+ import FormFooter from '../form/FormFooter';
33
23
  const Items = withListNavigation(styled.div `
34
24
  flex: 1;
35
25
  overflow-y: auto;
36
- padding: ${(props) => props.theme.grid.unit * 3}px;
37
26
  `);
38
27
  const CrossReferenceItem = withNavigableListItem(styled.div `
39
28
  cursor: pointer;
40
- padding: ${(props) => props.theme.grid.unit * 4}px;
41
- background-color: ${(props) => props.$isSelected
42
- ? props.theme.colors.background.selected
43
- : props.theme.colors.background.primary};
29
+ padding: ${(props) => props.theme.grid.unit * 2}px;
30
+ background-color: ${(props) => props.theme.colors.background.primary};
44
31
  transition: background-color 0.1s;
45
- border: solid
46
- ${(props) => props.$isSelected
47
- ? props.theme.colors.brand.medium
48
- : props.theme.colors.border.secondary};
49
- border-width: 1px 0;
50
- z-index: ${(props) => (props.$isSelected ? '1' : '0')};
32
+ border: 1px solid ${(props) => props.theme.colors.border.secondary};
33
+ overflow: hidden;
34
+ max-width: 100%;
35
+ box-sizing: border-box;
36
+ margin-bottom: ${(props) => props.theme.grid.unit * 2}px;
51
37
 
52
38
  &:hover {
53
- background-color: ${(props) => props.theme.colors.background.selected};
39
+ background-color: #f2f2f2;
40
+ }
41
+
42
+ &.active {
43
+ background-color: #f2f2f2;
44
+ z-index: 1;
54
45
  }
55
46
  `);
56
47
  const Label = styled.span `
57
48
  color: ${(props) => props.theme.colors.text.primary};
49
+ -webkit-line-clamp: 2;
50
+ -webkit-box-orient: vertical;
51
+ overflow: hidden;
52
+ word-break: break-all;
53
+ overflow-wrap: break-word;
54
+ min-width: fit-content;
58
55
  `;
59
56
  const Caption = styled.span `
60
57
  color: ${(props) => props.theme.colors.text.secondary};
58
+ display: -webkit-box;
59
+ -webkit-line-clamp: 2;
60
+ -webkit-box-orient: vertical;
61
+ overflow: hidden;
62
+ word-break: break-all;
63
+ overflow-wrap: break-word;
64
+ margin-left: 3.2px;
61
65
  `;
62
66
  const Heading = styled.div `
63
- display: flex;
64
- align-items: center;
65
- justify-content: space-between;
66
- flex-shrink: 0;
67
+ font-size: ${(props) => props.theme.font.size.medium};
67
68
  font-weight: ${(props) => props.theme.font.weight.bold};
69
+ color: ${(props) => props.theme.colors.text.primary};
70
+ margin-bottom: ${(props) => props.theme.grid.unit * 3}px;
68
71
  `;
69
72
  const Empty = styled.div `
70
73
  margin-bottom: ${(props) => props.theme.grid.unit * 4}px;
71
74
  color: ${(props) => props.theme.colors.text.tertiary};
72
75
  `;
76
+ const GroupExpandableSection = styled(ExpandableSection) `
77
+ & > div:first-child {
78
+ flex-direction: row-reverse;
79
+ justify-content: flex-end;
80
+ padding-left: 0;
81
+ gap: 10px;
82
+ font-weight: ${(props) => props.theme.font.weight.normal};
83
+ svg {
84
+ width: 20px;
85
+ height: 20px;
86
+ }
87
+ }
88
+ `;
73
89
  const DefaultLabelWrapper = styled.div `
74
- margin-bottom: ${(props) => props.theme.grid.unit * 2}px;
90
+ display: flex;
91
+ align-items: center;
92
+ min-width: 0;
75
93
  `;
76
- const CustomTextArea = styled(TextArea) `
77
- width: 100%;
78
- height: 75px;
94
+ const ItemIcon = styled.span `
95
+ flex-shrink: 0;
96
+ display: flex;
97
+ align-items: center;
79
98
  color: ${(props) => props.theme.colors.text.secondary};
80
- background-color: ${(props) => props.theme.colors.background.primary} !important;
99
+ margin-right: ${(props) => props.theme.grid.unit * 2}px;
100
+ svg {
101
+ width: 20px;
102
+ height: 20px;
103
+ }
104
+ `;
105
+ const FieldLabel = styled.label `
106
+ font-size: ${(props) => props.theme.font.size.normal};
107
+ color: ${(props) => props.theme.colors.text.secondary};
108
+ margin-top: ${(props) => props.theme.grid.unit * 4}px;
109
+ margin-bottom: ${(props) => props.theme.grid.unit}px;
110
+ `;
111
+ const CustomTextInput = styled.input `
112
+ color: ${(props) => props.theme.colors.text.secondary};
113
+ background-color: ${(props) => props.theme.colors.background.primary};
81
114
  border: 1px solid ${(props) => props.theme.colors.border.secondary};
82
115
  border-radius: ${(props) => props.theme.grid.radius.small};
83
116
  padding: ${(props) => props.theme.grid.unit * 2}px;
117
+ &:disabled {
118
+ cursor: not-allowed;
119
+ background-color: ${(props) => props.theme.colors.background.disabled};
120
+ color: ${(props) => props.theme.colors.text.muted};
121
+ }
122
+ `;
123
+ const StyledModalBody = styled(ModalBody) `
124
+ display: flex;
125
+ flex-direction: column;
126
+ padding: ${(props) => props.theme.grid.unit * 5}px
127
+ ${(props) => props.theme.grid.unit * 6}px;
128
+ max-height: 580px;
129
+ min-width: 600px;
130
+ max-width: 600px;
131
+ overflow: hidden;
84
132
  `;
85
133
  const trimmedCaption = (caption, limit) => {
86
134
  if (caption.length <= limit) {
@@ -89,7 +137,27 @@ const trimmedCaption = (caption, limit) => {
89
137
  const captionSearch = new RegExp(`^(.{${limit}}[^\\s]*).*`);
90
138
  return caption.replace(captionSearch, '$1…');
91
139
  };
92
- export const CrossReferenceItems = ({ targets, handleSelect, handleCancel, currentTargetId, currentCustomLabel, }) => {
140
+ const getTargetIcon = (target, files) => {
141
+ if (target.type === schema.nodes.supplement.name) {
142
+ if (target.href && allowedHref(target.href)) {
143
+ return React.createElement(WebLinkIcon, { className: "file-icon" });
144
+ }
145
+ const fileName = files.find((f) => f.id === target.href)?.name ?? target.label ?? '';
146
+ return getFileIcon(fileName) ?? React.createElement(FileUnknownIcon, null);
147
+ }
148
+ return nodeTypeIcon(schema.nodes[target.type]);
149
+ };
150
+ const GROUP_LABELS = {
151
+ figure_element: 'Figures',
152
+ table_element: 'Tables',
153
+ equation_element: 'Equations',
154
+ listing_element: 'Listings',
155
+ box_element: 'Boxes',
156
+ embed: 'Media',
157
+ supplement: 'Supplementary files',
158
+ };
159
+ export const CrossReferenceItems = ({ targets, files, handleSelect, handleCancel, currentTargetId, currentCustomLabel, isEdit = false, }) => {
160
+ const [isOpen, setIsOpen] = useState(true);
93
161
  const [selectedItem, setSelectedItem] = useState('');
94
162
  const customTextRef = useRef(null);
95
163
  useEffect(() => {
@@ -97,20 +165,39 @@ export const CrossReferenceItems = ({ targets, handleSelect, handleCancel, curre
97
165
  setSelectedItem(currentTargetId);
98
166
  }
99
167
  }, [currentTargetId]);
100
- return (React.createElement(Container, null,
101
- React.createElement(Heading, null, "Insert Cross-reference"),
102
- React.createElement(Items, null, targets.length ? (targets.map((target) => (React.createElement(CrossReferenceItem, { key: target.id, "$isSelected": selectedItem === target.id, onClick: () => setSelectedItem(target.id) },
103
- React.createElement(DefaultLabelWrapper, null,
104
- React.createElement(Label, null, target.label),
105
- React.createElement(Caption, null, target.caption && ': ' + trimmedCaption(target.caption, 200))),
106
- selectedItem === target.id && (React.createElement(CustomTextArea, { ref: customTextRef, placeholder: 'Or type custom text', defaultValue: currentTargetId &&
107
- currentTargetId === selectedItem &&
108
- currentCustomLabel
109
- ? currentCustomLabel
110
- : '' })))))) : (React.createElement(Empty, null, "No cross-reference targets available."))),
111
- React.createElement(Actions, null,
112
- React.createElement(ButtonGroup, null,
113
- React.createElement(SecondaryButton, { onClick: handleCancel }, "Cancel"),
114
- React.createElement(PrimaryButton, { onClick: () => selectedItem &&
115
- handleSelect(selectedItem, customTextRef.current?.value || ''), disabled: !selectedItem }, "Insert")))));
168
+ const close = useCallback(() => {
169
+ setIsOpen(false);
170
+ handleCancel();
171
+ }, [handleCancel]);
172
+ const grouped = targets.reduce((acc, t) => {
173
+ var _a;
174
+ ;
175
+ (acc[_a = t.type] ?? (acc[_a] = [])).push(t);
176
+ return acc;
177
+ }, {});
178
+ return (React.createElement(StyledModal, { isOpen: isOpen, onRequestClose: close, shouldCloseOnOverlayClick: true },
179
+ React.createElement(ModalContainer, { "data-cy": "cross-reference-editor" },
180
+ React.createElement(ModalHeader, null,
181
+ React.createElement(CloseButton, { onClick: close })),
182
+ React.createElement(StyledModalBody, null,
183
+ React.createElement(Heading, null, isEdit ? 'Edit Cross-reference' : 'Insert Cross-reference'),
184
+ React.createElement(FieldLabel, { htmlFor: "cross-reference-custom-text" }, "Custom display text"),
185
+ React.createElement(CustomTextInput, { ref: customTextRef, id: "cross-reference-custom-text", "data-cy": "cross-reference-custom-text", type: "text", placeholder: 'Custom display text...', disabled: !selectedItem, defaultValue: currentCustomLabel ?? '' }),
186
+ React.createElement(Items, null, Object.keys(grouped).length ? (Object.entries(grouped).map(([type, group]) => (React.createElement(GroupExpandableSection, { key: type, title: GROUP_LABELS[type], icon: ArrowDownIcon }, group.map((target) => {
187
+ const icon = getTargetIcon(target, files);
188
+ return (React.createElement(CrossReferenceItem, { key: target.id, "data-cy": "cross-reference-item", className: selectedItem === target.id ? 'active' : undefined, onClick: () => setSelectedItem(target.id) },
189
+ React.createElement(DefaultLabelWrapper, null,
190
+ icon ? React.createElement(ItemIcon, null, icon) : null,
191
+ React.createElement(Label, null,
192
+ target.label,
193
+ target.caption && `:`),
194
+ React.createElement(Caption, null, trimmedCaption(target.caption, 200)))));
195
+ }))))) : (React.createElement(Empty, null, "No cross-reference targets available.")))),
196
+ React.createElement(FormFooter, { onCancel: close, cancelLabel: "Cancel", primaryAction: React.createElement(PrimaryButton, { onClick: () => {
197
+ if (selectedItem) {
198
+ setIsOpen(false);
199
+ const customLabel = customTextRef.current?.value;
200
+ handleSelect(selectedItem, customLabel || '');
201
+ }
202
+ }, disabled: !selectedItem }, isEdit ? 'Update' : 'Insert') }))));
116
203
  };
@@ -1,11 +1,11 @@
1
- import { Category, Dialog, } from '@manuscripts/style-guide';
1
+ import { Category, Dialog } from '@manuscripts/style-guide';
2
2
  import React, { useState } from 'react';
3
3
  import { getSupplementDisplayLabel, performDeleteSupplement, } from '../../lib/supplements';
4
4
  import { getEditorProps } from '../../plugins/editor-props';
5
5
  import ReactSubView from '../../views/ReactSubView';
6
6
  const DeleteSupplementDialog = ({ label, onDelete }) => {
7
7
  const [isOpen, setOpen] = useState(true);
8
- return (React.createElement(Dialog, { isOpen: isOpen, category: Category.confirmation, header: 'Delete supplement', message: 'Are you sure you want to delete \u201C{label}\u201D?', actions: {
8
+ return (React.createElement(Dialog, { isOpen: isOpen, category: Category.confirmation, header: "Delete supplement", message: "Are you sure you want to delete \u201C{label}\u201D?", actions: {
9
9
  primary: {
10
10
  action: () => {
11
11
  onDelete();