@manuscripts/body-editor 3.15.1 → 3.16.1

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 (60) 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/views/CrossReferenceItems.js +139 -52
  5. package/dist/cjs/components/views/DeleteSupplementDialog.js +1 -1
  6. package/dist/cjs/lib/__tests__/plugins.test.js +1 -0
  7. package/dist/cjs/lib/supplements.js +9 -1
  8. package/dist/cjs/menus.js +35 -0
  9. package/dist/cjs/plugins/objects.js +1 -1
  10. package/dist/cjs/testing/default-editor-data.js +1 -0
  11. package/dist/cjs/toolbar.js +6 -0
  12. package/dist/cjs/versions.js +1 -1
  13. package/dist/cjs/views/cross_reference.js +12 -2
  14. package/dist/cjs/views/cross_reference_editable.js +95 -4
  15. package/dist/cjs/views/figure_editable.js +3 -0
  16. package/dist/es/commands.js +43 -9
  17. package/dist/es/components/form/FormFooter.js +2 -2
  18. package/dist/es/components/keyboard-shortcuts-modal/KeyboardShortcutsModal.js +4 -2
  19. package/dist/es/components/views/CrossReferenceItems.js +141 -54
  20. package/dist/es/components/views/DeleteSupplementDialog.js +2 -2
  21. package/dist/es/lib/__tests__/plugins.test.js +1 -0
  22. package/dist/es/lib/supplements.js +8 -1
  23. package/dist/es/menus.js +36 -1
  24. package/dist/es/plugins/objects.js +1 -1
  25. package/dist/es/testing/default-editor-data.js +1 -0
  26. package/dist/es/toolbar.js +8 -2
  27. package/dist/es/versions.js +1 -1
  28. package/dist/es/views/cross_reference.js +12 -2
  29. package/dist/es/views/cross_reference_editable.js +96 -5
  30. package/dist/es/views/figure_editable.js +3 -0
  31. package/dist/types/commands.d.ts +1 -0
  32. package/dist/types/components/form/FormFooter.d.ts +2 -1
  33. package/dist/types/components/views/CrossReferenceItems.d.ts +3 -0
  34. package/dist/types/lib/supplements.d.ts +1 -0
  35. package/dist/types/versions.d.ts +1 -1
  36. package/dist/types/views/cross_reference_editable.d.ts +6 -0
  37. package/package.json +3 -3
  38. package/src/commands.ts +52 -10
  39. package/src/components/ChangeHandlingForm.tsx +4 -2
  40. package/src/components/form/FormFooter.tsx +3 -1
  41. package/src/components/keyboard-shortcuts-modal/KeyboardShortcutsModal.tsx +6 -7
  42. package/src/components/toolbar/InsertEmbedDialog.tsx +6 -2
  43. package/src/components/toolbar/type-selector/styles.ts +2 -1
  44. package/src/components/views/CrossReferenceItems.tsx +227 -100
  45. package/src/components/views/DeleteSupplementDialog.tsx +3 -6
  46. package/src/lib/__tests__/plugins.test.ts +1 -0
  47. package/src/lib/footnotes.ts +3 -1
  48. package/src/lib/supplements.ts +13 -1
  49. package/src/lib/utils.ts +15 -4
  50. package/src/menus.tsx +39 -0
  51. package/src/plugins/add-subtitle.ts +18 -16
  52. package/src/plugins/objects.ts +2 -6
  53. package/src/testing/default-editor-data.ts +1 -0
  54. package/src/toolbar.tsx +9 -0
  55. package/src/versions.ts +1 -1
  56. package/src/views/cross_reference.ts +17 -3
  57. package/src/views/cross_reference_editable.ts +123 -11
  58. package/src/views/figure_editable.ts +4 -0
  59. package/src/views/supplement.ts +0 -1
  60. package/src/views/supplements.ts +2 -2
@@ -19,10 +19,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
19
19
  };
20
20
  Object.defineProperty(exports, "__esModule", { value: true });
21
21
  exports.CrossReferenceEditableView = void 0;
22
+ const style_guide_1 = require("@manuscripts/style-guide");
22
23
  const track_changes_plugin_1 = require("@manuscripts/track-changes-plugin");
23
24
  const transform_1 = require("@manuscripts/transform");
24
25
  const prosemirror_state_1 = require("prosemirror-state");
25
26
  const CrossReferenceItems_1 = require("../components/views/CrossReferenceItems");
