@manuscripts/body-editor 3.19.1 → 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 (34) 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/lib/utils.js +12 -1
  4. package/dist/cjs/plugins/cross-references.js +37 -14
  5. package/dist/cjs/versions.js +1 -1
  6. package/dist/cjs/views/ReactSubView.js +1 -0
  7. package/dist/cjs/views/cross_reference.js +13 -2
  8. package/dist/cjs/views/cross_reference_editable.js +1 -4
  9. package/dist/es/components/cross-ref-check-modal/CrossRefWarningModal.js +202 -52
  10. package/dist/es/components/cross-ref-check-modal/openModal.js +9 -3
  11. package/dist/es/lib/utils.js +11 -1
  12. package/dist/es/plugins/cross-references.js +37 -15
  13. package/dist/es/versions.js +1 -1
  14. package/dist/es/views/ReactSubView.js +1 -0
  15. package/dist/es/views/cross_reference.js +13 -2
  16. package/dist/es/views/cross_reference_editable.js +1 -4
  17. package/dist/types/components/authors-affiliations/AuthorsAndAffiliationsModals.d.ts +1 -1
  18. package/dist/types/components/cross-ref-check-modal/CrossRefWarningModal.d.ts +4 -2
  19. package/dist/types/components/cross-ref-check-modal/openModal.d.ts +2 -2
  20. package/dist/types/lib/utils.d.ts +4 -0
  21. package/dist/types/plugins/cross-references.d.ts +2 -0
  22. package/dist/types/versions.d.ts +1 -1
  23. package/dist/types/views/ReactSubView.d.ts +5 -2
  24. package/dist/types/views/cross_reference.d.ts +1 -0
  25. package/package.json +1 -1
  26. package/src/components/cross-ref-check-modal/CrossRefWarningModal.tsx +315 -83
  27. package/src/components/cross-ref-check-modal/openModal.ts +14 -4
  28. package/src/lib/utils.ts +24 -0
  29. package/src/plugins/cross-references.ts +41 -13
  30. package/src/versions.ts +1 -1
  31. package/src/views/ReactSubView.tsx +6 -3
  32. package/src/views/cross_reference.ts +14 -2
  33. package/src/views/cross_reference_editable.ts +1 -4
  34. 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);
@@ -15,7 +15,7 @@
15
15
  */
16
16
  import { isElementNodeType, isSectionNodeType, schema, } from '@manuscripts/transform';
17
17
  import { Fragment, } from 'prosemirror-model';
18
- import { findChildrenByType, findParentNode, findParentNodeOfTypeClosestToPos, } from 'prosemirror-utils';
18
+ import { findChildren, findChildrenByType, findParentNode, findParentNodeOfTypeClosestToPos, } from 'prosemirror-utils';
19
19
  import { fieldConfigMap } from '../components/references/ReferenceForm/config';
20
20
  import { arrowDown } from '../icons';
21
21
  import { handleEnterKey } from './navigation-utils';
@@ -197,3 +197,13 @@ export const getLastTitleNode = (state) => {
197
197
  const titleNode = findChildrenByType(state.doc, state.schema.nodes.title)[0];
198
198
  return titleNode;
199
199
  };
200
+ export const getSurroundingText = (pos, node) => {
201
+ const nodeContent = findChildren(pos.node(), (child) => child.attrs.id === node.attrs.id)[0];
202
+ const content = pos.node().content;
203
+ const MAX_LENGTH = 40;
204
+ const nodeStart = nodeContent.pos;
205
+ const nodeEnd = nodeContent.pos + node.nodeSize;
206
+ const leftHandText = content.textBetween(nodeStart > MAX_LENGTH ? nodeStart - MAX_LENGTH : 0, nodeStart);
207
+ const rightHandText = content.textBetween(nodeEnd, Math.min(nodeEnd + MAX_LENGTH, content.size));
208
+ return { leftHandText, rightHandText };
209
+ };
@@ -14,14 +14,17 @@
14
14
  * limitations under the License.
15
15
  */
16
16
  import { schema, } from '@manuscripts/transform';
17
- import { trackChangesPluginKey } from '@manuscripts/track-changes-plugin';
17
+ import { trackChangesPluginKey, TrackChangesAction, } from '@manuscripts/track-changes-plugin';
18
18
  import isEqual from 'lodash/isEqual';
19
19
  import { NodeSelection, Plugin } from 'prosemirror-state';
20
20
  import { Decoration, DecorationSet } from 'prosemirror-view';
21
21
  import { openCrossRefWarningModal } from '../components/cross-ref-check-modal/openModal';
22
22
  import { objectsKey } from './objects';
