@manuscripts/body-editor 3.19.0 → 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 (53) 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/components/references/ReferenceForm/ReferenceForm.js +22 -40
  4. package/dist/cjs/components/references/ReferenceForm/styled-components.js +1 -13
  5. package/dist/cjs/components/references/ReferencesEditor.js +26 -1
  6. package/dist/cjs/components/references/ReferencesModal.js +71 -7
  7. package/dist/cjs/lib/utils.js +12 -1
  8. package/dist/cjs/menus.js +12 -0
  9. package/dist/cjs/plugins/cross-references.js +37 -14
  10. package/dist/cjs/versions.js +1 -1
  11. package/dist/cjs/views/ReactSubView.js +1 -0
  12. package/dist/cjs/views/cross_reference.js +13 -2
  13. package/dist/cjs/views/cross_reference_editable.js +1 -4
  14. package/dist/es/components/cross-ref-check-modal/CrossRefWarningModal.js +202 -52
  15. package/dist/es/components/cross-ref-check-modal/openModal.js +9 -3
  16. package/dist/es/components/references/ReferenceForm/ReferenceForm.js +21 -40
  17. package/dist/es/components/references/ReferenceForm/styled-components.js +0 -12
  18. package/dist/es/components/references/ReferencesEditor.js +21 -0
  19. package/dist/es/components/references/ReferencesModal.js +73 -9
  20. package/dist/es/lib/utils.js +11 -1
  21. package/dist/es/menus.js +12 -0
  22. package/dist/es/plugins/cross-references.js +37 -15
  23. package/dist/es/versions.js +1 -1
  24. package/dist/es/views/ReactSubView.js +1 -0
  25. package/dist/es/views/cross_reference.js +13 -2
  26. package/dist/es/views/cross_reference_editable.js +1 -4
  27. package/dist/types/components/authors-affiliations/AuthorsAndAffiliationsModals.d.ts +1 -1
  28. package/dist/types/components/cross-ref-check-modal/CrossRefWarningModal.d.ts +4 -2
  29. package/dist/types/components/cross-ref-check-modal/openModal.d.ts +2 -2
  30. package/dist/types/components/references/ReferenceForm/ReferenceForm.d.ts +3 -3
  31. package/dist/types/components/references/ReferenceForm/styled-components.d.ts +0 -1
  32. package/dist/types/components/references/ReferencesEditor.d.ts +3 -0
  33. package/dist/types/lib/utils.d.ts +4 -0
  34. package/dist/types/plugins/cross-references.d.ts +2 -0
  35. package/dist/types/versions.d.ts +1 -1
  36. package/dist/types/views/ReactSubView.d.ts +5 -2
  37. package/dist/types/views/cross_reference.d.ts +1 -0
  38. package/package.json +2 -2
  39. package/src/components/cross-ref-check-modal/CrossRefWarningModal.tsx +315 -83
  40. package/src/components/cross-ref-check-modal/openModal.ts +14 -4
  41. package/src/components/references/ReferenceForm/ReferenceForm.tsx +21 -82
  42. package/src/components/references/ReferenceForm/styled-components.ts +0 -13
  43. package/src/components/references/ReferencesEditor.tsx +38 -0
  44. package/src/components/references/ReferencesModal.tsx +116 -14
  45. package/src/lib/utils.ts +24 -0
  46. package/src/lib/view.ts +1 -0
  47. package/src/menus.tsx +12 -0
  48. package/src/plugins/cross-references.ts +41 -13
  49. package/src/versions.ts +1 -1
  50. package/src/views/ReactSubView.tsx +6 -3
  51. package/src/views/cross_reference.ts +14 -2
  52. package/src/views/cross_reference_editable.ts +1 -4
  53. package/styles/Editor.css +12 -0
@@ -14,71 +14,148 @@
14
14
  * limitations under the License.
15
15
  */
16
16
 
17
- import { ManuscriptNode } from '@manuscripts/transform'
18
- import React, { useState } from 'react'
19
-
20
17
  import {
21
- AttentionOrangeIcon,
22
- CloseButton,
18
+ ArrowUpIcon,
19
+ AttentionRedIcon,
20
+ ButtonGroup,
21
+ IconButton,
23
22
  ModalContainer,
24
- ModalHeader,
25
23
  PrimaryButton,
24
+ RadioButton,
26
25
  StyledModal,
27
26
  TertiaryButton,
28
- TextButton,
27
+ WebLinkIcon,
28
+ withFocusTrap,
29
29
  } from '@manuscripts/style-guide'