27
+ const comments_1 = require("../lib/comments");
28
+ const doc_1 = require("../lib/doc");
29
+ const supplements_1 = require("../lib/supplements");
26
30
  const objects_1 = require("../plugins/objects");
27
31
  const creators_1 = require("./creators");
28
32
  const cross_reference_1 = require("./cross_reference");
@@ -37,14 +41,20 @@ class CrossReferenceEditableView extends cross_reference_1.CrossReferenceView {
37
41
  if (!can?.editArticle || rids.length) {
38
42
  return;
39
43
  }
44
+ this.showPicker();
45
+ };
46
+ this.showPicker = () => {
47
+ const rids = this.node.attrs.rids;
40
48
  const componentProps = {
41
49
  handleSelect: this.handleSelect,
42
50
  targets: this.getTargets(),
51
+ files: this.props.getFiles(),
43
52
  handleCancel: this.handleCancel,
44
53
  currentTargetId: rids[0],
45
54
  currentCustomLabel: this.node.attrs.label,
55
+ isEdit: rids.length > 0,
46
56
  };
47
- this.popperContainer = (0, ReactSubView_1.default)(this.props, CrossReferenceItems_1.CrossReferenceItems, componentProps, this.node, this.getPos, this.view, ['cross-reference-editor']);
57
+ this.popperContainer = (0, ReactSubView_1.default)(this.props, CrossReferenceItems_1.CrossReferenceItems, componentProps, this.node, this.getPos, this.view);
48
58
  this.popperContainer.setAttribute('tabindex', '0');
49
59
  this.props.popper.show(this.dom, this.popperContainer, 'auto');
50
60
  };
@@ -56,15 +66,39 @@ class CrossReferenceEditableView extends cross_reference_1.CrossReferenceView {
56
66
  this.handleCancel();
57
67
  };
58
68
  this.getTargets = () => {
59
- const excludedTypes = [transform_1.schema.nodes.image_element.name];
60
69
  const targets = objects_1.objectsKey.getState(this.view.state);
61
- return Array.from(targets.values()).filter((t) => !excludedTypes.includes(t.type));
70
+ const files = this.props.getFiles();
71
+ const fileMap = new Map(files.map((f) => [f.id, f.name]));
72
+ const excludedTypes = [transform_1.schema.nodes.image_element.name];
73
+ const supplement = transform_1.schema.nodes.supplement.name;
74
+ return Array.from(targets.values()).reduce((acc, t) => {
75
+ if (excludedTypes.includes(t.type)) {
76
+ return acc;
77
+ }
78
+ if (t.type === supplement && t.href) {
79
+ const found = (0, doc_1.findNodeByID)(this.view.state.doc, t.id);
80
+ const label = found
81
+ ? (0, supplements_1.getSupplementDisplayLabel)(found.node, files)
82
+ : (fileMap.get(t.href) ?? t.href);
83
+ acc.push({ ...t, label, caption: '' });
84
+ }
85
+ else {
86
+ acc.push(t);
87
+ }
88
+ return acc;
89
+ }, []);
62
90
  };
