@manuscripts/body-editor 3.13.10 → 3.13.12

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 (69) hide show
  1. package/dist/cjs/commands.js +21 -2
  2. package/dist/cjs/components/views/FigureDropdown.js +8 -0
  3. package/dist/cjs/configs/editor-views.js +6 -0
  4. package/dist/cjs/icons.js +2 -1
  5. package/dist/cjs/lib/files.js +6 -1
  6. package/dist/cjs/menus.js +8 -0
  7. package/dist/cjs/node-type-icons.js +4 -0
  8. package/dist/cjs/plugins/accessibility_element.js +2 -1
  9. package/dist/cjs/versions.js +1 -1
  10. package/dist/cjs/views/accessibility_element.js +7 -1
  11. package/dist/cjs/views/affiliations.js +5 -1
  12. package/dist/cjs/views/caption.js +0 -1
  13. package/dist/cjs/views/caption_title.js +0 -1
  14. package/dist/cjs/views/headshot_element.js +74 -0
  15. package/dist/cjs/views/headshot_element_editable.js +20 -0
  16. package/dist/cjs/views/headshot_grid.js +82 -0
  17. package/dist/cjs/views/headshot_grid_editable.js +21 -0
  18. package/dist/cjs/views/headshot_image_editable.js +79 -0
  19. package/dist/cjs/views/supplement.js +16 -1
  20. package/dist/es/commands.js +18 -0
  21. package/dist/es/components/views/FigureDropdown.js +8 -0
  22. package/dist/es/configs/editor-views.js +6 -0
  23. package/dist/es/icons.js +2 -1
  24. package/dist/es/lib/files.js +6 -1
  25. package/dist/es/menus.js +9 -1
  26. package/dist/es/node-type-icons.js +5 -1
  27. package/dist/es/plugins/accessibility_element.js +2 -1
  28. package/dist/es/versions.js +1 -1
  29. package/dist/es/views/accessibility_element.js +7 -1
  30. package/dist/es/views/affiliations.js +5 -1
  31. package/dist/es/views/caption.js +0 -1
  32. package/dist/es/views/caption_title.js +0 -1
  33. package/dist/es/views/headshot_element.js +70 -0
  34. package/dist/es/views/headshot_element_editable.js +18 -0
  35. package/dist/es/views/headshot_grid.js +75 -0
  36. package/dist/es/views/headshot_grid_editable.js +19 -0
  37. package/dist/es/views/headshot_image_editable.js +75 -0
  38. package/dist/es/views/supplement.js +16 -1
  39. package/dist/types/commands.d.ts +1 -0
  40. package/dist/types/icons.d.ts +1 -0
  41. package/dist/types/versions.d.ts +1 -1
  42. package/dist/types/views/headshot_element.d.ts +29 -0
  43. package/dist/types/views/headshot_element_editable.d.ts +18 -0
  44. package/dist/types/views/headshot_grid.d.ts +28 -0
  45. package/dist/types/views/headshot_grid_editable.d.ts +44 -0
  46. package/dist/types/views/headshot_image_editable.d.ts +28 -0
  47. package/dist/types/views/supplement.d.ts +1 -0
  48. package/package.json +2 -2
  49. package/src/commands.ts +27 -0
  50. package/src/components/views/FigureDropdown.tsx +11 -0
  51. package/src/configs/editor-views.ts +6 -0
  52. package/src/icons.ts +2 -0
  53. package/src/lib/files.ts +7 -1
  54. package/src/menus.tsx +10 -0
  55. package/src/node-type-icons.tsx +12 -0
  56. package/src/plugins/accessibility_element.ts +4 -1
  57. package/src/versions.ts +1 -1
  58. package/src/views/accessibility_element.ts +7 -1
  59. package/src/views/affiliations.ts +6 -1
  60. package/src/views/caption.ts +0 -1
  61. package/src/views/caption_title.ts +0 -1
  62. package/src/views/headshot_element.ts +98 -0
  63. package/src/views/headshot_element_editable.ts +20 -0
  64. package/src/views/headshot_grid.ts +98 -0
  65. package/src/views/headshot_grid_editable.ts +21 -0
  66. package/src/views/headshot_image_editable.ts +93 -0
  67. package/src/views/supplement.ts +15 -2
  68. package/styles/AdvancedEditor.css +169 -1
  69. package/styles/Editor.css +1 -1
