@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.
- package/dist/cjs/components/cross-ref-check-modal/CrossRefWarningModal.js +202 -52
- package/dist/cjs/components/cross-ref-check-modal/openModal.js +8 -2
- package/dist/cjs/lib/utils.js +12 -1
- package/dist/cjs/plugins/cross-references.js +37 -14
- package/dist/cjs/versions.js +1 -1
- package/dist/cjs/views/ReactSubView.js +1 -0
- package/dist/cjs/views/cross_reference.js +13 -2
- package/dist/cjs/views/cross_reference_editable.js +1 -4
- package/dist/es/components/cross-ref-check-modal/CrossRefWarningModal.js +202 -52
- package/dist/es/components/cross-ref-check-modal/openModal.js +9 -3
- package/dist/es/lib/utils.js +11 -1
- package/dist/es/plugins/cross-references.js +37 -15
- package/dist/es/versions.js +1 -1
- package/dist/es/views/ReactSubView.js +1 -0
- package/dist/es/views/cross_reference.js +13 -2
- package/dist/es/views/cross_reference_editable.js +1 -4
- package/dist/types/components/authors-affiliations/AuthorsAndAffiliationsModals.d.ts +1 -1
- package/dist/types/components/cross-ref-check-modal/CrossRefWarningModal.d.ts +4 -2
- package/dist/types/components/cross-ref-check-modal/openModal.d.ts +2 -2
- package/dist/types/lib/utils.d.ts +4 -0
- package/dist/types/plugins/cross-references.d.ts +2 -0
- package/dist/types/versions.d.ts +1 -1
- package/dist/types/views/ReactSubView.d.ts +5 -2
- package/dist/types/views/cross_reference.d.ts +1 -0
- package/package.json +1 -1
- package/src/components/cross-ref-check-modal/CrossRefWarningModal.tsx +315 -83
- package/src/components/cross-ref-check-modal/openModal.ts +14 -4
- package/src/lib/utils.ts +24 -0
- package/src/plugins/cross-references.ts +41 -13
- package/src/versions.ts +1 -1
- package/src/views/ReactSubView.tsx +6 -3
- package/src/views/cross_reference.ts +14 -2
- package/src/views/cross_reference_editable.ts +1 -4
- package/styles/Editor.css +12 -0
|
@@ -13,52 +13,111 @@
|
|
|
13
13
|
* See the License for the specific language governing permissions and
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
|
+
import { ArrowUpIcon, AttentionRedIcon, ButtonGroup, IconButton, ModalContainer, PrimaryButton, RadioButton, StyledModal, TertiaryButton, WebLinkIcon, withFocusTrap, } from '@manuscripts/style-guide';
|
|
17
|
+
import { schema } from '@manuscripts/transform';
|
|
16
18
|
import React, { useState } from 'react';
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
+
import { getSurroundingText } from '../../lib/utils';
|
|
20
|
+
import { nodeTypeIcon } from '../../node-type-icons';
|
|
19
21
|
import styled from 'styled-components';
|
|
20
22
|
export const CrossRefWarningModal = ({ onClose, xrefs, onConfirm, selectAndScrollTo }) => {
|
|
21
23
|
const [isOpen, setIsOpen] = useState(true);
|
|
24
|
+
const [deleteOption, setDeleteOption] = useState('delete-without-ref');
|
|
22
25
|
const handleClose = () => {
|
|
23
26
|
setIsOpen(false);
|
|
24
27
|
onClose();
|
|
25
28
|
};
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
const [showRef, setShowRef] = useState(false);
|
|
30
|
+
const [isScrolling, setIsScrolling] = useState(false);
|
|
31
|
+
const handleSelectAndScrollTo = ($pos) => {
|
|
32
|
+
setIsScrolling(true);
|
|
33
|
+
selectAndScrollTo($pos);
|
|
34
|
+
};
|
|
35
|
+
const toggleReferenceList = () => setShowRef(!showRef);
|
|
36
|
+
const onChangeDeleteOption = (e) => {
|
|
37
|
+
const { value } = e.target;
|
|
38
|
+
setDeleteOption(value);
|
|
39
|
+
};
|
|
40
|
+
const references = xrefs.flatMap((xrefGroup) => xrefGroup.xrefs.map((xref) => [...xref]));
|
|
41
|
+
const elementLabel = xrefs.length === 1 ? xrefs[0].label : `${xrefs.length} elements`;
|
|
42
|
+
const locationWord = references.length === 1 ? 'location' : 'locations';
|
|
43
|
+
const pronoun = xrefs.length === 1 ? 'it' : 'them';
|
|
44
|
+
return (React.createElement(Modal, { isOpen: isOpen, "$isScrolling": isScrolling, onRequestClose: () => handleClose(), shouldCloseOnOverlayClick: false, hideOverlay: true },
|
|
45
|
+
React.createElement(Container, { "$isScrolling": isScrolling, "data-cy": "cross-reference-warning-modal" },
|
|
30
46
|
React.createElement(Body, null,
|
|
31
47
|
React.createElement(Title, null,
|
|
32
|
-
React.createElement(
|
|
33
|
-
"
|
|
34
|
-
React.createElement("p", null,
|
|
35
|
-
|
|
36
|
-
|
|
48
|
+
React.createElement(AttentionRedIcon, { width: 24, height: 24 }),
|
|
49
|
+
"Confirm Deletion"),
|
|
50
|
+
React.createElement("p", null,
|
|
51
|
+
React.createElement("b", null, elementLabel),
|
|
52
|
+
" ",
|
|
53
|
+
xrefs.length === 1 ? 'is' : 'are',
|
|
54
|
+
" actively referenced in",
|
|
55
|
+
' ',
|
|
56
|
+
React.createElement("b", null,
|
|
57
|
+
references.length,
|
|
58
|
+
" ",
|
|
59
|
+
locationWord),
|
|
60
|
+
' ',
|
|
61
|
+
"in your document. Deleting ",
|
|
62
|
+
pronoun,
|
|
63
|
+
" will break the following cross-references:"),
|
|
64
|
+
React.createElement("div", null,
|
|
65
|
+
React.createElement(ToggleHeader, null,
|
|
66
|
+
React.createElement(SecondaryBoldHeading, null, showRef
|
|
67
|
+
? 'Hide locations'
|
|
68
|
+
: `Show ${references.length} location`),
|
|
69
|
+
React.createElement(ToggleButton, { onClick: toggleReferenceList }, showRef ? React.createElement(ArrowUpIcon, null) : React.createElement(ArrowDownIcon, null))),
|
|
70
|
+
showRef && (React.createElement(ListWrapper, null, xrefs.length === 1 ? (React.createElement(XrefGroupDisplay, { label: xrefs[0].label, xrefs: xrefs[0].xrefs, selectAndScrollTo: handleSelectAndScrollTo })) : (React.createElement(XrefGroupsDisplay, { xrefsGroup: xrefs, selectAndScrollTo: handleSelectAndScrollTo }))))),
|
|
71
|
+
React.createElement(SelectorContainer, null,
|
|
72
|
+
React.createElement(RadioButton, { name: 'delete-option', id: 'delete-without-ref-option', value: 'delete-without-ref', label: 'Delete but keep referenced text', checked: deleteOption === 'delete-without-ref', onChange: onChangeDeleteOption }),
|
|
73
|
+
React.createElement(RadioButton, { name: 'delete-option', id: 'delete-with-ref-option', value: 'delete-with-ref', label: 'Delete & remove references', checked: deleteOption === 'delete-with-ref', onChange: onChangeDeleteOption })),
|
|
74
|
+
React.createElement(ButtonGroup, null,
|
|
37
75
|
React.createElement(TertiaryButton, { type: "button", onClick: () => handleClose() }, "Cancel"),
|
|
38
76
|
React.createElement(PrimaryButton, { "$danger": true, type: "button", onClick: () => {
|
|
39
|
-
onConfirm();
|
|
77
|
+
onConfirm(deleteOption);
|
|
40
78
|
setIsOpen(false);
|
|
41
|
-
} }, "Delete
|
|
79
|
+
} }, "Delete"))))));
|
|
42
80
|
};
|
|
43
|
-
const XrefGroupDisplay = ({
|
|
44
|
-
return (React.createElement(
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
81
|
+
const XrefGroupDisplay = ({ label, xrefs, selectAndScrollTo }) => {
|
|
82
|
+
return (React.createElement(ReferencesList, null, xrefs.map(([xrefNode, pos], i) => {
|
|
83
|
+
const { leftHandText, rightHandText } = getSurroundingText(pos, xrefNode);
|
|
84
|
+
const derivedLabel = xrefNode.attrs.label || label || '[cross-ref]';
|
|
85
|
+
return (React.createElement(ListItem, { key: i },
|
|
86
|
+
React.createElement(XRefTextContainer, null,
|
|
87
|
+
React.createElement(XRefAdjacentText, { "$direction": 'rtl' }, leftHandText),
|
|
88
|
+
React.createElement(XRefLabel, null, derivedLabel),
|
|
89
|
+
React.createElement(XRefAdjacentText, { "$direction": 'ltr' }, rightHandText)),
|
|
90
|
+
React.createElement(ScrollButton, { "$mini": true, onClick: () => selectAndScrollTo(pos) }, "Show")));
|
|
91
|
+
})));
|
|
92
|
+
};
|
|
93
|
+
const XrefGroupsDisplay = ({ xrefsGroup, selectAndScrollTo }) => {
|
|
94
|
+
return (React.createElement(ReferencesList, null, xrefsGroup.map(({ referenced, label, caption, xrefs }, i) => {
|
|
95
|
+
const icon = getReferencedIcon(referenced.type);
|
|
96
|
+
return (React.createElement("div", { key: i },
|
|
97
|
+
React.createElement(DarkSecondaryBoldHeading, null,
|
|
98
|
+
icon ? React.createElement(ItemIcon, null, icon) : null,
|
|
99
|
+
`${label}:`,
|
|
100
|
+
React.createElement(CaptionTitle, { "$direction": 'ltr' }, caption)),
|
|
101
|
+
React.createElement(XrefGroupDisplay, { label: label, xrefs: xrefs, selectAndScrollTo: selectAndScrollTo })));
|
|
102
|
+
})));
|
|
103
|
+
};
|
|
104
|
+
const getReferencedIcon = (type) => {
|
|
105
|
+
if (type === schema.nodes.supplement) {
|
|
106
|
+
return React.createElement(WebLinkIcon, { className: "file-icon" });
|
|
107
|
+
}
|
|
108
|
+
return nodeTypeIcon(schema.nodes[type.name]);
|
|
50
109
|
};
|
|
51
110
|
const Container = styled(ModalContainer) `
|
|
52
111
|
position: absolute;
|
|
53
|
-
top: 1rem;
|
|
54
|
-
left: 50
|
|
55
|
-
|
|
56
|
-
max-height: calc(50vh - 2rem);
|
|
112
|
+
top: ${({ $isScrolling }) => ($isScrolling ? '1rem' : '30%')};
|
|
113
|
+
left: ${({ $isScrolling }) => $isScrolling ? 'calc(100% - 556px - 0rem)' : '50%'};
|
|
114
|
+
max-height: calc(60vh - 2rem);
|
|
57
115
|
min-height: 280px;
|
|
58
|
-
transform: translate(-50%, 0);
|
|
59
|
-
max-width:
|
|
116
|
+
transform: ${({ $isScrolling }) => $isScrolling ? 'translate(0, 0)' : 'translate(-50%, 0)'};
|
|
117
|
+
max-width: 556px;
|
|
60
118
|
transition:
|
|
61
119
|
top 0.2s,
|
|
120
|
+
left 0.2s,
|
|
62
121
|
transform 0.2s;
|
|
63
122
|
`;
|
|
64
123
|
const Modal = styled(StyledModal) `
|
|
@@ -86,6 +145,8 @@ const Modal = styled(StyledModal) `
|
|
|
86
145
|
right: 0;
|
|
87
146
|
bottom: 0;
|
|
88
147
|
background: rgba(0, 0, 0, 0.2);
|
|
148
|
+
opacity: ${({ $isScrolling }) => ($isScrolling ? 0.15 : 1)};
|
|
149
|
+
transform: opacity 0.2s;
|
|
89
150
|
}
|
|
90
151
|
h3 {
|
|
91
152
|
font-size: 16px;
|
|
@@ -95,11 +156,11 @@ const Modal = styled(StyledModal) `
|
|
|
95
156
|
margin: 0.5em 0;
|
|
96
157
|
}
|
|
97
158
|
`;
|
|
98
|
-
const Body = styled.div `
|
|
159
|
+
const Body = withFocusTrap(styled.div `
|
|
99
160
|
margin: 1.5rem;
|
|
100
161
|
display: flex;
|
|
101
162
|
flex-flow: column;
|
|
102
|
-
|
|
163
|
+
`);
|
|
103
164
|
const Title = styled.h2 `
|
|
104
165
|
font-size: 18px;
|
|
105
166
|
font-weight: 700;
|
|
@@ -108,35 +169,124 @@ const Title = styled.h2 `
|
|
|
108
169
|
color: #353535;
|
|
109
170
|
svg {
|
|
110
171
|
vertical-align: text-top;
|
|
172
|
+
margin-right: 8px;
|
|
111
173
|
}
|
|
112
174
|
`;
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
175
|
+
const ToggleHeader = styled.div `
|
|
176
|
+
display: flex;
|
|
177
|
+
justify-content: space-between;
|
|
178
|
+
align-items: center;
|
|
179
|
+
padding: 6px 3px 6px 8px;
|
|
180
|
+
border-radius: 4px 4px 0 0;
|
|
181
|
+
border: 1px solid #e2e2e2;
|
|
117
182
|
background: #f2f2f2;
|
|
183
|
+
`;
|
|
184
|
+
const ListWrapper = styled.div `
|
|
118
185
|
border: 1px solid #e2e2e2;
|
|
119
|
-
border-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
186
|
+
border-top: none;
|
|
187
|
+
max-height: 21vh;
|
|
188
|
+
min-height: 100px;
|
|
189
|
+
overflow-y: auto;
|
|
190
|
+
padding: 12px;
|
|
191
|
+
`;
|
|
192
|
+
const ToggleButton = styled(IconButton) `
|
|
193
|
+
height: 24px;
|
|
194
|
+
&&:not([disabled]):focus-visible {
|
|
195
|
+
outline: 2px solid ${(props) => props.theme.colors.outline.focus};
|
|
196
|
+
outline-offset: 2px;
|
|
197
|
+
}
|
|
198
|
+
svg {
|
|
199
|
+
width: 12px;
|
|
200
|
+
height: 7px;
|
|
132
201
|
}
|
|
133
202
|
`;
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
203
|
+
const SecondaryHeading = styled.div `
|
|
204
|
+
font-size: 14px;
|
|
205
|
+
font-style: normal;
|
|
206
|
+
font-weight: 700;
|
|
207
|
+
line-height: 24px;
|
|
208
|
+
color: #6e6e6e;
|
|
209
|
+
user-select: none;
|
|
137
210
|
`;
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
211
|
+
const SecondaryBoldHeading = styled(SecondaryHeading) `
|
|
212
|
+
font-weight: 700;
|
|
213
|
+
color: #6e6e6e;
|
|
214
|
+
`;
|
|
215
|
+
const DarkSecondaryBoldHeading = styled(SecondaryBoldHeading) `
|
|
216
|
+
display: flex;
|
|
217
|
+
padding: 0 4px;
|
|
218
|
+
margin-bottom: 10px;
|
|
219
|
+
border-radius: 4px;
|
|
220
|
+
color: #353535;
|
|
221
|
+
background: #f2f2f2;
|
|
222
|
+
`;
|
|
223
|
+
const ItemIcon = styled.span `
|
|
224
|
+
display: flex;
|
|
225
|
+
padding-right: 8px;
|
|
226
|
+
svg {
|
|
227
|
+
align-self: center;
|
|
228
|
+
}
|
|
229
|
+
`;
|
|
230
|
+
const ReferencesList = styled.ul `
|
|
231
|
+
display: flex;
|
|
232
|
+
gap: 10px;
|
|
233
|
+
flex-direction: column;
|
|
234
|
+
padding: 0;
|
|
235
|
+
margin: 0;
|
|
236
|
+
list-style: none;
|
|
237
|
+
`;
|
|
238
|
+
const ListItem = styled.li `
|
|
239
|
+
display: flex;
|
|
240
|
+
justify-content: space-between;
|
|
241
|
+
align-items: center;
|
|
242
|
+
border-bottom: solid 1px #e2e2e2;
|
|
243
|
+
&:last-child {
|
|
244
|
+
border-bottom: none;
|
|
245
|
+
padding: 0;
|
|
246
|
+
}
|
|
247
|
+
`;
|
|
248
|
+
const ScrollButton = styled(TertiaryButton) `
|
|
249
|
+
margin: 0;
|
|
250
|
+
padding: 4px;
|
|
251
|
+
font-size: 14px;
|
|
252
|
+
line-height: 24px;
|
|
253
|
+
text-decoration-line: underline;
|
|
254
|
+
`;
|
|
255
|
+
const XRefTextContainer = styled.div `
|
|
256
|
+
display: flex;
|
|
257
|
+
color: #6e6e6e;
|
|
258
|
+
font-size: 12px;
|
|
259
|
+
font-style: normal;
|
|
260
|
+
font-weight: 400;
|
|
261
|
+
line-height: 16px;
|
|
262
|
+
`;
|
|
263
|
+
const XRefLabel = styled.span `
|
|
264
|
+
border-radius: 4px;
|
|
265
|
+
border: 1px solid #fe8f1f;
|
|
266
|
+
background: #fff9e5;
|
|
267
|
+
mix-blend-mode: darken;
|
|
268
|
+
`;
|
|
269
|
+
const XRefAdjacentText = styled.span `
|
|
270
|
+
display: block;
|
|
271
|
+
max-width: 150px;
|
|
272
|
+
white-space: nowrap;
|
|
273
|
+
overflow: hidden;
|
|
274
|
+
text-overflow: ellipsis;
|
|
275
|
+
direction: ${(props) => props.$direction};
|
|
276
|
+
`;
|
|
277
|
+
const CaptionTitle = styled(XRefAdjacentText) `
|
|
278
|
+
max-width: 120px;
|
|
279
|
+
margin-left: 4px;
|
|
280
|
+
`;
|
|
281
|
+
const SelectorContainer = styled.div `
|
|
282
|
+
display: flex;
|
|
283
|
+
gap: 24px;
|
|
284
|
+
padding: 8px 12px 4px 12px;
|
|
285
|
+
margin: 24px 0;
|
|
286
|
+
border-radius: 4px;
|
|
287
|
+
border: 1px solid #fe8f1f;
|
|
288
|
+
background: #fff9e5;
|
|
289
|
+
`;
|
|
290
|
+
const ArrowDownIcon = styled(ArrowUpIcon) `
|
|
291
|
+
transform: rotate(180deg);
|
|
142
292
|
`;
|
|
@@ -15,14 +15,20 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import { getEditorProps } from '../../plugins/editor-props';
|
|
17
17
|
import ReactSubView from '../../views/ReactSubView';
|
|
18
|
-
import { CrossRefWarningModal } from './CrossRefWarningModal';
|
|
18
|
+
import { CrossRefWarningModal, } from './CrossRefWarningModal';
|
|
19
19
|
export const openCrossRefWarningModal = (view, xrefGroups, onConfirm, onClose, selectAndScrollTo) => {
|
|
20
20
|
const { state } = view;
|
|
21
21
|
const props = getEditorProps(state);
|
|
22
22
|
const componentProps = {
|
|
23
23
|
xrefs: xrefGroups,
|
|
24
|
-
onConfirm
|
|
25
|
-
|
|
24
|
+
onConfirm: (deleteOption) => {
|
|
25
|
+
dialog.unmount();
|
|
26
|
+
onConfirm(deleteOption);
|
|
27
|
+
},
|
|
28
|
+
onClose: () => {
|
|
29
|
+
dialog.unmount();
|
|
30
|
+
onClose();
|
|
31
|
+
},
|
|
26
32
|
selectAndScrollTo,
|
|
27
33
|
};
|
|
28
34
|
const dialog = ReactSubView(props, CrossRefWarningModal, componentProps, state.doc, () => 0, view);
|
package/dist/es/lib/utils.js
CHANGED
|
@@ -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(
|
|
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
|
-
|
|
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
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
|
|
181
|
-
|
|
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
|
};
|
package/dist/es/versions.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const VERSION = '3.19.
|
|
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
|
|
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") =>
|
|
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;
|
package/dist/types/versions.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION = "3.19.
|
|
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:
|
|
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[]):
|
|
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.
|
|
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",
|