23
+ export const DELETE_WITHOUT_REF = 'delete-without-ref';
24
+ export const SELECTED_CROSS_REF_CLASS = 'selected-cross-reference';
23
25
  let modalActive = false;
24
26
  let modalElement = null;
27
+ let scrollTargetEl = null;
25
28
  export default () => {
26
29
  let view = null;
27
30
  return new Plugin({
@@ -40,7 +43,10 @@ export default () => {
40
43
  if (!view ||
41
44
  !tr.docChanged ||
42
45
  tr.getMeta(trackChangesPluginKey) ||
43
- tr.getMeta('addToHistory') === false) {
46
+ tr.getMeta(TrackChangesAction.refreshChanges) ||
47
+ tr.getMeta('addToHistory') === false ||
48
+ tr.getMeta('collab$') ||
49
+ tr.getMeta(DELETE_WITHOUT_REF)) {
44
50
  return true;
45
51
  }
46
52
  if (modalActive) {
@@ -59,6 +65,9 @@ export default () => {
59
65
  modalElement.remove();
60
66
  modalElement = null;
61
67
  }
68
+ if (scrollTargetEl) {
69
+ scrollTargetEl.classList.remove(SELECTED_CROSS_REF_CLASS);
70
+ }
62
71
  };
63
72
  const deletedIds = new Set(xrefGroups.map((g) => g.referenced.attrs.id));
64
73
  const onClose = () => {
@@ -149,12 +158,13 @@ function createXrefGroups(targets, stateDoc, resultDoc) {
149
158
  const xrefs = xrefsByRid.get(id);
150
159
  if (xrefs?.length) {
151
160
  const label = targets.get(referenced.attrs.id)?.label || '';
152
- xrefGroups.push({ referenced, label, xrefs });
161
+ const caption = targets.get(referenced.attrs.id)?.caption || '';
162
+ xrefGroups.push({ referenced, label, caption, xrefs });
153
163
  }
154
164
  }
155
165
  return xrefGroups;
156
166
  }
157
- const onConfirmCreator = (view, cleanup, deletedIds, tr) => () => {
167
+ const onConfirmCreator = (view, cleanup, deletedIds, tr) => (deleteOption) => {
158
168
  cleanup();
159
169
  if (!view) {
160
170
  return;
@@ -168,18 +178,23 @@ const onConfirmCreator = (view, cleanup, deletedIds, tr) => () => {
168
178
  }
169
179
  newTr.step(step);
170
180
  }
171
- const xrefPositions = [];
172
- newTr.doc.descendants((node, pos) => {
173
- if (node.type === schema.nodes.cross_reference) {
174
- const rids = node.attrs.rids;
175
- if (rids.some((rid) => deletedIds.has(rid))) {
176
- xrefPositions.push({ from: pos, to: pos + node.nodeSize });
181
+ if (deleteOption === 'delete-with-ref') {
182
+ const xrefPositions = [];
183
+ newTr.doc.descendants((node, pos) => {
184
+ if (node.type === schema.nodes.cross_reference) {
185
+ const rids = node.attrs.rids;
186
+ if (rids.some((rid) => deletedIds.has(rid))) {
187
+ xrefPositions.push({ from: pos, to: pos + node.nodeSize });
188
+ }
177
189
  }
190
+ });
191
+ for (let i = xrefPositions.length - 1; i >= 0; i--) {
192
+ const { from, to } = xrefPositions[i];
193
+ newTr.delete(from, to);
178
194
  }
179
- });
180
- for (let i = xrefPositions.length - 1; i >= 0; i--) {
181
- const { from, to } = xrefPositions[i];
182
- newTr.delete(from, to);
195
+ }
196
+ else {
197
+ newTr.setMeta(DELETE_WITHOUT_REF, true);
183
198
  }
184
199
  view.dispatch(newTr);
185
200
  };
@@ -189,7 +204,6 @@ const selectAndScrollToCreator = (view) => ($pos) => {
189
204
  }
190
205
  const selTr = view.state.tr;
191
206
  selTr.setSelection(NodeSelection.create(view.state.doc, $pos.pos));
192
- view.focus();
193
207
  view.dispatch(selTr);
194
208
  let scrollable = view.dom.parentElement;
195
209
  while (scrollable != null &&
@@ -212,4 +226,12 @@ const selectAndScrollToCreator = (view) => ($pos) => {
212
226
  modalElement?.classList.remove('modal-bottom');
213
227
  scrollable.scrollTo({ top: scrollTo, behavior: 'smooth' });
214
228
  }
229
+ const dom = view.nodeDOM($pos.pos);
230
+ if (dom && dom instanceof HTMLElement) {
231
+ dom.classList.add(SELECTED_CROSS_REF_CLASS);
232
+ if (scrollTargetEl && scrollTargetEl !== dom) {
233
+ scrollTargetEl?.classList.remove(SELECTED_CROSS_REF_CLASS);
234
+ }
235
+ scrollTargetEl = dom;
236
+ }
215
237
  };
@@ -1,2 +1,2 @@
1
- export const VERSION = '3.19.1';
1
+ export const VERSION = '3.19.2';
2
2
  export const MATHJAX_VERSION = '3.2.2';
@@ -20,6 +20,7 @@ import { createRoot } from 'react-dom/client';
20
20
  import { ThemeProvider } from 'styled-components';
21
21
  function createSubView(props, Component, componentProps, node, getPos, view, classNames = []) {
22
22
  const container = document.createElement('div');
23
+ container.unmount = () => root.unmount();
23
24
  const Wrapped = createView(props, Component, componentProps, node, getPos, view, classNames, container);
24
25
  const root = createRoot(container);
25
26
  root.render(React.createElement(Wrapped, null));
@@ -50,10 +50,21 @@ export class CrossReferenceView extends BaseNodeView {
50
50
  const target = attrs.rids.length ? targets.get(attrs.rids[0]) : undefined;
51
51
  const isSupplement = target?.type === schema.nodes.supplement.name;
52
52
  const derivedLabel = isSupplement
53
- ? target?.caption || target?.label || ''
54
- : target?.label || '';
53
+ ? target?.caption || target?.label || '[cross-ref]'
54
+ : target?.label || '[cross-ref]';
55
55
  this.dom.textContent = attrs.label || derivedLabel;
56
56
  this.dom.addEventListener('click', this.handleClick);
57
+ if (this.isOrphaned()) {
58
+ this.dom.classList.add('orphaned-cross-reference');
59
+ }
60
+ else {
61
+ this.dom.classList.remove('orphaned-cross-reference');
62
+ }
63
+ }
64
+ isOrphaned() {
65
+ const targets = objectsKey.getState(this.view.state);
66
+ const rid = this.node.attrs.rids[0];
67
+ return !targets?.get(rid);
57
68
  }
58
69
  }
59
70
  export default createNodeView(CrossReferenceView);
@@ -111,9 +111,7 @@ export class CrossReferenceEditableView extends CrossReferenceView {
111
111
  this.showContextMenu = () => {
112
112
  this.props.popper.destroy();
113
113
  const can = this.props.getCapabilities();
114
- const targets = objectsKey.getState(this.view.state);
115
- const rid = this.node.attrs.rids[0];
116
- const isOrphaned = !targets?.get(rid);
114
+ const isOrphaned = this.isOrphaned();
117
115
  const actions = [
118
116
  {
119
117
  label: 'Comment',
@@ -135,7 +133,6 @@ export class CrossReferenceEditableView extends CrossReferenceView {
135
133
  label: 'Edit',
136
134
  icon: 'Edit',
137
135
  action: () => this.handleEdit(),
138
- disabled: isOrphaned,
139
136
  });
140
137
  }
141
138
  this.contextMenu = ReactSubView(this.props, ContextMenu, { actions }, this.node, this.getPos, this.view, ['context-menu']);
@@ -26,4 +26,4 @@ export interface AuthorsAndAffiliationsModalsProps {
26
26
  addNewAffiliation?: boolean;
27
27
  }
28
28
  export declare const AuthorsAndAffiliationsModals: React.FC<AuthorsAndAffiliationsModalsProps>;
29
- export declare const openAuthorsAndAffiliationsModals: (pos: number, view: ManuscriptEditorView | EditorView | undefined, initialModal: "authors" | "affiliations") => HTMLDivElement | undefined;
29
+ export declare const openAuthorsAndAffiliationsModals: (pos: number, view: ManuscriptEditorView | EditorView | undefined, initialModal: "authors" | "affiliations") => import("../../views/ReactSubView").SubViewContainer | undefined;
@@ -14,16 +14,18 @@
14
14
  * limitations under the License.
15
15
  */
16
16
  import { ManuscriptNode } from '@manuscripts/transform';
17
- import React from 'react';
18
17
  import { ResolvedPos } from 'prosemirror-model';
18
+ import React from 'react';
19
19
  export type XrefGroup = {
20
20
  referenced: ManuscriptNode;
21
21
  label: string;
22
+ caption: string;
22
23
  xrefs: [ManuscriptNode, ResolvedPos][];
23
24
  };
25
+ export type DeleteOption = 'delete-without-ref' | 'delete-with-ref';
24
26
  export declare const CrossRefWarningModal: React.FC<{
25
27
  onClose: () => void;
26
28
  xrefs: XrefGroup[];
27
- onConfirm: () => void;
29
+ onConfirm: (deleteOption: DeleteOption) => void;
28
30
  selectAndScrollTo: ($pos: ResolvedPos) => void;
29
31
  }>;
@@ -15,5 +15,5 @@
15
15
  */
16
16
  import { ManuscriptEditorView } from '@manuscripts/transform';
17
17
  import { ResolvedPos } from 'prosemirror-model';
18
- import { XrefGroup } from './CrossRefWarningModal';
19
- export declare const openCrossRefWarningModal: (view: ManuscriptEditorView, xrefGroups: XrefGroup[], onConfirm: () => void, onClose: () => void, selectAndScrollTo: ($pos: ResolvedPos) => void) => HTMLDivElement;
18
+ import { DeleteOption, XrefGroup } from './CrossRefWarningModal';
19
+ export declare const openCrossRefWarningModal: (view: ManuscriptEditorView, xrefGroups: XrefGroup[], onConfirm: (deleteOption: DeleteOption) => void, onClose: () => void, selectAndScrollTo: ($pos: ResolvedPos) => void) => HTMLDivElement;
@@ -43,3 +43,7 @@ export declare const getLastTitleNode: (state: ManuscriptEditorState) => {
43
43
  node: ProseMirrorNode;
44
44
  pos: number;
45
45
  };
46
+ export declare const getSurroundingText: (pos: ResolvedPos, node: ManuscriptNode) => {
47
+ leftHandText: string;
48
+ rightHandText: string;
49
+ };
@@ -15,5 +15,7 @@
15
15
  */
16
16
  import { Plugin } from 'prosemirror-state';
17
17
  import { DecorationSet } from 'prosemirror-view';
18
+ export declare const DELETE_WITHOUT_REF = "delete-without-ref";
19
+ export declare const SELECTED_CROSS_REF_CLASS = "selected-cross-reference";
18
20
  declare const _default: () => Plugin<DecorationSet>;
19
21
  export default _default;
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "3.19.1";
1
+ export declare const VERSION = "3.19.2";
2
2
  export declare const MATHJAX_VERSION = "3.2.2";
@@ -17,6 +17,9 @@ import { ManuscriptEditorView, ManuscriptNode } from '@manuscripts/transform';
17
17
  import React from 'react';
18
18
  import { EditorProps } from '../configs/ManuscriptsEditor';
19
19
  import { Trackable } from '../types';
20
+ export type SubViewContainer = HTMLDivElement & {
21
+ unmount: () => void;
22
+ };
20
23
  export interface ReactViewComponentProps<NodeT extends ManuscriptNode> {
21
24
  nodeAttrs: NodeT['attrs'];
22
25
  setNodeAttrs: (nextAttrs: Partial<NodeT['attrs']>) => void;
@@ -25,8 +28,8 @@ export interface ReactViewComponentProps<NodeT extends ManuscriptNode> {
25
28
  getPos: () => number;
26
29
  node: ManuscriptNode | Trackable<ManuscriptNode>;
27
30
  };
28
- container: HTMLDivElement;
31
+ container: SubViewContainer;
29
32
  }
30
- declare function createSubView<T extends Trackable<ManuscriptNode>>(props: EditorProps, Component: React.FC<any>, componentProps: object, node: T, getPos: () => number, view: ManuscriptEditorView, classNames?: string[]): HTMLDivElement;
33
+ declare function createSubView<T extends Trackable<ManuscriptNode>>(props: EditorProps, Component: React.FC<any>, componentProps: object, node: T, getPos: () => number, view: ManuscriptEditorView, classNames?: string[]): SubViewContainer;
31
34
  export declare function createSubViewAsync<T extends Trackable<ManuscriptNode>>(props: EditorProps, Component: React.FC<any>, componentProps: object, node: T, getPos: () => number, view: ManuscriptEditorView, classNames?: string[]): Promise<HTMLDivElement>;
32
35
  export default createSubView;
@@ -22,6 +22,7 @@ export declare class CrossReferenceView extends BaseNodeView<Trackable<CrossRefe
22
22
  initialise: () => void;
23
23
  ignoreMutation: () => boolean;
24
24
  createDOM: () => void;
25
+ protected isOrphaned(): boolean;
25
26
  }
26
27
  declare const _default: (props: import("../configs/ManuscriptsEditor").EditorProps, dispatch?: import("..").Dispatch) => import("../types").NodeViewCreator<CrossReferenceView>;
27
28
  export default _default;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@manuscripts/body-editor",
3
3
  "description": "Prosemirror components for editing and viewing manuscripts",
4
- "version": "3.19.1",
4
+ "version": "3.19.2",
5
5
  "repository": "github:Atypon-OpenSource/manuscripts-body-editor",
6
6
  "license": "Apache-2.0",
7
7
  "main": "dist/cjs",