63
91
  this.handleCancel = () => {
64
92
  if (!this.node.attrs.rids.length) {
65
93
  const { state } = this.view;
66
94
  const pos = this.getPos();
67
- const tr = state.tr.delete(pos, pos + this.node.nodeSize);
95
+ if (pos === undefined) {
96
+ return;
97
+ }
98
+ const label = this.node.attrs.label;
99
+ const tr = label
100
+ ? state.tr.replaceWith(pos, pos + this.node.nodeSize, state.schema.text(label))
101
+ : state.tr.delete(pos, pos + this.node.nodeSize);
68
102
  tr.setSelection(prosemirror_state_1.TextSelection.create(tr.doc, pos));
69
103
  (0, track_changes_plugin_1.skipTracking)(tr);
70
104
  this.view.dispatch(tr);
@@ -85,6 +119,63 @@ class CrossReferenceEditableView extends cross_reference_1.CrossReferenceView {
85
119
  this.view.dispatch(tr.setSelection(selection));
86
120
  this.destroy();
87
121
  };
122
+ this.handleClick = () => {
123
+ if ((0, track_changes_plugin_1.isDeleted)(this.node)) {
124
+ return;
125
+ }
126
+ if (!this.node.attrs.rids.length) {
127
+ return;
128
+ }
129
+ this.showContextMenu();
130
+ };
131
+ this.showContextMenu = () => {
132
+ this.props.popper.destroy();
133
+ const can = this.props.getCapabilities();
134
+ const targets = objects_1.objectsKey.getState(this.view.state);
135
+ const rid = this.node.attrs.rids[0];
136
+ const isOrphaned = !targets?.get(rid);
137
+ const actions = [
138
+ {
139
+ label: 'Comment',
140
+ icon: 'AddComment',
141
+ action: () => {
142
+ this.props.popper.destroy();
143
+ (0, comments_1.handleComment)(this.node, this.view);
144
+ },
145
+ },
146
+ {
147
+ label: 'Go to content',
148
+ icon: 'Scroll',
149
+ action: () => this.navigateToTarget(),
150
+ disabled: isOrphaned,
151
+ },
152
+ ];
153
+ if (can?.editArticle) {
154
+ actions.unshift({
155
+ label: 'Edit',
156
+ icon: 'Edit',
157
+ action: () => this.handleEdit(),
158
+ disabled: isOrphaned,
159
+ });
160
+ }
161
+ this.contextMenu = (0, ReactSubView_1.default)(this.props, style_guide_1.ContextMenu, { actions }, this.node, this.getPos, this.view, ['context-menu']);
162
+ this.props.popper.show(this.dom, this.contextMenu, 'right-start', false);
163
+ };
164
+ this.navigateToTarget = () => {
165
+ this.props.popper.destroy();
166
+ const rids = this.node.attrs.rids;
167
+ if (!rids.length) {
168
+ return;
169
+ }
170
+ this.props.navigate({
171
+ pathname: this.props.location.pathname,
172
+ hash: '#' + rids[0],
173
+ });
174
+ };
175
+ this.handleEdit = () => {
176
+ this.props.popper.destroy();
177
+ this.showPicker();
178
+ };
88
179
  }
89
180
  }
90
181
  exports.CrossReferenceEditableView = CrossReferenceEditableView;
@@ -198,6 +198,9 @@ class FigureEditableView extends figure_1.FigureView {
198
198
  this.addTools();
199
199
  }