30
- import { ResolvedPos } from 'prosemirror-model'
31
- import { startCase } from 'lodash'
30
+ import { ManuscriptNode, schema } from '@manuscripts/transform'
31
+ import { NodeType, ResolvedPos } from 'prosemirror-model'
32
+ import React, { useState } from 'react'
33
+
34
+ import { getSurroundingText } from '../../lib/utils'
35
+ import { nodeTypeIcon } from '../../node-type-icons'
32
36
  import styled from 'styled-components'
33
37
 
34
38
  export type XrefGroup = {
35
39
  referenced: ManuscriptNode
36
40
  label: string
41
+ caption: string
37
42
  xrefs: [ManuscriptNode, ResolvedPos][]
38
43
  }
39
44
 
45
+ export type DeleteOption = 'delete-without-ref' | 'delete-with-ref'
46
+
40
47
  export const CrossRefWarningModal: React.FC<{
41
48
  onClose: () => void
42
49
  xrefs: XrefGroup[]
43
- onConfirm: () => void
50
+ onConfirm: (deleteOption: DeleteOption) => void
44
51
  selectAndScrollTo: ($pos: ResolvedPos) => void
45
52
  }> = ({ onClose, xrefs, onConfirm, selectAndScrollTo }) => {
46
53
  const [isOpen, setIsOpen] = useState(true)
54
+ const [deleteOption, setDeleteOption] =
55
+ useState<DeleteOption>('delete-without-ref')
56
+
47
57
  const handleClose = () => {
48
58
  setIsOpen(false)
49
59
  onClose()
50
60
  }
51
61
 
62
+ const [showRef, setShowRef] = useState(false)
63
+ const [isScrolling, setIsScrolling] = useState(false)
64
+
65
+ const handleSelectAndScrollTo = ($pos: ResolvedPos) => {
66
+ setIsScrolling(true)
67
+ selectAndScrollTo($pos)
68
+ }
69
+
70
+ const toggleReferenceList = () => setShowRef(!showRef)
71
+
72
+ const onChangeDeleteOption = (e: React.ChangeEvent<HTMLInputElement>) => {
73
+ const { value } = e.target
74
+ setDeleteOption(value as DeleteOption)
75
+ }
76
+
77
+ const references = xrefs.flatMap((xrefGroup) =>
78
+ xrefGroup.xrefs.map((xref) => [...xref])
79
+ ) as [ManuscriptNode, ResolvedPos][]
80
+
81
+ const elementLabel =
82
+ xrefs.length === 1 ? xrefs[0].label : `${xrefs.length} elements`
83
+ const locationWord = references.length === 1 ? 'location' : 'locations'
84
+ const pronoun = xrefs.length === 1 ? 'it' : 'them'
85
+
52
86
  return (
53
87
  <Modal
54
88
  isOpen={isOpen}
89
+ $isScrolling={isScrolling}
55
90
  onRequestClose={() => handleClose()}
56
91
  shouldCloseOnOverlayClick={false}
57
92
  hideOverlay={true}
58
93
  >
59
- <Container data-cy="cross-reference-warning-modal">
60
- <ModalHeader>
61
- <CloseButton
62
- onClick={() => handleClose()}
63
- data-cy="modal-close-button"
64
- />
65
- </ModalHeader>
94
+ <Container
95
+ $isScrolling={isScrolling}
96
+ data-cy="cross-reference-warning-modal"
97
+ >
66
98
  <Body>
67
99
  <Title>
68
- <AttentionOrangeIcon width={24} height={22} /> Delete referenced
69
- content?
100
+ <AttentionRedIcon width={24} height={24} />
101
+ Confirm Deletion
70
102
  </Title>
71
- <p>You are deleting content referenced elsewhere in the document:</p>
72
- <ScrolableItems>
73
- {xrefs.map((group, i) => (
74
- <XrefGroupDisplay
75
- key={i}
76
- group={group}
77
- selectAndScrollTo={selectAndScrollTo}
78
- />
79
- ))}
80
- </ScrolableItems>
81
- <Actions>
103
+ <p>
104
+ <b>{elementLabel}</b> {xrefs.length === 1 ? 'is' : 'are'} actively
105
+ referenced in{' '}
106
+ <b>
107
+ {references.length} {locationWord}
108
+ </b>{' '}
109
+ in your document. Deleting {pronoun} will break the following
110
+ cross-references:
111
+ </p>
112
+ <div>
113
+ <ToggleHeader>
114
+ <SecondaryBoldHeading>
115
+ {showRef
116
+ ? 'Hide locations'
117
+ : `Show ${references.length} location`}
118
+ </SecondaryBoldHeading>
119
+ <ToggleButton onClick={toggleReferenceList}>
120
+ {showRef ? <ArrowUpIcon /> : <ArrowDownIcon />}
121
+ </ToggleButton>
122
+ </ToggleHeader>
123
+ {showRef && (
124
+ <ListWrapper>
125
+ {xrefs.length === 1 ? (
126
+ <XrefGroupDisplay
127
+ label={xrefs[0].label}
128
+ xrefs={xrefs[0].xrefs}
129
+ selectAndScrollTo={handleSelectAndScrollTo}
130
+ />
131
+ ) : (
132
+ <XrefGroupsDisplay
133
+ xrefsGroup={xrefs}
134
+ selectAndScrollTo={handleSelectAndScrollTo}
135
+ />
136
+ )}
137
+ </ListWrapper>
138
+ )}
139
+ </div>
140
+ <SelectorContainer>
141
+ <RadioButton
142
+ name={'delete-option'}
143
+ id={'delete-without-ref-option'}
144
+ value={'delete-without-ref'}
145
+ label={'Delete but keep referenced text'}
146
+ checked={deleteOption === 'delete-without-ref'}
147
+ onChange={onChangeDeleteOption}
148
+ />
149
+ <RadioButton
150
+ name={'delete-option'}
151
+ id={'delete-with-ref-option'}
152
+ value={'delete-with-ref'}
153
+ label={'Delete & remove references'}
154
+ checked={deleteOption === 'delete-with-ref'}
155
+ onChange={onChangeDeleteOption}
156
+ />
157
+ </SelectorContainer>
158
+ <ButtonGroup>
82
159
  <TertiaryButton type="button" onClick={() => handleClose()}>
83
160
  Cancel
84
161
  </TertiaryButton>
@@ -86,13 +163,13 @@ export const CrossRefWarningModal: React.FC<{
86
163
  $danger={true}
87
164
  type="button"
88
165
  onClick={() => {
89
- onConfirm()
166
+ onConfirm(deleteOption)
90
167
  setIsOpen(false)
91
168
  }}
92
169
  >
93
- Delete & remove citation
170
+ Delete
94
171
  </PrimaryButton>
95
- </Actions>
172
+ </ButtonGroup>
96
173
  </Body>
97
174
  </Container>
98
175
  </Modal>
@@ -100,45 +177,95 @@ export const CrossRefWarningModal: React.FC<{
100
177
  }
101
178
 
102
179
  const XrefGroupDisplay: React.FC<{
103
- group: XrefGroup
180
+ label: string
181
+ xrefs: XrefGroup['xrefs']
182
+ selectAndScrollTo: ($pos: ResolvedPos) => void
183
+ }> = ({ label, xrefs, selectAndScrollTo }) => {
184
+ return (
185
+ <ReferencesList>
186
+ {xrefs.map(([xrefNode, pos], i) => {
187
+ const { leftHandText, rightHandText } = getSurroundingText(
188
+ pos,
189
+ xrefNode
190
+ )
191
+ const derivedLabel = xrefNode.attrs.label || label || '[cross-ref]'
192
+
193
+ return (
194
+ <ListItem key={i}>
195
+ <XRefTextContainer>
196
+ <XRefAdjacentText $direction={'rtl'}>
197
+ {leftHandText}
198
+ </XRefAdjacentText>
199
+ <XRefLabel>{derivedLabel}</XRefLabel>
200
+ <XRefAdjacentText $direction={'ltr'}>
201
+ {rightHandText}
202
+ </XRefAdjacentText>
203
+ </XRefTextContainer>
204
+ <ScrollButton $mini={true} onClick={() => selectAndScrollTo(pos)}>
205
+ Show
206
+ </ScrollButton>
207
+ </ListItem>
208
+ )
209
+ })}
210
+ </ReferencesList>
211
+ )
212
+ }
213
+
214
+ const XrefGroupsDisplay: React.FC<{
215
+ xrefsGroup: XrefGroup[]
104
216
  selectAndScrollTo: ($pos: ResolvedPos) => void
105
- }> = ({ group, selectAndScrollTo }) => {
217
+ }> = ({ xrefsGroup, selectAndScrollTo }) => {
106
218
  return (
107
- <div>
108
- <h3>{group.label}</h3>
109
- <ReferencesList>
110
- {group.xrefs.map(([, pos], i) => {
111
- return (
112
- <li key={i}>
113
- <TextButton onClick={() => selectAndScrollTo(pos)}>
114
- {`${startCase(pos.parent.type.name)} - ${pos.parent.textContent}`}
115
- </TextButton>
116
- </li>
117
- )
118
- })}
119
- </ReferencesList>
120
- </div>
219
+ <ReferencesList>
220
+ {xrefsGroup.map(({ referenced, label, caption, xrefs }, i) => {
221
+ const icon = getReferencedIcon(referenced.type)
222
+
223
+ return (
224
+ <div key={i}>
225
+ <DarkSecondaryBoldHeading>
226
+ {icon ? <ItemIcon>{icon}</ItemIcon> : null}
227
+ {`${label}:`}
228
+ <CaptionTitle $direction={'ltr'}>{caption}</CaptionTitle>
229
+ </DarkSecondaryBoldHeading>
230
+ <XrefGroupDisplay
231
+ label={label}
232
+ xrefs={xrefs}
233
+ selectAndScrollTo={selectAndScrollTo}
234
+ />
235
+ </div>
236
+ )
237
+ })}
238
+ </ReferencesList>
121
239
  )
122
240
  }
123
241
 
124
- const Container = styled(ModalContainer)`
242
+ const getReferencedIcon = (type: NodeType): React.ReactNode => {
243
+ if (type === schema.nodes.supplement) {
244
+ return <WebLinkIcon className="file-icon" />
245
+ }
246
+ return nodeTypeIcon(schema.nodes[type.name])
247
+ }
248
+
249
+ const Container = styled(ModalContainer)<{ $isScrolling: boolean }>`
125
250
  position: absolute;
126
- top: 1rem;
127
- left: 50%;
128
- right: 0;
129
- max-height: calc(50vh - 2rem);
251
+ top: ${({ $isScrolling }) => ($isScrolling ? '1rem' : '30%')};
252
+ left: ${({ $isScrolling }) =>
253
+ $isScrolling ? 'calc(100% - 556px - 0rem)' : '50%'};
254
+ max-height: calc(60vh - 2rem);
130
255
  min-height: 280px;
131
- transform: translate(-50%, 0);
132
- max-width: 480px;
256
+ transform: ${({ $isScrolling }) =>
257
+ $isScrolling ? 'translate(0, 0)' : 'translate(-50%, 0)'};
258
+ max-width: 556px;
133
259
  transition:
134
260
  top 0.2s,
261
+ left 0.2s,
135
262
  transform 0.2s;
136
263
  `
137
264
 
138
265
  // since we need to scroll inside the editor when this dialog is active, we can't use dialog.showModal()
139
266
  // so we recreate the appearance using classic position:fixed/after approach.
140
267
  // While showModal doesn't block scrolling - it doesn't allow to focus on the editor and that kills the scrollIntoView
141
- const Modal = styled(StyledModal)`
268
+ const Modal = styled(StyledModal)<{ $isScrolling: boolean }>`
142
269
  position: fixed;
143
270
  top: 0;
144
271
  left: 0;
@@ -163,6 +290,8 @@ const Modal = styled(StyledModal)`
163
290
  right: 0;
164
291
  bottom: 0;
165
292
  background: rgba(0, 0, 0, 0.2);
293
+ opacity: ${({ $isScrolling }) => ($isScrolling ? 0.15 : 1)};
294
+ transform: opacity 0.2s;
166
295
  }
167
296
  h3 {
168
297
  font-size: 16px;
@@ -173,11 +302,11 @@ const Modal = styled(StyledModal)`
173
302
  }
174
303
  `
175
304
 
176
- const Body = styled.div`
305
+ const Body = withFocusTrap(styled.div`
177
306
  margin: 1.5rem;
178
307
  display: flex;
179
308
  flex-flow: column;
180
- `
309
+ `)
181
310
 
182
311
  const Title = styled.h2`
183
312
  font-size: 18px;
@@ -187,37 +316,140 @@ const Title = styled.h2`
187
316
  color: #353535;
188
317
  svg {
189
318
  vertical-align: text-top;
319
+ margin-right: 8px;
190
320
  }
191
321
  `
192
322
 
193
- const ReferencesList = styled.ul`
194
- padding: 8px;
195
- margin-left: 0;
196
- list-style: none;
323
+ const ToggleHeader = styled.div`
324
+ display: flex;
325
+ justify-content: space-between;
326
+ align-items: center;
327
+ padding: 6px 3px 6px 8px;
328
+ border-radius: 4px 4px 0 0;
329
+ border: 1px solid #e2e2e2;
197
330
  background: #f2f2f2;
331
+ `
332
+
333
+ const ListWrapper = styled.div`
198
334
  border: 1px solid #e2e2e2;
199
- border-radius: 3px;
200
-
201
- ${TextButton} {
202
- margin-left: 0;
203
- text-decoration: underline;
204
- &:hover {
205
- text-decoration: none;
206
- }
207
- display: block;
208
- max-width: 100%;
209
- overflow: hidden;
210
- color: #353535;
211
- text-overflow: ellipsis;
335
+ border-top: none;
336
+ max-height: 21vh;
337
+ min-height: 100px;
338
+ overflow-y: auto;
339
+ padding: 12px;
340
+ `
341
+
342
+ const ToggleButton = styled(IconButton)`
343
+ height: 24px;
344
+ &&:not([disabled]):focus-visible {
345
+ outline: 2px solid ${(props) => props.theme.colors.outline.focus};
346
+ outline-offset: 2px;
347
+ }
348
+ svg {
349
+ width: 12px;
350
+ height: 7px;
212
351
  }
213
352
  `
214
353
 
215
- const Actions = styled.footer`
216
- text-align: right;
217
- padding-top: 1rem;
354
+ const SecondaryHeading = styled.div`
355
+ font-size: 14px;
356
+ font-style: normal;
357
+ font-weight: 700;
358
+ line-height: 24px;
359
+ color: #6e6e6e;
360
+ user-select: none;
218
361
  `
219
- const ScrolableItems = styled.div`
220
- max-height: 16vh;
221
- min-height: 100px;
222
- overflow-y: auto;
362
+
363
+ const SecondaryBoldHeading = styled(SecondaryHeading)`
364
+ font-weight: 700;
365
+ color: #6e6e6e;
366
+ `
367
+
368
+ const DarkSecondaryBoldHeading = styled(SecondaryBoldHeading)`
369
+ display: flex;
370
+ padding: 0 4px;
371
+ margin-bottom: 10px;
372
+ border-radius: 4px;
373
+ color: #353535;
374
+ background: #f2f2f2;
375
+ `
376
+
377
+ const ItemIcon = styled.span`
378
+ display: flex;
379
+ padding-right: 8px;
380
+ svg {
381
+ align-self: center;
382
+ }
383
+ `
384
+
385
+ const ReferencesList = styled.ul`
386
+ display: flex;
387
+ gap: 10px;
388
+ flex-direction: column;
389
+ padding: 0;
390
+ margin: 0;
391
+ list-style: none;
392
+ `
393
+
394
+ const ListItem = styled.li`
395
+ display: flex;
396
+ justify-content: space-between;
397
+ align-items: center;
398
+ border-bottom: solid 1px #e2e2e2;
399
+ &:last-child {
400
+ border-bottom: none;
401
+ padding: 0;
402
+ }
403
+ `
404
+
405
+ const ScrollButton = styled(TertiaryButton)`
406
+ margin: 0;
407
+ padding: 4px;
408
+ font-size: 14px;
409
+ line-height: 24px;
410
+ text-decoration-line: underline;
411
+ `
412
+
413
+ const XRefTextContainer = styled.div`
414
+ display: flex;
415
+ color: #6e6e6e;
416
+ font-size: 12px;
417
+ font-style: normal;
418
+ font-weight: 400;
419
+ line-height: 16px;
420
+ `
421
+
422
+ const XRefLabel = styled.span`
423
+ border-radius: 4px;
424
+ border: 1px solid #fe8f1f;
425
+ background: #fff9e5;
426
+ mix-blend-mode: darken;
427
+ `
428
+
429
+ const XRefAdjacentText = styled.span<{ $direction: 'rtl' | 'ltr' }>`
430
+ display: block;
431
+ max-width: 150px;
432
+ white-space: nowrap;
433
+ overflow: hidden;
434
+ text-overflow: ellipsis;
435
+ direction: ${(props) => props.$direction};
436
+ `
437
+
438
+ const CaptionTitle = styled(XRefAdjacentText)`
439
+ max-width: 120px;
440
+ margin-left: 4px;
441
+ `
442
+
443
+ const SelectorContainer = styled.div`
444
+ display: flex;
445
+ gap: 24px;
446
+ padding: 8px 12px 4px 12px;
447
+ margin: 24px 0;
448
+ border-radius: 4px;
449
+ border: 1px solid #fe8f1f;
450
+ background: #fff9e5;
451
+ `
452
+
453
+ const ArrowDownIcon = styled(ArrowUpIcon)`
454
+ transform: rotate(180deg);
223
455
  `
@@ -19,12 +19,16 @@ import { ResolvedPos } from 'prosemirror-model'
19
19
 
20
20
  import { getEditorProps } from '../../plugins/editor-props'
21
21
  import ReactSubView from '../../views/ReactSubView'
22
- import { CrossRefWarningModal, XrefGroup } from './CrossRefWarningModal'
22
+ import {
23
+ CrossRefWarningModal,
24
+ DeleteOption,
25
+ XrefGroup,
26
+ } from './CrossRefWarningModal'
23
27
 
24
28
  export const openCrossRefWarningModal = (
25
29
  view: ManuscriptEditorView,
26
30
  xrefGroups: XrefGroup[],
27
- onConfirm: () => void,
31
+ onConfirm: (deleteOption: DeleteOption) => void,
28
32
  onClose: () => void,
29
33
  selectAndScrollTo: ($pos: ResolvedPos) => void
30
34
  ): HTMLDivElement => {
@@ -32,8 +36,14 @@ export const openCrossRefWarningModal = (
32
36
  const props = getEditorProps(state)
33
37
  const componentProps = {
34
38
  xrefs: xrefGroups,
35
- onConfirm,
36
- onClose,
39
+ onConfirm: (deleteOption: DeleteOption) => {
40
+ dialog.unmount()
41
+ onConfirm(deleteOption)
42
+ },
43
+ onClose: () => {
44
+ dialog.unmount()
45
+ onClose()
46
+ },
37
47
  selectAndScrollTo,
38
48
  }
39
49
 
@@ -16,15 +16,7 @@
16
16
 
17
17
  import {
18
18
  AddAuthorIcon,
19
- ButtonGroup,
20
- Category,
21
- DeleteIcon,
22
- Dialog,
23
- IconButton,
24
- LinkIcon,
25
19
  OptionType,
26
- PrimaryButton,
27
- SecondaryButton,
28
20
  SelectField,
29
21
  Label,
30
22
  FormRow,
@@ -42,9 +34,7 @@ import { shouldRenderField } from '../../../lib/utils'
42
34
  import { ChangeHandlingForm } from '../../ChangeHandlingForm'
43
35
  import { PersonDropDown } from './PersonDropDown'
44
36
  import {
45
- Actions,
46
37
  Button,
47
- DeleteButton,
48
38
  FormFields,
49
39
  ReferenceTextArea,
50
40
  ReferenceTextField,
@@ -58,22 +48,33 @@ const bibliographyItemTypeOptions: OptionType[] = bibliographyItemTypes.map(
58
48
  )
59
49
  export interface ReferenceFormActions {
60
50
  reset: () => void
51
+ submit: () => void
52
+ isDirty: () => boolean
53
+ }
54
+
55
+ export const validateReference = (values: BibliographyItemAttrs) => {
56
+ const errors: Partial<BibliographyItemAttrs> = {}
57
+
58
+ if (values.type === 'literal') {
59
+ if (!values.literal?.trim()) {
60
+ errors.literal = 'Literal is required for unstructured references'
61
+ }
62
+ } else {
63
+ if (!values.title?.trim()) {
64
+ errors.title = 'Title is required'
65
+ }
66
+ }
67
+ return errors
61
68
  }
62
69
 
63
70
  export const ReferenceForm: React.FC<{
64
71
  values: BibliographyItemAttrs
65
- showDelete: boolean
66
72
  onChange: (values: BibliographyItemAttrs) => void
67
- onCancel: () => void
68
- onDelete: () => void
69
73
  onSave: (values: BibliographyItemAttrs) => void
70
74
  actionsRef?: MutableRefObject<ReferenceFormActions | undefined>
71
75
  }> = ({
72
76
  values,
73
- showDelete,
74
77
  onChange,
75
- onDelete,
76
- onCancel,
77
78
  onSave,
78
79
  actionsRef,
79
80
  }) => {
@@ -88,28 +89,15 @@ export const ReferenceForm: React.FC<{
88
89
  }
89
90
  }, [values])
90
91
 
91
- const [showDeleteDialog, setShowDeleteDialog] = useState(false)
92
-
93
- const validateReference = (values: BibliographyItemAttrs) => {
94
- const errors: Partial<BibliographyItemAttrs> = {}
95
-
96
- if (values.type === 'literal') {
97
- if (!values.literal?.trim()) {
98
- errors.literal = 'Literal is required for unstructured references'
99
- }
100
- } else {
101
- if (!values.title?.trim()) {
102
- errors.title = 'Title is required'
103
- }
104
- }
105
- return errors
106
- }
107
-
108
92
  if (actionsRef && !actionsRef.current) {
109
93
  actionsRef.current = {
110
94
  reset: () => {
111
95
  formRef.current?.resetForm()
112
96
  },
97
+ submit: () => {
98
+ formRef.current?.submitForm()
99
+ },
100
+ isDirty: () => formRef.current?.dirty ?? false,
113
101
  }
114
102
  }
115
103
 
@@ -124,55 +112,6 @@ export const ReferenceForm: React.FC<{
124
112
  {(formik) => {
125
113
  return (
126
114
  <ChangeHandlingForm onChange={onChange}>
127
- <Dialog
128
- isOpen={showDeleteDialog}
129
- category={Category.confirmation}
130
- header="Delete Reference"
131
- message="Are you sure you want to delete this reference from the list?"
132
- actions={{
133
- secondary: {
134
- action: () => {
135
- onDelete()
136
- setShowDeleteDialog(false)
137
- },
138
- title: 'Delete',
139
- },
140
- primary: {
141
- action: () => setShowDeleteDialog(false),
142
- title: 'Cancel',
143
- },
144
- }}
145
- />
146
- <Actions>
147
- <ButtonGroup>
148
- <IconButton
149
- as="a"
150
- href={`https://doi.org/${formik.values.DOI}`}
151
- target={'_blank'}
152
- >
153
- <LinkIcon />
154
- </IconButton>
155
- <DeleteButton
156
- $defaultColor
157
- disabled={!showDelete}
158
- data-tooltip-content="Unable to delete because the item is used in the document"
159
- data-tooltip-hidden={showDelete}
160
- onClick={() => setShowDeleteDialog(true)}
161
- >
162
- <DeleteIcon />
163
- </DeleteButton>
164
- </ButtonGroup>
165
- <ButtonGroup>
166
- <SecondaryButton onClick={onCancel}>Cancel</SecondaryButton>
167
- <PrimaryButton
168
- type="submit"
169
- disabled={!formik.isValid || !formik.dirty}
170
- >
171
- Save
172
- </PrimaryButton>
173
- </ButtonGroup>
174
- </Actions>
175
-
176
115
  <FormFields ref={fieldsRef}>
177
116
  <FormRow>
178
117
  <Label htmlFor={'citation-item-type'}>Type</Label>