@manuscripts/body-editor 3.16.0 → 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.
- package/dist/cjs/commands.js +45 -10
- package/dist/cjs/components/form/FormFooter.js +2 -2
- package/dist/cjs/components/keyboard-shortcuts-modal/KeyboardShortcutsModal.js +4 -2
- package/dist/cjs/components/views/CrossReferenceItems.js +139 -52
- package/dist/cjs/components/views/DeleteSupplementDialog.js +1 -1
- package/dist/cjs/lib/supplements.js +9 -1
- package/dist/cjs/menus.js +35 -0
- package/dist/cjs/plugins/objects.js +1 -1
- package/dist/cjs/toolbar.js +6 -0
- package/dist/cjs/versions.js +1 -1
- package/dist/cjs/views/cross_reference.js +12 -2
- package/dist/cjs/views/cross_reference_editable.js +95 -4
- package/dist/cjs/views/figure_editable.js +3 -0
- package/dist/es/commands.js +43 -9
- package/dist/es/components/form/FormFooter.js +2 -2
- package/dist/es/components/keyboard-shortcuts-modal/KeyboardShortcutsModal.js +4 -2
- package/dist/es/components/views/CrossReferenceItems.js +141 -54
- package/dist/es/components/views/DeleteSupplementDialog.js +2 -2
- package/dist/es/lib/supplements.js +8 -1
- package/dist/es/menus.js +36 -1
- package/dist/es/plugins/objects.js +1 -1
- package/dist/es/toolbar.js +8 -2
- package/dist/es/versions.js +1 -1
- package/dist/es/views/cross_reference.js +12 -2
- package/dist/es/views/cross_reference_editable.js +96 -5
- package/dist/es/views/figure_editable.js +3 -0
- package/dist/types/commands.d.ts +1 -0
- package/dist/types/components/form/FormFooter.d.ts +2 -1
- package/dist/types/components/views/CrossReferenceItems.d.ts +3 -0
- package/dist/types/lib/supplements.d.ts +1 -0
- package/dist/types/versions.d.ts +1 -1
- package/dist/types/views/cross_reference_editable.d.ts +6 -0
- package/package.json +4 -4
- package/src/commands.ts +52 -10
- package/src/components/ChangeHandlingForm.tsx +4 -2
- package/src/components/form/FormFooter.tsx +3 -1
- package/src/components/keyboard-shortcuts-modal/KeyboardShortcutsModal.tsx +6 -7
- package/src/components/toolbar/InsertEmbedDialog.tsx +6 -2
- package/src/components/toolbar/type-selector/styles.ts +2 -1
- package/src/components/views/CrossReferenceItems.tsx +227 -100
- package/src/components/views/DeleteSupplementDialog.tsx +3 -6
- package/src/lib/footnotes.ts +3 -1
- package/src/lib/supplements.ts +13 -1
- package/src/lib/utils.ts +15 -4
- package/src/menus.tsx +39 -0
- package/src/plugins/add-subtitle.ts +18 -16
- package/src/plugins/objects.ts +2 -6
- package/src/toolbar.tsx +9 -0
- package/src/versions.ts +1 -1
- package/src/views/cross_reference.ts +17 -3
- package/src/views/cross_reference_editable.ts +123 -11
- package/src/views/figure_editable.ts +4 -0
- package/src/views/supplement.ts +0 -1
- package/src/views/supplements.ts +2 -2
|
@@ -15,96 +15,147 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import {
|
|
18
|
-
|
|
18
|
+
CloseButton,
|
|
19
|
+
ExpandableSection,
|
|
19
20
|
PrimaryButton,
|
|
20
|
-
SecondaryButton,
|
|
21
|
-
TextArea,
|
|
22
|
-
withFocusTrap,
|
|
23
21
|
withListNavigation,
|
|
24
22
|
withNavigableListItem,
|
|
23
|
+
FileUnknownIcon,
|
|
24
|
+
getFileIcon,
|
|
25
|
+
ArrowDownIcon,
|
|
26
|
+
StyledModal,
|
|
27
|
+
ModalContainer,
|
|
28
|
+
ModalHeader,
|
|
29
|
+
ModalBody,
|
|
30
|
+
WebLinkIcon,
|
|
25
31
|
} from '@manuscripts/style-guide'
|
|
26
|
-
import { Target } from '@manuscripts/transform'
|
|
27
|
-
import React, { useEffect, useRef, useState } from 'react'
|
|
32
|
+
import { Target, schema } from '@manuscripts/transform'
|
|
33
|
+
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
|
28
34
|
import styled from 'styled-components'
|
|
29
35
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
flex-direction: column;
|
|
35
|
-
max-height: 60vh;
|
|
36
|
-
overflow: hidden;
|
|
37
|
-
`)
|
|
38
|
-
|
|
39
|
-
const Actions = styled.div`
|
|
40
|
-
display: flex;
|
|
41
|
-
align-items: center;
|
|
42
|
-
justify-content: flex-end;
|
|
43
|
-
flex-shrink: 0;
|
|
44
|
-
`
|
|
36
|
+
import { FileAttachment } from '../../lib/files'
|
|
37
|
+
import { allowedHref } from '../../lib/url'
|
|
38
|
+
import { nodeTypeIcon } from '../../node-type-icons'
|
|
39
|
+
import FormFooter from '../form/FormFooter'
|
|
45
40
|
|
|
46
41
|
const Items = withListNavigation(styled.div`
|
|
47
42
|
flex: 1;
|
|
48
43
|
overflow-y: auto;
|
|
49
|
-
padding: ${(props) => props.theme.grid.unit * 3}px;
|
|
50
44
|
`)
|
|
51
45
|
|
|
52
|
-
const CrossReferenceItem = withNavigableListItem(styled.div
|
|
53
|
-
$isSelected: boolean
|
|
54
|
-
}>`
|
|
46
|
+
const CrossReferenceItem = withNavigableListItem(styled.div`
|
|
55
47
|
cursor: pointer;
|
|
56
|
-
padding: ${(props) => props.theme.grid.unit *
|
|
57
|
-
background-color: ${(props) =>
|
|
58
|
-
props.$isSelected
|
|
59
|
-
? props.theme.colors.background.selected
|
|
60
|
-
: props.theme.colors.background.primary};
|
|
48
|
+
padding: ${(props) => props.theme.grid.unit * 2}px;
|
|
49
|
+
background-color: ${(props) => props.theme.colors.background.primary};
|
|
61
50
|
transition: background-color 0.1s;
|
|
62
|
-
border: solid
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
border-width: 1px 0;
|
|
68
|
-
z-index: ${(props) => (props.$isSelected ? '1' : '0')};
|
|
51
|
+
border: 1px solid ${(props) => props.theme.colors.border.secondary};
|
|
52
|
+
overflow: hidden;
|
|
53
|
+
max-width: 100%;
|
|
54
|
+
box-sizing: border-box;
|
|
55
|
+
margin-bottom: ${(props) => props.theme.grid.unit * 2}px;
|
|
69
56
|
|
|
70
57
|
&:hover {
|
|
71
|
-
background-color:
|
|
58
|
+
background-color: #f2f2f2;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
&.active {
|
|
62
|
+
background-color: #f2f2f2;
|
|
63
|
+
z-index: 1;
|
|
72
64
|
}
|
|
73
65
|
`)
|
|
74
66
|
|
|
75
67
|
const Label = styled.span`
|
|
76
68
|
color: ${(props) => props.theme.colors.text.primary};
|
|
69
|
+
-webkit-line-clamp: 2;
|
|
70
|
+
-webkit-box-orient: vertical;
|
|
71
|
+
overflow: hidden;
|
|
72
|
+
word-break: break-all;
|
|
73
|
+
overflow-wrap: break-word;
|
|
74
|
+
min-width: fit-content;
|
|
77
75
|
`
|
|
78
76
|
|
|
79
77
|
const Caption = styled.span`
|
|
80
78
|
color: ${(props) => props.theme.colors.text.secondary};
|
|
79
|
+
display: -webkit-box;
|
|
80
|
+
-webkit-line-clamp: 2;
|
|
81
|
+
-webkit-box-orient: vertical;
|
|
82
|
+
overflow: hidden;
|
|
83
|
+
word-break: break-all;
|
|
84
|
+
overflow-wrap: break-word;
|
|
85
|
+
margin-left: 3.2px;
|
|
81
86
|
`
|
|
82
87
|
|
|
83
88
|
const Heading = styled.div`
|
|
84
|
-
|
|
85
|
-
align-items: center;
|
|
86
|
-
justify-content: space-between;
|
|
87
|
-
flex-shrink: 0;
|
|
89
|
+
font-size: ${(props) => props.theme.font.size.medium};
|
|
88
90
|
font-weight: ${(props) => props.theme.font.weight.bold};
|
|
91
|
+
color: ${(props) => props.theme.colors.text.primary};
|
|
92
|
+
margin-bottom: ${(props) => props.theme.grid.unit * 3}px;
|
|
89
93
|
`
|
|
90
94
|
|
|
91
95
|
const Empty = styled.div`
|
|
92
96
|
margin-bottom: ${(props) => props.theme.grid.unit * 4}px;
|
|
93
97
|
color: ${(props) => props.theme.colors.text.tertiary};
|
|
94
98
|
`
|
|
99
|
+
|
|
100
|
+
const GroupExpandableSection = styled(ExpandableSection)`
|
|
101
|
+
& > div:first-child {
|
|
102
|
+
flex-direction: row-reverse;
|
|
103
|
+
justify-content: flex-end;
|
|
104
|
+
padding-left: 0;
|
|
105
|
+
gap: 10px;
|
|
106
|
+
font-weight: ${(props) => props.theme.font.weight.normal};
|
|
107
|
+
svg {
|
|
108
|
+
width: 20px;
|
|
109
|
+
height: 20px;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
`
|
|
95
113
|
const DefaultLabelWrapper = styled.div`
|
|
96
|
-
|
|
114
|
+
display: flex;
|
|
115
|
+
align-items: center;
|
|
116
|
+
min-width: 0;
|
|
97
117
|
`
|
|
98
118
|
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
119
|
+
const ItemIcon = styled.span`
|
|
120
|
+
flex-shrink: 0;
|
|
121
|
+
display: flex;
|
|
122
|
+
align-items: center;
|
|
123
|
+
color: ${(props) => props.theme.colors.text.secondary};
|
|
124
|
+
margin-right: ${(props) => props.theme.grid.unit * 2}px;
|
|
125
|
+
svg {
|
|
126
|
+
width: 20px;
|
|
127
|
+
height: 20px;
|
|
128
|
+
}
|
|
129
|
+
`
|
|
130
|
+
|
|
131
|
+
const FieldLabel = styled.label`
|
|
132
|
+
font-size: ${(props) => props.theme.font.size.normal};
|
|
133
|
+
color: ${(props) => props.theme.colors.text.secondary};
|
|
134
|
+
margin-top: ${(props) => props.theme.grid.unit * 4}px;
|
|
135
|
+
margin-bottom: ${(props) => props.theme.grid.unit}px;
|
|
136
|
+
`
|
|
137
|
+
|
|
138
|
+
const CustomTextInput = styled.input`
|
|
102
139
|
color: ${(props) => props.theme.colors.text.secondary};
|
|
103
|
-
background-color: ${(props) =>
|
|
104
|
-
props.theme.colors.background.primary} !important;
|
|
140
|
+
background-color: ${(props) => props.theme.colors.background.primary};
|
|
105
141
|
border: 1px solid ${(props) => props.theme.colors.border.secondary};
|
|
106
142
|
border-radius: ${(props) => props.theme.grid.radius.small};
|
|
107
143
|
padding: ${(props) => props.theme.grid.unit * 2}px;
|
|
144
|
+
&:disabled {
|
|
145
|
+
cursor: not-allowed;
|
|
146
|
+
background-color: ${(props) => props.theme.colors.background.disabled};
|
|
147
|
+
color: ${(props) => props.theme.colors.text.muted};
|
|
148
|
+
}
|
|
149
|
+
`
|
|
150
|
+
const StyledModalBody = styled(ModalBody)`
|
|
151
|
+
display: flex;
|
|
152
|
+
flex-direction: column;
|
|
153
|
+
padding: ${(props) => props.theme.grid.unit * 5}px
|
|
154
|
+
${(props) => props.theme.grid.unit * 6}px;
|
|
155
|
+
max-height: 580px;
|
|
156
|
+
min-width: 600px;
|
|
157
|
+
max-width: 600px;
|
|
158
|
+
overflow: hidden;
|
|
108
159
|
`
|
|
109
160
|
|
|
110
161
|
// trim a caption, avoiding cutting words
|
|
@@ -118,23 +169,54 @@ const trimmedCaption = (caption: string, limit: number): string => {
|
|
|
118
169
|
return caption.replace(captionSearch, '$1…')
|
|
119
170
|
}
|
|
120
171
|
|
|
172
|
+
const getTargetIcon = (
|
|
173
|
+
target: Target,
|
|
174
|
+
files: FileAttachment[]
|
|
175
|
+
): React.ReactNode => {
|
|
176
|
+
if (target.type === schema.nodes.supplement.name) {
|
|
177
|
+
if (target.href && allowedHref(target.href)) {
|
|
178
|
+
return <WebLinkIcon className="file-icon" />
|
|
179
|
+
}
|
|
180
|
+
const fileName =
|
|
181
|
+
files.find((f) => f.id === target.href)?.name ?? target.label ?? ''
|
|
182
|
+
return getFileIcon(fileName) ?? <FileUnknownIcon />
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return nodeTypeIcon(schema.nodes[target.type])
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const GROUP_LABELS: Record<string, string> = {
|
|
189
|
+
figure_element: 'Figures',
|
|
190
|
+
table_element: 'Tables',
|
|
191
|
+
equation_element: 'Equations',
|
|
192
|
+
listing_element: 'Listings',
|
|
193
|
+
box_element: 'Boxes',
|
|
194
|
+
embed: 'Media',
|
|
195
|
+
supplement: 'Supplementary files',
|
|
196
|
+
}
|
|
197
|
+
|
|
121
198
|
interface Props {
|
|
122
199
|
targets: Target[]
|
|
200
|
+
files: FileAttachment[]
|
|
123
201
|
handleSelect: (id: string, customLabel?: string) => void
|
|
124
202
|
handleCancel: () => void
|
|
125
203
|
currentTargetId?: string
|
|
126
204
|
currentCustomLabel?: string
|
|
205
|
+
isEdit?: boolean
|
|
127
206
|
}
|
|
128
207
|
|
|
129
208
|
export const CrossReferenceItems: React.FC<Props> = ({
|
|
130
209
|
targets,
|
|
210
|
+
files,
|
|
131
211
|
handleSelect,
|
|
132
212
|
handleCancel,
|
|
133
213
|
currentTargetId,
|
|
134
214
|
currentCustomLabel,
|
|
215
|
+
isEdit = false,
|
|
135
216
|
}) => {
|
|
217
|
+
const [isOpen, setIsOpen] = useState(true)
|
|
136
218
|
const [selectedItem, setSelectedItem] = useState<string>('')
|
|
137
|
-
const customTextRef = useRef<
|
|
219
|
+
const customTextRef = useRef<HTMLInputElement>(null)
|
|
138
220
|
|
|
139
221
|
useEffect(() => {
|
|
140
222
|
if (currentTargetId) {
|
|
@@ -142,58 +224,103 @@ export const CrossReferenceItems: React.FC<Props> = ({
|
|
|
142
224
|
}
|
|
143
225
|
}, [currentTargetId])
|
|
144
226
|
|
|
227
|
+
const close = useCallback(() => {
|
|
228
|
+
setIsOpen(false)
|
|
229
|
+
handleCancel()
|
|
230
|
+
}, [handleCancel])
|
|
231
|
+
|
|
232
|
+
const grouped = targets.reduce<Record<string, Target[]>>((acc, t) => {
|
|
233
|
+
;(acc[t.type] ??= []).push(t)
|
|
234
|
+
return acc
|
|
235
|
+
}, {})
|
|
236
|
+
|
|
145
237
|
return (
|
|
146
|
-
<
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
currentTargetId === selectedItem &&
|
|
170
|
-
currentCustomLabel
|
|
171
|
-
? currentCustomLabel
|
|
172
|
-
: ''
|
|
173
|
-
}
|
|
174
|
-
/>
|
|
175
|
-
)}
|
|
176
|
-
</CrossReferenceItem>
|
|
177
|
-
))
|
|
178
|
-
) : (
|
|
179
|
-
<Empty>No cross-reference targets available.</Empty>
|
|
180
|
-
)}
|
|
181
|
-
</Items>
|
|
182
|
-
|
|
183
|
-
<Actions>
|
|
184
|
-
<ButtonGroup>
|
|
185
|
-
<SecondaryButton onClick={handleCancel}>Cancel</SecondaryButton>
|
|
186
|
-
<PrimaryButton
|
|
187
|
-
onClick={() =>
|
|
188
|
-
selectedItem &&
|
|
189
|
-
handleSelect(selectedItem, customTextRef.current?.value || '')
|
|
190
|
-
}
|
|
238
|
+
<StyledModal
|
|
239
|
+
isOpen={isOpen}
|
|
240
|
+
onRequestClose={close}
|
|
241
|
+
shouldCloseOnOverlayClick={true}
|
|
242
|
+
>
|
|
243
|
+
<ModalContainer data-cy="cross-reference-editor">
|
|
244
|
+
<ModalHeader>
|
|
245
|
+
<CloseButton onClick={close} />
|
|
246
|
+
</ModalHeader>
|
|
247
|
+
<StyledModalBody>
|
|
248
|
+
<Heading>
|
|
249
|
+
{isEdit ? 'Edit Cross-reference' : 'Insert Cross-reference'}
|
|
250
|
+
</Heading>
|
|
251
|
+
|
|
252
|
+
<FieldLabel htmlFor="cross-reference-custom-text">
|
|
253
|
+
Custom display text
|
|
254
|
+
</FieldLabel>
|
|
255
|
+
<CustomTextInput
|
|
256
|
+
ref={customTextRef}
|
|
257
|
+
id="cross-reference-custom-text"
|
|
258
|
+
data-cy="cross-reference-custom-text"
|
|
259
|
+
type="text"
|
|
260
|
+
placeholder={'Custom display text...'}
|
|
191
261
|
disabled={!selectedItem}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
262
|
+
defaultValue={currentCustomLabel ?? ''}
|
|
263
|
+
/>
|
|
264
|
+
|
|
265
|
+
<Items>
|
|
266
|
+
{Object.keys(grouped).length ? (
|
|
267
|
+
Object.entries(grouped).map(([type, group]) => (
|
|
268
|
+
<GroupExpandableSection
|
|
269
|
+
key={type}
|
|
270
|
+
title={GROUP_LABELS[type]}
|
|
271
|
+
icon={ArrowDownIcon}
|
|
272
|
+
>
|
|
273
|
+
{group.map((target) => {
|
|
274
|
+
const icon = getTargetIcon(target, files)
|
|
275
|
+
|
|
276
|
+
return (
|
|
277
|
+
<CrossReferenceItem
|
|
278
|
+
key={target.id}
|
|
279
|
+
data-cy="cross-reference-item"
|
|
280
|
+
className={
|
|
281
|
+
selectedItem === target.id ? 'active' : undefined
|
|
282
|
+
}
|
|
283
|
+
onClick={() => setSelectedItem(target.id)}
|
|
284
|
+
>
|
|
285
|
+
<DefaultLabelWrapper>
|
|
286
|
+
{icon ? <ItemIcon>{icon}</ItemIcon> : null}
|
|
287
|
+
<Label>
|
|
288
|
+
{target.label}
|
|
289
|
+
{target.caption && `:`}
|
|
290
|
+
</Label>
|
|
291
|
+
<Caption>
|
|
292
|
+
{trimmedCaption(target.caption, 200)}
|
|
293
|
+
</Caption>
|
|
294
|
+
</DefaultLabelWrapper>
|
|
295
|
+
</CrossReferenceItem>
|
|
296
|
+
)
|
|
297
|
+
})}
|
|
298
|
+
</GroupExpandableSection>
|
|
299
|
+
))
|
|
300
|
+
) : (
|
|
301
|
+
<Empty>No cross-reference targets available.</Empty>
|
|
302
|
+
)}
|
|
303
|
+
</Items>
|
|
304
|
+
</StyledModalBody>
|
|
305
|
+
<FormFooter
|
|
306
|
+
onCancel={close}
|
|
307
|
+
cancelLabel="Cancel"
|
|
308
|
+
primaryAction={
|
|
309
|
+
<PrimaryButton
|
|
310
|
+
onClick={() => {
|
|
311
|
+
if (selectedItem) {
|
|
312
|
+
setIsOpen(false)
|
|
313
|
+
const customLabel = customTextRef.current?.value
|
|
314
|
+
handleSelect(selectedItem, customLabel || '')
|
|
315
|
+
}
|
|
316
|
+
}}
|
|
317
|
+
disabled={!selectedItem}
|
|
318
|
+
>
|
|
319
|
+
{isEdit ? 'Update' : 'Insert'}
|
|
320
|
+
</PrimaryButton>
|
|
321
|
+
}
|
|
322
|
+
/>
|
|
323
|
+
</ModalContainer>
|
|
324
|
+
</StyledModal>
|
|
198
325
|
)
|
|
199
326
|
}
|
|
@@ -13,10 +13,7 @@
|
|
|
13
13
|
* See the License for the specific language governing permissions and
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
|
-
import {
|
|
17
|
-
Category,
|
|
18
|
-
Dialog,
|
|
19
|
-
} from '@manuscripts/style-guide'
|
|
16
|
+
import { Category, Dialog } from '@manuscripts/style-guide'
|
|
20
17
|
import { ManuscriptEditorView, SupplementNode } from '@manuscripts/transform'
|
|
21
18
|
import React, { useState } from 'react'
|
|
22
19
|
|
|
@@ -37,8 +34,8 @@ const DeleteSupplementDialog: React.FC<{
|
|
|
37
34
|
<Dialog
|
|
38
35
|
isOpen={isOpen}
|
|
39
36
|
category={Category.confirmation}
|
|
40
|
-
header=
|
|
41
|
-
message=
|
|
37
|
+
header="Delete supplement"
|
|
38
|
+
message="Are you sure you want to delete “{label}”?"
|
|
42
39
|
actions={{
|
|
43
40
|
primary: {
|
|
44
41
|
action: () => {
|
package/src/lib/footnotes.ts
CHANGED
|
@@ -43,7 +43,9 @@ export const findFootnotesContainerNode = (
|
|
|
43
43
|
)
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
export const findParentFootnote: (
|
|
46
|
+
export const findParentFootnote: (
|
|
47
|
+
selection: Selection
|
|
48
|
+
) => ContentNodeWithPos | undefined = findParentNodeOfType([
|
|
47
49
|
schema.nodes.footnote,
|
|
48
50
|
schema.nodes.general_table_footnote,
|
|
49
51
|
])
|
package/src/lib/supplements.ts
CHANGED
|
@@ -20,7 +20,10 @@ import {
|
|
|
20
20
|
schema,
|
|
21
21
|
SupplementNode,
|
|
22
22
|
} from '@manuscripts/transform'
|
|
23
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
findChildrenByType,
|
|
25
|
+
findParentNodeClosestToPos,
|
|
26
|
+
} from 'prosemirror-utils'
|
|
24
27
|
|
|
25
28
|
import { allowedHref } from './url'
|
|
26
29
|
|
|
@@ -29,10 +32,19 @@ export type NodeWeblink = {
|
|
|
29
32
|
pos: number
|
|
30
33
|
}
|
|
31
34
|
|
|
35
|
+
export const getSupplementCaptionTitle = (node: SupplementNode): string => {
|
|
36
|
+
return node.firstChild?.textContent.trim() ?? ''
|
|
37
|
+
}
|
|
38
|
+
|
|
32
39
|
export const getSupplementDisplayLabel = (
|
|
33
40
|
node: SupplementNode,
|
|
34
41
|
files: { id: string; name: string }[]
|
|
35
42
|
): string => {
|
|
43
|
+
const captionTitle = getSupplementCaptionTitle(node)
|
|
44
|
+
if (captionTitle) {
|
|
45
|
+
return captionTitle
|
|
46
|
+
}
|
|
47
|
+
|
|
36
48
|
const href = node.attrs.href
|
|
37
49
|
if (allowedHref(href)) {
|
|
38
50
|
return href
|
package/src/lib/utils.ts
CHANGED
|
@@ -89,15 +89,26 @@ export const getChildOfType = (
|
|
|
89
89
|
): boolean =>
|
|
90
90
|
!!getMatchingChild(parent, (node) => node.type.name === nodeType.name, deep)
|
|
91
91
|
|
|
92
|
-
export const findParentNodeWithId: (
|
|
92
|
+
export const findParentNodeWithId: (
|
|
93
|
+
selection: Selection
|
|
94
|
+
) => ContentNodeWithPos | undefined = findParentNode(
|
|
95
|
+
(node) => 'id' in node.attrs
|
|
96
|
+
)
|
|
93
97
|
|
|
94
|
-
export const findParentNodeWithIdValue: (
|
|
98
|
+
export const findParentNodeWithIdValue: (
|
|
99
|
+
selection: Selection
|
|
100
|
+
) => ContentNodeWithPos | undefined = findParentNode((node) => node.attrs.id)
|
|
95
101
|
|
|
96
|
-
export const findParentSection: (
|
|
102
|
+
export const findParentSection: (
|
|
103
|
+
selection: Selection
|
|
104
|
+
) => ContentNodeWithPos | undefined = findParentNode((node) =>
|
|
97
105
|
isSectionNodeType(node.type)
|
|
98
106
|
)
|
|
99
107
|
|
|
100
|
-
export const findParentElement = (
|
|
108
|
+
export const findParentElement = (
|
|
109
|
+
selection: Selection,
|
|
110
|
+
validIds?: string[]
|
|
111
|
+
): ContentNodeWithPos | undefined =>
|
|
101
112
|
findParentNode((node) => {
|
|
102
113
|
// if validIds was passed and this element is not in it, then keep looking
|
|
103
114
|
if (validIds && !validIds.includes(node.attrs.id)) {
|
package/src/menus.tsx
CHANGED
|
@@ -38,6 +38,7 @@ import {
|
|
|
38
38
|
insertBoxElement,
|
|
39
39
|
insertContributors,
|
|
40
40
|
insertCrossReference,
|
|
41
|
+
canInsertCrossReference,
|
|
41
42
|
insertEmbed,
|
|
42
43
|
insertGraphicalAbstract,
|
|
43
44
|
insertHeadshotGrid,
|
|
@@ -400,6 +401,44 @@ export const getEditorMenus = (
|
|
|
400
401
|
{
|
|
401
402
|
role: 'separator',
|
|
402
403
|
},
|
|
404
|
+
{
|
|
405
|
+
id: 'insert-citation',
|
|
406
|
+
label: 'Citation',
|
|
407
|
+
shortcut: {
|
|
408
|
+
mac: 'Option+CommandOrControl+C',
|
|
409
|
+
pc: 'CommandOrControl+Option+C',
|
|
410
|
+
},
|
|
411
|
+
isEnabled:
|
|
412
|
+
isEditAllowed(state) &&
|
|
413
|
+
isCommandValid(canInsert(schema.nodes.citation)),
|
|
414
|
+
run: doCommand(insertInlineCitation),
|
|
415
|
+
isHidden: !templateAllows(state, schema.nodes.citation),
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
id: 'insert-cross-reference',
|
|
419
|
+
label: 'Cross-reference',
|
|
420
|
+
shortcut: {
|
|
421
|
+
mac: 'Option+CommandOrControl+R',
|
|
422
|
+
pc: 'CommandOrControl+Option+R',
|
|
423
|
+
},
|
|
424
|
+
isEnabled:
|
|
425
|
+
isEditAllowed(state) && isCommandValid(canInsertCrossReference),
|
|
426
|
+
run: doCommand(insertCrossReference),
|
|
427
|
+
isHidden: !templateAllows(state, schema.nodes.cross_reference),
|
|
428
|
+
},
|
|
429
|
+
{
|
|
430
|
+
id: 'insert-footnote',
|
|
431
|
+
label: 'Footnote',
|
|
432
|
+
shortcut: {
|
|
433
|
+
mac: 'Option+CommandOrControl+F',
|
|
434
|
+
pc: 'CommandOrControl+Option+F',
|
|
435
|
+
},
|
|
436
|
+
isEnabled:
|
|
437
|
+
isEditAllowed(state) &&
|
|
438
|
+
isCommandValid(canInsert(schema.nodes.inline_footnote)),
|
|
439
|
+
run: doCommand(insertInlineFootnote),
|
|
440
|
+
isHidden: !templateAllows(state, schema.nodes.inline_footnote),
|
|
441
|
+
},
|
|
403
442
|
{
|
|
404
443
|
id: 'insert-special-character',
|
|
405
444
|
label: 'Special Characters',
|
|
@@ -72,23 +72,25 @@ export default (props: EditorProps) =>
|
|
|
72
72
|
const titleEndPos = titlePos + titleNode.nodeSize
|
|
73
73
|
|
|
74
74
|
return DecorationSet.create(state.doc, [
|
|
75
|
-
Decoration.widget(
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
75
|
+
Decoration.widget(
|
|
76
|
+
titleEndPos,
|
|
77
|
+
(view: ManuscriptEditorView) => {
|
|
78
|
+
return createAddSubtitleButton(() => {
|
|
79
|
+
const subtitlesNode = schema.nodes.subtitles.create(
|
|
80
|
+
{ id: uuidv4() },
|
|
81
|
+
[schema.nodes.subtitle.create({ id: uuidv4() })]
|
|
82
|
+
)
|
|
83
|
+
const pos = findInsertionPosition(
|
|
84
|
+
schema.nodes.subtitles,
|
|
85
|
+
state.doc
|
|
86
|
+
)
|
|
87
|
+
const tr = view.state.tr.insert(pos, subtitlesNode)
|
|
88
|
+
const subtitlePos = pos + 1
|
|
89
|
+
tr.setSelection(TextSelection.create(tr.doc, subtitlePos))
|
|
88
90
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
91
|
+
view.dispatch(tr)
|
|
92
|
+
view.focus()
|
|
93
|
+
})
|
|
92
94
|
},
|
|
93
95
|
{ side: -1 }
|
|
94
96
|
),
|
package/src/plugins/objects.ts
CHANGED
|
@@ -60,7 +60,7 @@ export default () => {
|
|
|
60
60
|
if (id) {
|
|
61
61
|
const target = targets.get(id)
|
|
62
62
|
|
|
63
|
-
if (target) {
|
|
63
|
+
if (target && target.label) {
|
|
64
64
|
const caption = findChildren(
|
|
65
65
|
node,
|
|
66
66
|
(node) =>
|
|
@@ -115,11 +115,7 @@ export default () => {
|
|
|
115
115
|
* Returns the position for a widget decoration when there is no caption.
|
|
116
116
|
* Falls back to the end of the node.
|
|
117
117
|
*/
|
|
118
|
-
const getDecorationPos = (
|
|
119
|
-
target: Target,
|
|
120
|
-
doc: ManuscriptNode,
|
|
121
|
-
pos: number
|
|
122
|
-
) => {
|
|
118
|
+
const getDecorationPos = (target: Target, doc: ManuscriptNode, pos: number) => {
|
|
123
119
|
const $pos = doc.resolve(pos + 1)
|
|
124
120
|
return $pos.end()
|
|
125
121
|
}
|
package/src/toolbar.tsx
CHANGED
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
ToolbarBoldIcon,
|
|
25
25
|
ToolbarBoxedTextIcon,
|
|
26
26
|
ToolbarCitationIcon,
|
|
27
|
+
ToolbarCrossReferenceIcon,
|
|
27
28
|
ToolbarEquationIcon,
|
|
28
29
|
ToolbarFigureIcon,
|
|
29
30
|
ToolbarIndentIcon,
|
|
@@ -47,9 +48,11 @@ import {
|
|
|
47
48
|
addInlineComment,
|
|
48
49
|
blockActive,
|
|
49
50
|
canInsert,
|
|
51
|
+
canInsertCrossReference,
|
|
50
52
|
Dispatch,
|
|
51
53
|
insertBlock,
|
|
52
54
|
insertBoxElement,
|
|
55
|
+
insertCrossReference,
|
|
53
56
|
insertEmbed,
|
|
54
57
|
insertInlineCitation,
|
|
55
58
|
insertLink,
|
|
@@ -179,6 +182,12 @@ export const toolbar: ToolbarConfig = {
|
|
|
179
182
|
isEnabled: isEnabled(canInsert(schema.nodes.highlight_marker)), // TODO: check both ends of selection
|
|
180
183
|
run: addInlineComment,
|
|
181
184
|
},
|
|
185
|
+
cross_reference: {
|
|
186
|
+
title: 'Insert cross-reference',
|
|
187
|
+
content: <ToolbarCrossReferenceIcon />,
|
|
188
|
+
isEnabled: isEnabled(canInsertCrossReference),
|
|
189
|
+
run: insertCrossReference,
|
|
190
|
+
},
|
|
182
191
|
citation: {
|
|
183
192
|
title: 'Insert citation',
|
|
184
193
|
content: <ToolbarCitationIcon />,
|
package/src/versions.ts
CHANGED