200
200
  addTools() {
201
+ if (this.getPos() === undefined) {
202
+ return;
203
+ }
201
204
  this.manageReactTools();
202
205
  const existingDragHandlers = this.container.querySelectorAll('.drag-handler');
203
206
  existingDragHandlers.forEach((handler) => handler.remove());
@@ -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 { isDeleted, skipTracking, skipSelect } from '@manuscripts/track-changes-plugin';
16
+ import { isDeleted, skipTracking, skipSelect, } from '@manuscripts/track-changes-plugin';
17
17
  import { generateNodeID, isElementNodeType, isListNode, isSectionNodeType, isTableElementNode, schema, } from '@manuscripts/transform';
18
18
  import { Fragment, NodeRange, Slice, } from 'prosemirror-model';
19
19
  import { wrapInList } from 'prosemirror-schema-list';
@@ -298,7 +298,7 @@ export const insertSupplement = (file, view, setSelection = true) => {
298
298
  export const insertSupplementWeblink = (url, title, view) => {
299
299
  const supplement = schema.nodes.supplement.createAndFill({
300
300
  id: generateNodeID(schema.nodes.supplement),
301
- href: url
301
+ href: url,
302
302
  }, createAndFillCaption());
303
303
  const tr = view.state.tr;
304
304
  const { pos } = upsertSupplementsSection(tr, supplement);
@@ -466,15 +466,52 @@ export const insertInlineCitation = (state, dispatch) => {
466
466
  }
467
467
  return true;
468
468
  };
469
+ export const canInsertCrossReference = (state) => {
470
+ if (!canInsert(schema.nodes.cross_reference)(state)) {
471
+ return false;
472
+ }
473
+ const { from, to, empty, $from, $to } = state.selection;
474
+ if (empty) {
475
+ return true;
476
+ }
477
+ if (!$from.sameParent($to)) {
478
+ return false;
479
+ }
480
+ let overlaps = false;
481
+ state.doc.nodesBetween(from, to, (node) => {
482
+ if (node.isAtom && !node.isText) {
483
+ overlaps = true;
484
+ return false;
485
+ }
486
+ return !overlaps;
487
+ });
488
+ return !overlaps;
489
+ };
469
490
  export const insertCrossReference = (state, dispatch) => {
491
+ if (!canInsertCrossReference(state)) {
492
+ return false;
493
+ }
494
+ const text = selectedText();
470
495
  const node = state.schema.nodes.cross_reference.create({
496
+ id: generateNodeID(schema.nodes.cross_reference),
471
497
  rids: [],
498
+ label: text,
472
499
  });
473
- const pos = state.selection.to;
474
- const tr = state.tr.insert(pos, node);
500
+ const { tr } = state;
501
+ let pos;
502
+ const isWrap = !state.selection.empty;
503
+ if (isWrap) {
504
+ pos = state.selection.from;
505
+ tr.replaceSelectionWith(node);
506
+ }
507
+ else {
508
+ pos = state.selection.to;
509
+ tr.insert(pos, node);
510
+ }
475
511
  if (dispatch) {
476
512
  const selection = NodeSelection.create(tr.doc, pos);
477
- dispatch(tr.setSelection(selection).scrollIntoView());
513
+ tr.setSelection(selection).scrollIntoView();
514
+ dispatch(isWrap ? skipTracking(tr) : tr);
478
515
  }
479
516
  return true;
480
517
  };
@@ -963,10 +1000,7 @@ export const insertTransGraphicalAbstract = (category, insertAfterPos) => (state
963
1000
  const node = schema.nodes.trans_graphical_abstract.createAndFill({
964
1001
  lang,
965
1002
  category: category.id,
966
- }, [
967
- schema.nodes.section_title.create(),
968
- createAndFillFigureElement(state),
969
- ]);
1003
+ }, [schema.nodes.section_title.create(), createAndFillFigureElement(state)]);
970
1004
  const tr = state.tr.insert(pos, node);
971
1005
  if (node.lastChild) {
972
1006
  expandAccessibilitySection(tr, node.lastChild);
@@ -16,9 +16,9 @@ const Footer = styled.div `
16
16
  const StyledIconTextButton = styled(IconTextButton) `
17
17
  color: ${(props) => props.theme.colors.brand.default};
18
18
  `;
19
- const FormFooter = ({ onCancel, primaryAction, }) => {
19
+ const FormFooter = ({ onCancel, primaryAction, cancelLabel = 'Close', }) => {
20
20
  return (React.createElement(Footer, null,
21
- React.createElement(StyledIconTextButton, { color: "secondary", onClick: onCancel }, "Close"),
21
+ React.createElement(StyledIconTextButton, { color: "secondary", onClick: onCancel }, cancelLabel),
22
22
  primaryAction));
23
23
  };
24
24
  export default FormFooter;
@@ -41,7 +41,8 @@ const ShortcutTabs = styled(InspectorTabs) `
41
41
  min-height: 0;
42
42
  `;
43
43
  const ModalTabsWrapper = styled('div') `
44
- margin: 0 ${(props) => props.theme.grid.unit * 8}px ${(props) => props.theme.grid.unit * 3}px;
44
+ margin: 0 ${(props) => props.theme.grid.unit * 8}px
45
+ ${(props) => props.theme.grid.unit * 3}px;
45
46
  `;
46
47
  const ShortcutTabPanel = styled(InspectorTabPanel).attrs({
47
48
  tabIndex: -1,
@@ -92,7 +93,8 @@ const Section = styled.section `
92
93
  }
93
94
  `;
94
95
  const SectionTitle = styled.h3 `
95
- margin: 0 ${(props) => props.theme.grid.unit * 8}px ${(props) => props.theme.grid.unit * 2}px;
96
+ margin: 0 ${(props) => props.theme.grid.unit * 8}px
97
+ ${(props) => props.theme.grid.unit * 2}px;
96
98
  font-size: ${(props) => props.theme.font.size.large};
97
99
  font-weight: ${(props) => props.theme.font.weight.normal};
98
100
  line-height: ${(props) => props.theme.font.lineHeight.large};
@@ -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();
@@ -12,6 +12,7 @@ import { defaultTheme } from '@manuscripts/style-guide';
12
12
  const userProfile = {
13
13
  _id: 'MPUserProfile:1',
14
14
  userID: 'MPUser:1',
15
+ connectID: 'connectID1'
15
16
  };
16
17
  const history = createMemoryHistory();
17
18
  const buildProps = (doc) => ({
@@ -14,9 +14,16 @@
14
14
  * limitations under the License.
15
15
  */
16
16
  import { schema, } from '@manuscripts/transform';
17
- import { findChildrenByType, findParentNodeClosestToPos } from 'prosemirror-utils';
17
+ import { findChildrenByType, findParentNodeClosestToPos, } from 'prosemirror-utils';
18
18
  import { allowedHref } from './url';
19
+ export const getSupplementCaptionTitle = (node) => {
20
+ return node.firstChild?.textContent.trim() ?? '';
21
+ };
19
22
  export const getSupplementDisplayLabel = (node, files) => {
23
+ const captionTitle = getSupplementCaptionTitle(node);
24
+ if (captionTitle) {
25
+ return captionTitle;
26
+ }
20
27
  const href = node.attrs.href;
21
28
  if (allowedHref(href)) {
22
29
  return href;
package/dist/es/menus.js CHANGED
@@ -16,7 +16,7 @@
16
16
  import { getGroupCategories, schema, } from '@manuscripts/transform';
17
17
  import { toggleMark } from 'prosemirror-commands';
18
18
  import { redo, undo } from 'prosemirror-history';
19
- import { activateSearchReplace, addInlineComment, blockActive, canInsert, copySelection, insertAbstractSection, insertAffiliation, insertAward, insertBackmatterSection, insertBlock, insertBoxElement, insertContributors, insertCrossReference, insertEmbed, insertGraphicalAbstract, insertHeadshotGrid, insertHeroImage, insertInlineCitation, insertInlineEquation, insertInlineFootnote, insertKeywords, insertLink, insertList, insertSection, markActive, paste, } from './commands';
19
+ import { activateSearchReplace, addInlineComment, blockActive, canInsert, copySelection, insertAbstractSection, insertAffiliation, insertAward, insertBackmatterSection, insertBlock, insertBoxElement, insertContributors, insertCrossReference, canInsertCrossReference, insertEmbed, insertGraphicalAbstract, insertHeadshotGrid, insertHeroImage, insertInlineCitation, insertInlineEquation, insertInlineFootnote, insertKeywords, insertLink, insertList, insertSection, markActive, paste, } from './commands';
20
20
  import { openInsertTableDialog } from './components/toolbar/InsertTableDialog';
21
21
  import { ListMenuItem } from './components/toolbar/ListMenuItem';
22
22
  import { openInsertSpecialCharacterDialog } from './components/views/InsertSpecialCharacter';
@@ -330,6 +330,41 @@ export const getEditorMenus = (editor) => {
330
330
  {
331
331
  role: 'separator',
332
332
  },
333
+ {
334
+ id: 'insert-citation',
335
+ label: 'Citation',
336
+ shortcut: {
337
+ mac: 'Option+CommandOrControl+C',
338
+ pc: 'CommandOrControl+Option+C',
339
+ },
340
+ isEnabled: isEditAllowed(state) &&
341
+ isCommandValid(canInsert(schema.nodes.citation)),
342
+ run: doCommand(insertInlineCitation),
343
+ isHidden: !templateAllows(state, schema.nodes.citation),
344
+ },
345
+ {
346
+ id: 'insert-cross-reference',
347
+ label: 'Cross-reference',
348
+ shortcut: {
349
+ mac: 'Option+CommandOrControl+R',
350
+ pc: 'CommandOrControl+Option+R',
351
+ },
352
+ isEnabled: isEditAllowed(state) && isCommandValid(canInsertCrossReference),
353
+ run: doCommand(insertCrossReference),
354
+ isHidden: !templateAllows(state, schema.nodes.cross_reference),
355
+ },
356
+ {
357
+ id: 'insert-footnote',
358
+ label: 'Footnote',
359
+ shortcut: {
360
+ mac: 'Option+CommandOrControl+F',
361
+ pc: 'CommandOrControl+Option+F',
362
+ },
363
+ isEnabled: isEditAllowed(state) &&
364
+ isCommandValid(canInsert(schema.nodes.inline_footnote)),
365
+ run: doCommand(insertInlineFootnote),
366
+ isHidden: !templateAllows(state, schema.nodes.inline_footnote),
367
+ },
333
368
  {
334
369
  id: 'insert-special-character',
335
370
  label: 'Special Characters',
@@ -39,7 +39,7 @@ export default () => {
39
39
  const { id } = node.attrs;
40
40
  if (id) {
41
41
  const target = targets.get(id);
42
- if (target) {
42
+ if (target && target.label) {
43
43
  const caption = findChildren(node, (node) => node.type === schema.nodes.caption ||
44
44
  node.type === schema.nodes.caption_title, false)[0];
45
45
  if (caption) {
@@ -35,6 +35,7 @@ export const defaultEditorProps = {
35
35
  getCurrentUser: () => ({
36
36
  _id: 'test-user-profile-1-id',
37
37
  userID: '',
38
+ connectID: '',
38
39
  }),
39
40
  history: createBrowserHistory(),
40
41
  theme: defaultTheme,