package/src/commands.ts CHANGED
@@ -925,6 +925,33 @@ export const insertBoxElement = (
925
925
  return true
926
926
  }
927
927
 
928
+ export const insertHeadshotGrid = (
929
+ state: ManuscriptEditorState,
930
+ dispatch?: Dispatch
931
+ ) => {
932
+ const selection = state.selection
933
+ const { nodes } = schema
934
+
935
+ const isBody = hasParentNodeOfType(nodes.body)(selection)
936
+ if (!isBody) {
937
+ return false
938
+ }
939
+
940
+ const position = findBlockInsertPosition(state)
941
+
942
+ if (position && dispatch) {
943
+ const headshotElement =
944
+ schema.nodes.headshot_element.createAndFill() as ManuscriptNode
945
+ const node = nodes.headshot_grid.create({}, headshotElement)
946
+ const tr = state.tr.insert(position, node)
947
+ const headshotNamePosition = position + 2
948
+ tr.setSelection(TextSelection.create(tr.doc, headshotNamePosition))
949
+ dispatch(tr.scrollIntoView())
950
+ }
951
+
952
+ return true
953
+ }
954
+
928
955
  export const insertSection =
929
956
  (subsection = false) =>
930
957
  (state: ManuscriptEditorState, dispatch?: Dispatch, view?: EditorView) => {
@@ -184,6 +184,17 @@ export const FigureOptions: React.FC<WrappedProps> = ({
184
184
 
185
185
  useDropdownKeyboardNav(isOpen, dropdownRef, toggleOpen)
186
186
 
187
+ const hasAnyActions =
188
+ !!showDownload ||
189
+ !!showUpload ||
190
+ !!showDetach ||
191
+ !!showReplace ||
192
+ showDelete()
193
+
194
+ if (!hasAnyActions) {
195
+ return null
196
+ }
197
+
187
198
  const isEmbedMode = !!onReplaceEmbed
188
199
 
189
200
  const groupFiles = memoGroupFiles()
@@ -45,6 +45,9 @@ import footnote from '../views/footnote'
45
45
  import footnotesElement from '../views/footnotes_element'
46
46
  import generalTableFootnote from '../views/general_table_footnote'
47
47
  import heroImage from '../views/hero_image_editable'
48
+ import headshotGrid from '../views/headshot_grid_editable'
49
+ import headshotElement from '../views/headshot_element'
50
+ import headshotImage from '../views/headshot_image_editable'
48
51
  import imageElement from '../views/image_element_editable'
49
52
  import inlineEquation from '../views/inline_equation_editable'
50
53
  import inlineFootnote from '../views/inline_footnote_editable'
@@ -130,6 +133,9 @@ export default (
130
133
  long_desc: accessibilityElement(props, dispatch),
131
134
  alt_text: accessibilityElement(props, dispatch),
132
135
  hero_image: heroImage(props, dispatch),
136
+ headshot_grid: headshotGrid(props, dispatch),
137
+ headshot_element: headshotElement(props, dispatch),
138
+ headshot_image: headshotImage(props, dispatch),
133
139
  abstracts: abstracts(props),
134
140
  trans_abstract: transAbstract(props),
135
141
  trans_graphical_abstract: transGraphicalAbstract(props),
package/src/icons.ts CHANGED
@@ -18,6 +18,7 @@ import {
18
18
  AlertIcon,
19
19
  ArrowDownCircleIcon,
20
20
  ArrowUpIcon,
21
+ CameraIcon,
21
22
  DeleteIcon,
22
23
  DraggableIcon,
23
24
  EditIcon,
@@ -44,6 +45,7 @@ export const addAuthorIcon = renderIcon(AddAuthorIcon)
44
45
  export const arrowDown = renderIcon(ArrowDownCircleIcon)
45
46
  export const arrowUp = renderIcon(ArrowUpIcon)
46
47
  export const alertIcon = renderIcon(AlertIcon)
48
+ export const cameraIcon = renderIcon(CameraIcon)
47
49
  export const deleteIcon = renderIcon(DeleteIcon)
48
50
  export const editIcon = renderIcon(EditIcon)
49
51
  export const sectionCategoryIcon = renderIcon(SectionCategoryIcon)
package/src/lib/files.ts CHANGED
@@ -69,6 +69,7 @@ const figureTypes = [
69
69
  schema.nodes.figure_element,
70
70
  schema.nodes.image_element,
71
71
  schema.nodes.hero_image,
72
+ schema.nodes.headshot_grid,
72
73
  schema.nodes.embed,
73
74
  ]
74
75
 
@@ -119,7 +120,12 @@ export const groupFiles = (
119
120
  })
120
121
  }
121
122
  } else {
122
- for (const figure of findChildrenByType(node, schema.nodes.figure)) {
123
+ const descend = node.type === schema.nodes.headshot_grid
124
+ const figureNodeType =
125
+ node.type === schema.nodes.headshot_grid
126
+ ? schema.nodes.headshot_image
127
+ : schema.nodes.figure
128
+ for (const figure of findChildrenByType(node, figureNodeType, descend)) {
123
129
  if (isHidden(figure.node)) {
124
130
  continue
125
131
  }
package/src/menus.tsx CHANGED
@@ -40,6 +40,7 @@ import {
40
40
  insertCrossReference,
41
41
  insertEmbed,
42
42
  insertGraphicalAbstract,
43
+ insertHeadshotGrid,
43
44
  insertHeroImage,
44
45
  insertInlineCitation,
45
46
  insertInlineEquation,
@@ -333,6 +334,15 @@ export const getEditorMenus = (
333
334
  run: doCommand(insertBoxElement),
334
335
  isHidden: !templateAllows(state, schema.nodes.box_element),
335
336
  },
337
+ {
338
+ id: 'insert-headshot-grid',
339
+ label: 'Headshot Panel',
340
+ isEnabled:
341
+ isEditAllowed(state) &&
342
+ isCommandValid(canInsert(schema.nodes.headshot_grid)),
343
+ run: doCommand(insertHeadshotGrid),
344
+ isHidden: !templateAllows(state, schema.nodes.headshot_grid),
345
+ },
336
346
  {
337
347
  role: 'separator',
338
348
  },
@@ -15,9 +15,11 @@
15
15
  */
16
16
 
17
17
  import {
18
+ AvatarIcon,
18
19
  FileDocumentIcon,
19
20
  SupplementsIcon,
20
21
  FileImageIcon,
22
+ FileHeadshotGridIcon,
21
23
  OutlineBlockQuoteIcon,
22
24
  OutlineEmbedIcon,
23
25
  OutlineEquationIcon,
@@ -36,6 +38,14 @@ import React from 'react'
36
38
 
37
39
  const { nodes } = schema
38
40
 
41
+ const OutlineHeadshotGridIcon: React.FC = () => (
42
+ <FileHeadshotGridIcon width="12" height="12" />
43
+ )
44
+
45
+ const OutlineHeadshotElementIcon: React.FC = () => (
46
+ <AvatarIcon width="16" height="16" />
47
+ )
48
+
39
49
  const OutlineImageIcon: React.FC = () => (
40
50
  <FileImageIcon width="11" height="14" />
41
51
  )
@@ -67,6 +77,8 @@ const icons: Map<
67
77
  [nodes.hero_image, OutlineImageIcon],
68
78
  [nodes.supplements, OutlineSupplementsIcon],
69
79
  [nodes.attachments, OutlineMainDocumentIcon],
80
+ [nodes.headshot_grid, OutlineHeadshotGridIcon],
81
+ [nodes.headshot_element, OutlineHeadshotElementIcon],
70
82
  ])
71
83
 
72
84
  export const nodeTypeIcon = (nodeType: NodeType, listType?: string) => {
@@ -71,7 +71,10 @@ const isSelectionWithin = (
71
71
  const buildExpandButtonDecorations = (doc: ManuscriptNode) => {
72
72
  const decorations: Decoration[] = []
73
73
  doc.descendants((node, pos) => {
74
- if (node.type.spec.content?.includes('alt_text')) {
74
+ if (
75
+ node.type !== schema.nodes.headshot_element &&
76
+ node.type.spec.content?.includes('alt_text')
77
+ ) {
75
78
  decorations.push(
76
79
  Decoration.widget(
77
80
  pos + node.nodeSize - 1,
package/src/versions.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  //THIS FILE WAS GENERATED BY versions.mjs
2
- export const VERSION = '3.13.10';
2
+ export const VERSION = '3.13.12';
3
3
  export const MATHJAX_VERSION = '3.2.2';
@@ -19,11 +19,18 @@ import { TextSelection } from 'prosemirror-state'
19
19
  import { createKeyboardInteraction } from '../lib/navigation-utils'
20
20
  import BlockView from './block_view'
21
21
  import { createNodeView } from './creators'
22
+ import empty from './empty'
22
23
  export class AccessibilityElementView extends BlockView<LongDescNode> {
23
24
  public contentDOM: HTMLElement
24
25
  private removeKeydownListener?: () => void
25
26
 
26
27
  public initialise() {
28
+ const $pos = this.view.state.doc.resolve(this.getPos())
29
+ if ($pos.parent.type === schema.nodes.headshot_element) {
30
+ const { dom } = empty('accessibility_element')()
31
+ this.dom = dom
32
+ return
33
+ }
27
34
  this.createDOM()
28
35
  this.createElement()
29
36
  this.updateContents()
@@ -45,7 +52,6 @@ export class AccessibilityElementView extends BlockView<LongDescNode> {
45
52
  public createElement() {
46
53
  super.createElement()
47
54
  this.contentDOM.className = 'accessibility_element_input'
48
- this.contentDOM.setAttribute('contenteditable', 'true')
49
55
 
50
56
  this.contentDOM.tabIndex = this.node.type === schema.nodes.alt_text ? 0 : -1
51
57
 
@@ -52,7 +52,6 @@ export class AffiliationsView extends BlockView<Trackable<AffiliationNode>> {
52
52
  this.container = document.createElement('div')
53
53
  this.container.classList.add('affiliations', 'block')
54
54
  this.container.contentEditable = 'false'
55
- this.container.addEventListener('click', this.handleClick)
56
55
  this.dom.setAttribute('contenteditable', 'false')
57
56
  this.dom.appendChild(this.container)
58
57
  }
@@ -75,6 +74,12 @@ export class AffiliationsView extends BlockView<Trackable<AffiliationNode>> {
75
74
  }
76
75
 
77
76
  private buildAffiliations(affs: PluginState) {
77
+ const can = this.props.getCapabilities()
78
+ this.container.removeEventListener('click', this.handleClick)
79
+ if (can.editMetadata) {
80
+ this.container.addEventListener('click', this.handleClick)
81
+ }
82
+
78
83
  const elements = []
79
84
  for (const affiliation of affs.affiliations) {
80
85
  const index = affs.indexedAffiliationIds.get(affiliation.id)
@@ -29,7 +29,6 @@ export class CaptionView
29
29
  protected createDOM() {
30
30
  this.dom = document.createElement('caption')
31
31
  this.dom.className = 'caption-description placeholder'
32
- this.dom.contentEditable = 'true'
33
32
  this.contentDOM = this.dom
34
33
  }
35
34
  }
@@ -26,7 +26,6 @@ export class CaptionTitleView extends BaseNodeView<CaptionTitleNode> {
26
26
  protected createDOM() {
27
27
  this.dom = document.createElement('label')
28
28
  this.dom.className = 'caption-title placeholder'
29
- this.dom.contentEditable = 'true'
30
29
  this.contentDOM = this.dom
31
30
  }
32
31
  }
@@ -0,0 +1,98 @@
1
+ /*!
2
+ * © 2026 Atypon Systems LLC
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ import { HeadshotElementNode } from '@manuscripts/transform'
18
+ import { TextSelection } from 'prosemirror-state'
19
+
20
+ import { plusIcon } from '../icons'
21
+ import { handleEnterKey } from '../lib/navigation-utils'
22
+ import { Trackable } from '../types'
23
+ import { BaseNodeView } from './base_node_view'
24
+ import { createNodeView } from './creators'
25
+
26
+ export class HeadshotElement extends BaseNodeView<
27
+ Trackable<HeadshotElementNode>
28
+ > {
29
+ public elementType = 'div'
30
+ private container: HTMLElement
31
+ private deleteHeadshotButton: HTMLElement
32
+
33
+ public initialise() {
34
+ this.createElement()
35
+ this.updateContents()
36
+ }
37
+
38
+ public createElement = () => {
39
+ this.dom = document.createElement('div')
40
+ this.dom.classList.add('headshot-element')
41
+ this.dom.tabIndex = 0
42
+ this.dom.setAttribute('id', this.node.attrs.id)
43
+
44
+ this.container = document.createElement('div')
45
+ this.container.classList.add('block')
46
+ this.dom.appendChild(this.container)
47
+
48
+ this.contentDOM = document.createElement('div')
49
+ this.contentDOM.classList.add('block-container', 'headshot-element-block')
50
+ this.container.appendChild(this.contentDOM)
51
+
52
+ const can = this.props.getCapabilities()
53
+
54
+ if (can.editArticle) {
55
+ this.deleteHeadshotButton = document.createElement('button')
56
+ this.deleteHeadshotButton.innerHTML = plusIcon
57
+ this.deleteHeadshotButton.classList.add('headshot-remove-button')
58
+ this.deleteHeadshotButton.setAttribute('data-cy', 'headshot-delete')
59
+ this.deleteHeadshotButton.contentEditable = 'false'
60
+ this.deleteHeadshotButton.addEventListener(
61
+ 'click',
62
+ this.handleDeleteHeadshot
63
+ )
64
+ this.deleteHeadshotButton.addEventListener(
65
+ 'keydown',
66
+ handleEnterKey(this.handleDeleteHeadshot)
67
+ )
68
+ this.container.appendChild(this.deleteHeadshotButton)
69
+ }
70
+ }
71
+
72
+ private handleDeleteHeadshot = () => {
73
+ const { state, dispatch } = this.view
74
+ const { tr } = state
75
+ const pos = this.getPos()
76
+
77
+ tr.delete(pos, pos + this.node.nodeSize)
78
+ this.view.focus()
79
+ const $pos = tr.doc.resolve(pos)
80
+ if ($pos.node().nodeSize > 2) {
81
+ // we set selection to the start to prevent it from selecting alt_text & long_desc as we render them for now with empty dom node
82
+ tr.setSelection(
83
+ TextSelection.near(tr.doc.resolve($pos.start()))
84
+ ).scrollIntoView()
85
+ }
86
+ dispatch(tr)
87
+ }
88
+
89
+ public destroy() {
90
+ this.deleteHeadshotButton?.removeEventListener(
91
+ 'click',
92
+ this.handleDeleteHeadshot
93
+ )
94
+ super.destroy()
95
+ }
96
+ }
97
+
98
+ export default createNodeView(HeadshotElement)
@@ -0,0 +1,20 @@
1
+ /*!
2
+ * © 2026 Atypon Systems LLC
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ import { createEditableNodeView } from './creators'
18
+ import { HeadshotElement } from './headshot_element'
19
+
20
+ export default createEditableNodeView(HeadshotElement)
@@ -0,0 +1,98 @@
1
+ /*!
2
+ * © 2026 Atypon Systems LLC
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ import {
18
+ HeadshotElementNode,
19
+ HeadshotGridNode,
20
+ schema,
21
+ } from '@manuscripts/transform'
22
+ import { TextSelection } from 'prosemirror-state'
23
+
24
+ import { addAuthorIcon } from '../icons'
25
+ import {
26
+ createKeyboardInteraction,
27
+ handleEnterKey,
28
+ } from '../lib/navigation-utils'
29
+ import BlockView from './block_view'
30
+ import { createNodeView } from './creators'
31
+
32
+ export class HeadshotGridView extends BlockView<HeadshotGridNode> {
33
+ public elementType = 'div'
34
+ private container: HTMLElement
35
+ private addHeadshotButton: HTMLElement
36
+ private removeKeydownListener?: () => void
37
+
38
+ public createElement = () => {
39
+ this.container = document.createElement('div')
40
+ this.container.classList.add('block', 'headshot-grid-container')
41
+ this.dom.appendChild(this.container)
42
+
43
+ this.contentDOM = document.createElement('div')
44
+ this.contentDOM.classList.add('headshot-grid-block')
45
+ this.contentDOM.setAttribute('id', this.node.attrs.id)
46
+ this.removeKeydownListener = createKeyboardInteraction({
47
+ container: this.contentDOM,
48
+ navigation: {
49
+ arrowKeys: { forward: 'ArrowDown', backward: 'ArrowUp' },
50
+ getItems: () => [...(this.contentDOM?.children || [])] as HTMLElement[],
51
+ },
52
+ })
53
+ this.container.appendChild(this.contentDOM)
54
+
55
+ const can = this.props.getCapabilities()
56
+
57
+ if (can.editArticle) {
58
+ this.addHeadshotButton = document.createElement('button')
59
+ this.addHeadshotButton.classList.add('add-headshot-element-button')
60
+ this.addHeadshotButton.setAttribute('data-cy', 'headshot-add')
61
+ this.addHeadshotButton.contentEditable = 'false'
62
+ this.addHeadshotButton.innerHTML = addAuthorIcon
63
+ const buttonText = document.createElement('span')
64
+ buttonText.classList.add('add-headshot-element-text')
65
+ buttonText.innerText = 'Add Headshot'
66
+ this.addHeadshotButton.appendChild(buttonText)
67
+ this.addHeadshotButton.addEventListener('click', this.handleAddHeadshot)
68
+ this.addHeadshotButton.addEventListener(
69
+ 'keydown',
70
+ handleEnterKey(this.handleAddHeadshot)
71
+ )
72
+ this.container.appendChild(this.addHeadshotButton)
73
+ }
74
+
75
+ this.dom.appendChild(this.container)
76
+ }
77
+
78
+ private handleAddHeadshot = () => {
79
+ const { state, dispatch } = this.view
80
+ const { tr } = state
81
+
82
+ const headshotElementNode =
83
+ schema.nodes.headshot_element.createAndFill() as HeadshotElementNode
84
+ const insertPos = this.getPos() + 1
85
+ tr.insert(insertPos, headshotElementNode)
86
+ this.view.focus()
87
+ tr.setSelection(TextSelection.create(tr.doc, insertPos))
88
+ dispatch(tr.scrollIntoView())
89
+ }
90
+
91
+ public destroy() {
92
+ this.addHeadshotButton?.removeEventListener('click', this.handleAddHeadshot)
93
+ this.removeKeydownListener?.()
94
+ super.destroy()
95
+ }
96
+ }
97
+
98
+ export default createNodeView(HeadshotGridView)
@@ -0,0 +1,21 @@
1
+ /*!
2
+ * © 2026 Atypon Systems LLC
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ import { createEditableNodeView } from './creators'
18
+ import { EditableBlock } from './editable_block'
19
+ import { HeadshotGridView } from './headshot_grid'
20
+
21
+ export default createEditableNodeView(EditableBlock(HeadshotGridView))
@@ -0,0 +1,93 @@
1
+ /*!
2
+ * © 2026 Atypon Systems LLC
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ import {
18
+ addTrackChangesAttributes,
19
+ addTrackChangesClassNames,
20
+ } from '@manuscripts/track-changes-plugin'
21
+
22
+ import { cameraIcon, plusIcon } from '../icons'
23
+ import { handleEnterKey } from '../lib/navigation-utils'
24
+ import { createEditableNodeView } from './creators'
25
+ import { FigureEditableView } from './figure_editable'
26
+
27
+ export class HeadshotImageEditableView extends FigureEditableView {
28
+ private detachImageButton: HTMLButtonElement
29
+
30
+ public ignoreMutation = () => true
31
+
32
+ override createDOM() {
33
+ this.dom = document.createElement('figure')
34
+ this.container = document.createElement('div')
35
+ this.container.className = 'headshot-figure'
36
+ this.container.contentEditable = 'false'
37
+ this.dom.appendChild(this.container)
38
+ }
39
+
40
+ override addTools() {
41
+ const src = this.node.attrs.src
42
+ const files = this.props.getFiles()
43
+ const file = src && files.filter((f) => f.id === src)[0]
44
+ const link = file && this.props.fileManagement.previewLink(file)
45
+ const can = this.props.getCapabilities()
46
+ if (can.detachFile && link) {
47
+ this.detachImageButton = document.createElement('button')
48
+ this.detachImageButton.innerHTML = plusIcon
49
+ this.detachImageButton.classList.add('headshot-remove-button')
50
+ this.detachImageButton.setAttribute('data-cy', 'headshot-image-detach')
51
+ this.detachImageButton.contentEditable = 'false'
52
+ this.detachImageButton.addEventListener('click', this.handleDetachImage)
53
+ this.detachImageButton.addEventListener(
54
+ 'keydown',
55
+ handleEnterKey(this.handleDetachImage)
56
+ )
57
+ this.container.appendChild(this.detachImageButton)
58
+ }
59
+ }
60
+
61
+ public override updateContents() {
62
+ super.updateContents()
63
+ addTrackChangesAttributes(this.node.attrs, this.dom)
64
+ addTrackChangesClassNames(this.node.attrs, this.dom)
65
+ }
66
+
67
+ protected override createPlaceholder = () => {
68
+ const element = document.createElement('div')
69
+ element.classList.add('figure', 'placeholder')
70
+ element.tabIndex = 0
71
+
72
+ const instructions = document.createElement('div')
73
+ instructions.classList.add('instructions')
74
+ instructions.innerHTML = `${cameraIcon}<div>Upload<br>photo</div>`
75
+
76
+ element.appendChild(instructions)
77
+
78
+ return element
79
+ }
80
+
81
+ private handleDetachImage = () => {
82
+ const { state, dispatch } = this.view
83
+ const pos = this.getPos()
84
+ dispatch(state.tr.delete(pos, pos + this.node.nodeSize))
85
+ }
86
+
87
+ public destroy() {
88
+ this.detachImageButton?.removeEventListener('click', this.handleDetachImage)
89
+ super.destroy()
90
+ }
91
+ }
92
+
93
+ export default createEditableNodeView(HeadshotImageEditableView)
@@ -28,13 +28,12 @@ export class SupplementView extends BaseNodeView<Trackable<SupplementNode>> {
28
28
  private supplementInfoEl: HTMLDivElement
29
29
  private static currentDragSupplementId: string | null = null
30
30
  private dragIcon: HTMLDivElement | undefined
31
-
31
+ private dragAndDropInitialized = false
32
32
  public ignoreMutation = () => true
33
33
 
34
34
  public initialise() {
35
35
  this.createElement()
36
36
  this.updateContents()
37
- this.setupDragAndDrop()
38
37
  }
39
38
 
40
39
  public createElement = () => {
@@ -56,6 +55,20 @@ export class SupplementView extends BaseNodeView<Trackable<SupplementNode>> {
56
55
  public updateContents() {
57
56
  super.updateContents()
58
57
  this.refreshFileInfo()
58
+ const can = this.props.getCapabilities()
59
+ this.dom.draggable = can.editArticle
60
+ if (can.editArticle) {
61
+ if (!this.dragAndDropInitialized) {
62
+ this.setupDragAndDrop()
63
+ this.dragAndDropInitialized = true
64
+ }
65
+ if (!this.dragIcon) {
66
+ this.addDragIcon()
67
+ }
68
+ } else {
69
+ this.dragIcon?.remove()
70
+ this.dragIcon = undefined
71
+ }
59
72
  }
60
73
 
61
74
  private getDropSide(element: Element, clientY: number): 'before' | 'after' {