@manuscripts/body-editor 3.13.14 → 3.13.16

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.
@@ -188,7 +188,7 @@ const AuthorDetailsForm = ({ values, onChange, onSave, actionsRef, isEmailRequir
188
188
  })),
189
189
  react_1.default.createElement(style_guide_1.FormRow, null,
190
190
  react_1.default.createElement(UnsavedLabel_1.UnsavedLabel, { htmlFor: "degrees", showDot: showUnsavedDot('degrees') }, "Qualification"),
191
- react_1.default.createElement(style_guide_1.MultiValueInput, { id: 'degrees', inputType: "text", placeholder: "E.g. Bsc Computer Science", initialValues: Array.isArray(formik.values.degrees) ? formik.values.degrees : [], onChange: (values) => {
191
+ react_1.default.createElement(style_guide_1.MultiValueInput, { id: 'degrees', inputType: "text", placeholder: "E.g. PhD, M.D., etc.", initialValues: Array.isArray(formik.values.degrees) ? formik.values.degrees : [], onChange: (values) => {
192
192
  formik.setFieldValue('degrees', values);
193
193
  } })))));
194
194
  };
@@ -0,0 +1,128 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DragDropManager = void 0;
4
+ const doc_1 = require("./doc");
5
+ const prosemirror_state_1 = require("prosemirror-state");
6
+ class DragDropManager {
7
+ constructor() {
8
+ this.handleDragStart = () => {
9
+ DragDropManager.currentNodeId = this.getContext().node.attrs.id;
10
+ this.element.classList.add('dragging');
11
+ };
12
+ this.handleDragEnd = () => {
13
+ DragDropManager.currentNodeId = null;
14
+ this.element.classList.remove('dragging');
15
+ this.clearDropClasses();
16
+ };
17
+ this.handleDragOver = (e) => {
18
+ const draggedId = DragDropManager.currentNodeId;
19
+ const nodeId = this.getContext().node.attrs.id;
20
+ if (DragDropManager.currentNodeId === null ||
21
+ draggedId === null ||
22
+ draggedId === nodeId) {
23
+ return;
24
+ }
25
+ e.preventDefault();
26
+ e.stopPropagation();
27
+ const movePosition = this.getMovePosition(e);
28
+ if (movePosition) {
29
+ const { noActualMove, side, fromPos, targetPos } = movePosition;
30
+ if (!noActualMove && this.canMoveOutsideParent(fromPos, targetPos)) {
31
+ this.element.classList.add(side === 'before' ? 'drop-target-above' : 'drop-target-below');
32
+ }
33
+ }
34
+ };
35
+ this.handleDragLeave = (e) => {
36
+ if (!this.element.contains(e.relatedTarget)) {
37
+ this.clearDropClasses();
38
+ }
39
+ };
40
+ this.handleDrop = (e) => {
41
+ if (DragDropManager.currentNodeId === null) {
42
+ return;
43
+ }
44
+ e.preventDefault();
45
+ e.stopPropagation();
46
+ const movePosition = this.getMovePosition(e);
47
+ if (movePosition) {
48
+ const { noActualMove, fromPos, targetPos, fromNode } = movePosition;
49
+ if (!noActualMove && this.canMoveOutsideParent(fromPos, targetPos)) {
50
+ this.moveNode(fromNode, fromPos, targetPos);
51
+ }
52
+ }
53
+ this.clearDropClasses();
54
+ };
55
+ }
56
+ setup(config) {
57
+ const { element, restrictToParent = true, getContext, disabled } = config;
58
+ if (disabled) {
59
+ return;
60
+ }
61
+ this.element = element;
62
+ this.element.draggable = true;
63
+ this.restrictToParent = restrictToParent;
64
+ this.getContext = getContext;
65
+ const abortController = new AbortController();
66
+ const signal = abortController.signal;
67
+ element.addEventListener('dragstart', this.handleDragStart, { signal });
68
+ element.addEventListener('dragend', this.handleDragEnd, { signal });
69
+ element.addEventListener('dragover', this.handleDragOver, { signal });
70
+ element.addEventListener('dragleave', this.handleDragLeave, { signal });
71
+ element.addEventListener('drop', this.handleDrop, { signal });
72
+ return () => {
73
+ DragDropManager.currentNodeId = null;
74
+ this.clearDropClasses();
75
+ this.element.classList.remove('dragging');
76
+ abortController.abort();
77
+ };
78
+ }
79
+ clearDropClasses() {
80
+ this.element.classList.remove('drop-target-above', 'drop-target-below');
81
+ }
82
+ getMovePosition(e) {
83
+ if (!DragDropManager.currentNodeId) {
84
+ return;
85
+ }
86
+ const { view, node, pos: toPos } = this.getContext();
87
+ const currentView = (0, doc_1.findNodeByID)(view.state.doc, DragDropManager.currentNodeId);
88
+ if (!currentView) {
89
+ return;
90
+ }
91
+ const side = this.getDropSide(this.element, e.clientY);
92
+ const targetPos = side === 'before' ? toPos : toPos + node.nodeSize;
93
+ const { node: fromNode, pos: fromPos } = currentView;
94
+ return {
95
+ noActualMove: targetPos === fromPos || targetPos === fromPos + fromNode.nodeSize,
96
+ side,
97
+ fromNode,
98
+ fromPos,
99
+ targetPos,
100
+ };
101
+ }
102
+ getDropSide(element, clientY) {
103
+ const { top, bottom } = element.getBoundingClientRect();
104
+ const middleY = (top + bottom) / 2;
105
+ return clientY > middleY ? 'after' : 'before';
106
+ }
107
+ moveNode(fromNode, fromPos, targetPos) {
108
+ const view = this.getContext().view;
109
+ const { state } = view;
110
+ const { tr } = state;
111
+ tr.insert(targetPos, fromNode);
112
+ const mappedFrom = tr.mapping.map(fromPos, -1);
113
+ tr.delete(mappedFrom, mappedFrom + fromNode.nodeSize);
114
+ tr.setSelection(prosemirror_state_1.NodeSelection.near(tr.doc.resolve(tr.mapping.map(targetPos, -1))));
115
+ view.dispatch(tr);
116
+ }
117
+ canMoveOutsideParent(from, to) {
118
+ if (!this.restrictToParent) {
119
+ return true;
120
+ }
121
+ const { view } = this.getContext();
122
+ const $fromPos = view.state.doc.resolve(from);
123
+ const $toPos = view.state.doc.resolve(to);
124
+ return $fromPos.sameParent($toPos);
125
+ }
126
+ }
127
+ exports.DragDropManager = DragDropManager;
128
+ DragDropManager.currentNodeId = null;
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MATHJAX_VERSION = exports.VERSION = void 0;
4
- exports.VERSION = '3.13.14';
4
+ exports.VERSION = '3.13.16';
5
5
  exports.MATHJAX_VERSION = '3.2.2';
@@ -21,10 +21,12 @@ const icons_1 = require("../icons");
21
21
  const navigation_utils_1 = require("../lib/navigation-utils");
22
22
  const base_node_view_1 = require("./base_node_view");
23
23
  const creators_1 = require("./creators");
24
+ const drag_drop_manager_1 = require("../lib/drag-drop-manager");
24
25
  class HeadshotElement extends base_node_view_1.BaseNodeView {
25
26
  constructor() {
26
27
  super(...arguments);
27
28
  this.elementType = 'div';
29
+ this.ignoreMutation = () => true;
28
30
  this.createElement = () => {
29
31
  this.dom = document.createElement('div');
30
32
  this.dom.classList.add('headshot-element');
@@ -46,6 +48,12 @@ class HeadshotElement extends base_node_view_1.BaseNodeView {
46
48
  this.deleteHeadshotButton.addEventListener('click', this.handleDeleteHeadshot);
47
49
  this.deleteHeadshotButton.addEventListener('keydown', (0, navigation_utils_1.handleEnterKey)(this.handleDeleteHeadshot));
48
50
  this.container.appendChild(this.deleteHeadshotButton);
51
+ const dragIcon = document.createElement('div');
52
+ dragIcon.className = 'headshot-drag-icon';
53
+ dragIcon.contentEditable = 'false';
54
+ dragIcon.innerHTML = icons_1.draggableIcon;
55
+ dragIcon.draggable = false;
56
+ this.container.appendChild(dragIcon);
49
57
  }
50
58
  };
51
59
  this.handleDeleteHeadshot = () => {
@@ -64,9 +72,23 @@ class HeadshotElement extends base_node_view_1.BaseNodeView {
64
72
  initialise() {
65
73
  this.createElement();
66
74
  this.updateContents();
75
+ this.setupDragAndDrop();
76
+ }
77
+ setupDragAndDrop() {
78
+ const can = this.props.getCapabilities();
79
+ this.cleanupDragDrop = new drag_drop_manager_1.DragDropManager().setup({
80
+ element: this.dom,
81
+ getContext: () => ({
82
+ view: this.view,
83
+ node: this.node,
84
+ pos: this.getPos(),
85
+ }),
86
+ disabled: !can.editArticle,
87
+ });
67
88
  }
68
89
  destroy() {
69
90
  this.deleteHeadshotButton?.removeEventListener('click', this.handleDeleteHeadshot);
91
+ this.cleanupDragDrop?.();
70
92
  super.destroy();
71
93
  }
72
94
  }
@@ -29,6 +29,7 @@ class HeadshotGridView extends block_view_1.default {
29
29
  constructor() {
30
30
  super(...arguments);
31
31
  this.elementType = 'div';
32
+ this.ignoreMutation = () => true;
32
33
  this.createElement = () => {
33
34
  this.container = document.createElement('div');
34
35
  this.container.classList.add('block', 'headshot-grid-container');
@@ -148,7 +148,7 @@ export const AuthorDetailsForm = ({ values, onChange, onSave, actionsRef, isEmai
148
148
  })),
149
149
  React.createElement(FormRow, null,
150
150
  React.createElement(UnsavedLabel, { htmlFor: "degrees", showDot: showUnsavedDot('degrees') }, "Qualification"),
151
- React.createElement(MultiValueInput, { id: 'degrees', inputType: "text", placeholder: "E.g. Bsc Computer Science", initialValues: Array.isArray(formik.values.degrees) ? formik.values.degrees : [], onChange: (values) => {
151
+ React.createElement(MultiValueInput, { id: 'degrees', inputType: "text", placeholder: "E.g. PhD, M.D., etc.", initialValues: Array.isArray(formik.values.degrees) ? formik.values.degrees : [], onChange: (values) => {
152
152
  formik.setFieldValue('degrees', values);
153
153
  } })))));
154
154
  };
@@ -0,0 +1,124 @@
1
+ import { findNodeByID } from './doc';
2
+ import { NodeSelection } from 'prosemirror-state';
3
+ export class DragDropManager {
4
+ constructor() {
5
+ this.handleDragStart = () => {
6
+ DragDropManager.currentNodeId = this.getContext().node.attrs.id;
7
+ this.element.classList.add('dragging');
8
+ };
9
+ this.handleDragEnd = () => {
10
+ DragDropManager.currentNodeId = null;
11
+ this.element.classList.remove('dragging');
12
+ this.clearDropClasses();
13
+ };
14
+ this.handleDragOver = (e) => {
15
+ const draggedId = DragDropManager.currentNodeId;
16
+ const nodeId = this.getContext().node.attrs.id;
17
+ if (DragDropManager.currentNodeId === null ||
18
+ draggedId === null ||
19
+ draggedId === nodeId) {
20
+ return;
21
+ }
22
+ e.preventDefault();
23
+ e.stopPropagation();
24
+ const movePosition = this.getMovePosition(e);
25
+ if (movePosition) {
26
+ const { noActualMove, side, fromPos, targetPos } = movePosition;
27
+ if (!noActualMove && this.canMoveOutsideParent(fromPos, targetPos)) {
28
+ this.element.classList.add(side === 'before' ? 'drop-target-above' : 'drop-target-below');
29
+ }
30
+ }
31
+ };
32
+ this.handleDragLeave = (e) => {
33
+ if (!this.element.contains(e.relatedTarget)) {
34
+ this.clearDropClasses();
35
+ }
36
+ };
37
+ this.handleDrop = (e) => {
38
+ if (DragDropManager.currentNodeId === null) {
39
+ return;
40
+ }
41
+ e.preventDefault();
42
+ e.stopPropagation();
43
+ const movePosition = this.getMovePosition(e);
44
+ if (movePosition) {
45
+ const { noActualMove, fromPos, targetPos, fromNode } = movePosition;
46
+ if (!noActualMove && this.canMoveOutsideParent(fromPos, targetPos)) {
47
+ this.moveNode(fromNode, fromPos, targetPos);
48
+ }
49
+ }
50
+ this.clearDropClasses();
51
+ };
52
+ }
53
+ setup(config) {
54
+ const { element, restrictToParent = true, getContext, disabled } = config;
55
+ if (disabled) {
56
+ return;
57
+ }
58
+ this.element = element;
59
+ this.element.draggable = true;
60
+ this.restrictToParent = restrictToParent;
61
+ this.getContext = getContext;
62
+ const abortController = new AbortController();
63
+ const signal = abortController.signal;
64
+ element.addEventListener('dragstart', this.handleDragStart, { signal });
65
+ element.addEventListener('dragend', this.handleDragEnd, { signal });
66
+ element.addEventListener('dragover', this.handleDragOver, { signal });
67
+ element.addEventListener('dragleave', this.handleDragLeave, { signal });
68
+ element.addEventListener('drop', this.handleDrop, { signal });
69
+ return () => {
70
+ DragDropManager.currentNodeId = null;
71
+ this.clearDropClasses();
72
+ this.element.classList.remove('dragging');
73
+ abortController.abort();
74
+ };
75
+ }
76
+ clearDropClasses() {
77
+ this.element.classList.remove('drop-target-above', 'drop-target-below');
78
+ }
79
+ getMovePosition(e) {
80
+ if (!DragDropManager.currentNodeId) {
81
+ return;
82
+ }
83
+ const { view, node, pos: toPos } = this.getContext();
84
+ const currentView = findNodeByID(view.state.doc, DragDropManager.currentNodeId);
85
+ if (!currentView) {
86
+ return;
87
+ }
88
+ const side = this.getDropSide(this.element, e.clientY);
89
+ const targetPos = side === 'before' ? toPos : toPos + node.nodeSize;
90
+ const { node: fromNode, pos: fromPos } = currentView;
91
+ return {
92
+ noActualMove: targetPos === fromPos || targetPos === fromPos + fromNode.nodeSize,
93
+ side,
94
+ fromNode,
95
+ fromPos,
96
+ targetPos,
97
+ };
98
+ }
99
+ getDropSide(element, clientY) {
100
+ const { top, bottom } = element.getBoundingClientRect();
101
+ const middleY = (top + bottom) / 2;
102
+ return clientY > middleY ? 'after' : 'before';
103
+ }
104
+ moveNode(fromNode, fromPos, targetPos) {
105
+ const view = this.getContext().view;
106
+ const { state } = view;
107
+ const { tr } = state;
108
+ tr.insert(targetPos, fromNode);
109
+ const mappedFrom = tr.mapping.map(fromPos, -1);
110
+ tr.delete(mappedFrom, mappedFrom + fromNode.nodeSize);
111
+ tr.setSelection(NodeSelection.near(tr.doc.resolve(tr.mapping.map(targetPos, -1))));
112
+ view.dispatch(tr);
113
+ }
114
+ canMoveOutsideParent(from, to) {
115
+ if (!this.restrictToParent) {
116
+ return true;
117
+ }
118
+ const { view } = this.getContext();
119
+ const $fromPos = view.state.doc.resolve(from);
120
+ const $toPos = view.state.doc.resolve(to);
121
+ return $fromPos.sameParent($toPos);
122
+ }
123
+ }
124
+ DragDropManager.currentNodeId = null;
@@ -1,2 +1,2 @@
1
- export const VERSION = '3.13.14';
1
+ export const VERSION = '3.13.16';
2
2
  export const MATHJAX_VERSION = '3.2.2';
@@ -14,14 +14,16 @@
14
14
  * limitations under the License.
15
15
  */
16
16
  import { TextSelection } from 'prosemirror-state';
17
- import { plusIcon } from '../icons';
17
+ import { draggableIcon, plusIcon } from '../icons';
18
18
  import { handleEnterKey } from '../lib/navigation-utils';
19
19
  import { BaseNodeView } from './base_node_view';
20
20
  import { createNodeView } from './creators';
21
+ import { DragDropManager } from '../lib/drag-drop-manager';
21
22
  export class HeadshotElement extends BaseNodeView {
22
23
  constructor() {
23
24
  super(...arguments);
24
25
  this.elementType = 'div';
26
+ this.ignoreMutation = () => true;
25
27
  this.createElement = () => {
26
28
  this.dom = document.createElement('div');
27
29
  this.dom.classList.add('headshot-element');
@@ -43,6 +45,12 @@ export class HeadshotElement extends BaseNodeView {
43
45
  this.deleteHeadshotButton.addEventListener('click', this.handleDeleteHeadshot);
44
46
  this.deleteHeadshotButton.addEventListener('keydown', handleEnterKey(this.handleDeleteHeadshot));
45
47
  this.container.appendChild(this.deleteHeadshotButton);
48
+ const dragIcon = document.createElement('div');
49
+ dragIcon.className = 'headshot-drag-icon';
50
+ dragIcon.contentEditable = 'false';
51
+ dragIcon.innerHTML = draggableIcon;
52
+ dragIcon.draggable = false;
53
+ this.container.appendChild(dragIcon);
46
54
  }
47
55
  };
48
56
  this.handleDeleteHeadshot = () => {
@@ -61,9 +69,23 @@ export class HeadshotElement extends BaseNodeView {
61
69
  initialise() {
62
70
  this.createElement();
63
71
  this.updateContents();
72
+ this.setupDragAndDrop();
73
+ }
74
+ setupDragAndDrop() {
75
+ const can = this.props.getCapabilities();
76
+ this.cleanupDragDrop = new DragDropManager().setup({
77
+ element: this.dom,
78
+ getContext: () => ({
79
+ view: this.view,
80
+ node: this.node,
81
+ pos: this.getPos(),
82
+ }),
83
+ disabled: !can.editArticle,
84
+ });
64
85
  }
65
86
  destroy() {
66
87
  this.deleteHeadshotButton?.removeEventListener('click', this.handleDeleteHeadshot);
88
+ this.cleanupDragDrop?.();
67
89
  super.destroy();
68
90
  }
69
91
  }
@@ -23,6 +23,7 @@ export class HeadshotGridView extends BlockView {
23
23
  constructor() {
24
24
  super(...arguments);
25
25
  this.elementType = 'div';
26
+ this.ignoreMutation = () => true;
26
27
  this.createElement = () => {
27
28
  this.container = document.createElement('div');
28
29
  this.container.classList.add('block', 'headshot-grid-container');
@@ -0,0 +1,31 @@
1
+ import { ManuscriptNode } from '@manuscripts/transform';
2
+ import { EditorView } from 'prosemirror-view';
3
+ interface DragDropContext {
4
+ view: EditorView;
5
+ node: ManuscriptNode;
6
+ pos: number;
7
+ }
8
+ interface DragDropConfig {
9
+ element: HTMLElement;
10
+ getContext: () => DragDropContext;
11
+ restrictToParent?: boolean;
12
+ disabled?: boolean;
13
+ }
14
+ export declare class DragDropManager {
15
+ private static currentNodeId;
16
+ private element;
17
+ private restrictToParent;
18
+ private getContext;
19
+ setup(config: DragDropConfig): (() => void) | undefined;
20
+ private handleDragStart;
21
+ private handleDragEnd;
22
+ private handleDragOver;
23
+ private handleDragLeave;
24
+ private handleDrop;
25
+ private clearDropClasses;
26
+ private getMovePosition;
27
+ private getDropSide;
28
+ private moveNode;
29
+ private canMoveOutsideParent;
30
+ }
31
+ export {};
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "3.13.14";
1
+ export declare const VERSION = "3.13.16";
2
2
  export declare const MATHJAX_VERSION = "3.2.2";
@@ -20,9 +20,12 @@ export declare class HeadshotElement extends BaseNodeView<Trackable<HeadshotElem
20
20
  elementType: string;
21
21
  private container;
22
22
  private deleteHeadshotButton;
23
+ private cleanupDragDrop;
24
+ ignoreMutation: () => boolean;
23
25
  initialise(): void;
24
26
  createElement: () => void;
25
27
  private handleDeleteHeadshot;
28
+ private setupDragAndDrop;
26
29
  destroy(): void;
27
30
  }
28
31
  declare const _default: (props: import("../configs/ManuscriptsEditor").EditorProps, dispatch?: import("..").Dispatch) => import("../types").NodeViewCreator<HeadshotElement>;
@@ -20,6 +20,7 @@ export declare class HeadshotGridView extends BlockView<HeadshotGridNode> {
20
20
  private container;
21
21
  private addHeadshotButton;
22
22
  private removeKeydownListener?;
23
+ ignoreMutation: () => boolean;
23
24
  createElement: () => void;
24
25
  private handleAddHeadshot;
25
26
  destroy(): void;
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.13.14",
4
+ "version": "3.13.16",
5
5
  "repository": "github:Atypon-OpenSource/manuscripts-body-editor",
6
6
  "license": "Apache-2.0",
7
7
  "main": "dist/cjs",
@@ -393,7 +393,7 @@ export const AuthorDetailsForm: React.FC<AuthorDetailsFormProps> = ({
393
393
  <MultiValueInput
394
394
  id={'degrees'}
395
395
  inputType="text"
396
- placeholder="E.g. Bsc Computer Science"
396
+ placeholder="E.g. PhD, M.D., etc."
397
397
  initialValues={
398
398
  Array.isArray(formik.values.degrees) ? formik.values.degrees : []
399
399
  }
@@ -0,0 +1,198 @@
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
+ import { ManuscriptNode } from '@manuscripts/transform'
17
+ import { EditorView } from 'prosemirror-view'
18
+
19
+ import { findNodeByID } from './doc'
20
+ import { NodeSelection } from 'prosemirror-state'
21
+
22
+ interface DragDropContext {
23
+ view: EditorView
24
+ node: ManuscriptNode
25
+ pos: number
26
+ }
27
+
28
+ interface DragDropConfig {
29
+ element: HTMLElement
30
+ getContext: () => DragDropContext
31
+ restrictToParent?: boolean
32
+ disabled?: boolean
33
+ }
34
+
35
+ export class DragDropManager {
36
+ private static currentNodeId: string | null = null
37
+
38
+ private element: HTMLElement
39
+ private restrictToParent: boolean
40
+ private getContext: () => DragDropContext
41
+
42
+ setup(config: DragDropConfig) {
43
+ const { element, restrictToParent = true, getContext, disabled } = config
44
+ if (disabled) {
45
+ return
46
+ }
47
+
48
+ this.element = element
49
+ this.element.draggable = true
50
+ this.restrictToParent = restrictToParent
51
+ this.getContext = getContext
52
+
53
+ const abortController = new AbortController()
54
+ const signal = abortController.signal
55
+
56
+ element.addEventListener('dragstart', this.handleDragStart, { signal })
57
+ element.addEventListener('dragend', this.handleDragEnd, { signal })
58
+ element.addEventListener('dragover', this.handleDragOver, { signal })
59
+ element.addEventListener('dragleave', this.handleDragLeave, { signal })
60
+ element.addEventListener('drop', this.handleDrop, { signal })
61
+
62
+ return () => {
63
+ DragDropManager.currentNodeId = null
64
+ this.clearDropClasses()
65
+ this.element.classList.remove('dragging')
66
+ abortController.abort()
67
+ }
68
+ }
69
+
70
+ private handleDragStart = () => {
71
+ DragDropManager.currentNodeId = this.getContext().node.attrs.id
72
+ this.element.classList.add('dragging')
73
+ }
74
+
75
+ private handleDragEnd = () => {
76
+ DragDropManager.currentNodeId = null
77
+ this.element.classList.remove('dragging')
78
+ this.clearDropClasses()
79
+ }
80
+
81
+ private handleDragOver = (e: DragEvent) => {
82
+ const draggedId = DragDropManager.currentNodeId
83
+ const nodeId = this.getContext().node.attrs.id
84
+ if (
85
+ DragDropManager.currentNodeId === null ||
86
+ draggedId === null ||
87
+ draggedId === nodeId
88
+ ) {
89
+ return
90
+ }
91
+
92
+ e.preventDefault()
93
+ e.stopPropagation()
94
+
95
+ const movePosition = this.getMovePosition(e)
96
+ if (movePosition) {
97
+ const { noActualMove, side, fromPos, targetPos } = movePosition
98
+ if (!noActualMove && this.canMoveOutsideParent(fromPos, targetPos)) {
99
+ this.element.classList.add(
100
+ side === 'before' ? 'drop-target-above' : 'drop-target-below'
101
+ )
102
+ }
103
+ }
104
+ }
105
+
106
+ private handleDragLeave = (e: DragEvent) => {
107
+ if (!this.element.contains(e.relatedTarget as Node)) {
108
+ this.clearDropClasses()
109
+ }
110
+ }
111
+
112
+ private handleDrop = (e: DragEvent) => {
113
+ if (DragDropManager.currentNodeId === null) {
114
+ return
115
+ }
116
+
117
+ e.preventDefault()
118
+ e.stopPropagation()
119
+
120
+ const movePosition = this.getMovePosition(e)
121
+ if (movePosition) {
122
+ const { noActualMove, fromPos, targetPos, fromNode } = movePosition
123
+ if (!noActualMove && this.canMoveOutsideParent(fromPos, targetPos)) {
124
+ this.moveNode(fromNode, fromPos, targetPos)
125
+ }
126
+ }
127
+ this.clearDropClasses()
128
+ }
129
+
130
+ private clearDropClasses() {
131
+ this.element.classList.remove('drop-target-above', 'drop-target-below')
132
+ }
133
+
134
+ private getMovePosition(e: DragEvent) {
135
+ if (!DragDropManager.currentNodeId) {
136
+ return
137
+ }
138
+
139
+ const { view, node, pos: toPos } = this.getContext()
140
+ const currentView = findNodeByID(
141
+ view.state.doc,
142
+ DragDropManager.currentNodeId
143
+ )
144
+ if (!currentView) {
145
+ return
146
+ }
147
+
148
+ const side = this.getDropSide(this.element, e.clientY)
149
+ const targetPos = side === 'before' ? toPos : toPos + node.nodeSize
150
+
151
+ const { node: fromNode, pos: fromPos } = currentView
152
+ return {
153
+ // No-move if dropping at the node’s start or end position
154
+ noActualMove:
155
+ targetPos === fromPos || targetPos === fromPos + fromNode.nodeSize,
156
+ side,
157
+ fromNode,
158
+ fromPos,
159
+ targetPos,
160
+ }
161
+ }
162
+
163
+ private getDropSide(
164
+ element: HTMLElement,
165
+ clientY: number
166
+ ): 'before' | 'after' {
167
+ const { top, bottom } = element.getBoundingClientRect()
168
+ const middleY = (top + bottom) / 2
169
+ return clientY > middleY ? 'after' : 'before'
170
+ }
171
+
172
+ private moveNode(
173
+ fromNode: ManuscriptNode,
174
+ fromPos: number,
175
+ targetPos: number
176
+ ) {
177
+ const view = this.getContext().view
178
+ const { state } = view
179
+ const { tr } = state
180
+ tr.insert(targetPos, fromNode)
181
+ const mappedFrom = tr.mapping.map(fromPos, -1)
182
+ tr.delete(mappedFrom, mappedFrom + fromNode.nodeSize)
183
+ tr.setSelection(
184
+ NodeSelection.near(tr.doc.resolve(tr.mapping.map(targetPos, -1)))
185
+ )
186
+ view.dispatch(tr)
187
+ }
188
+
189
+ private canMoveOutsideParent(from: number, to: number) {
190
+ if (!this.restrictToParent) {
191
+ return true
192
+ }
193
+ const { view } = this.getContext()
194
+ const $fromPos = view.state.doc.resolve(from)
195
+ const $toPos = view.state.doc.resolve(to)
196
+ return $fromPos.sameParent($toPos)
197
+ }
198
+ }
package/src/versions.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  //THIS FILE WAS GENERATED BY versions.mjs
2
- export const VERSION = '3.13.14';
2
+ export const VERSION = '3.13.16';
3
3
  export const MATHJAX_VERSION = '3.2.2';
@@ -17,11 +17,12 @@
17
17
  import { HeadshotElementNode } from '@manuscripts/transform'
18
18
  import { TextSelection } from 'prosemirror-state'
19
19
 
20
- import { plusIcon } from '../icons'
20
+ import { draggableIcon, plusIcon } from '../icons'
21
21
  import { handleEnterKey } from '../lib/navigation-utils'
22
22
  import { Trackable } from '../types'
23
23
  import { BaseNodeView } from './base_node_view'
24
24
  import { createNodeView } from './creators'
25
+ import { DragDropManager } from '../lib/drag-drop-manager'
25
26
 
26
27
  export class HeadshotElement extends BaseNodeView<
27
28
  Trackable<HeadshotElementNode>
@@ -29,10 +30,14 @@ export class HeadshotElement extends BaseNodeView<
29
30
  public elementType = 'div'
30
31
  private container: HTMLElement
31
32
  private deleteHeadshotButton: HTMLElement
33
+ private cleanupDragDrop: (() => void) | undefined
34
+
35
+ public ignoreMutation = () => true
32
36
 
33
37
  public initialise() {
34
38
  this.createElement()
35
39
  this.updateContents()
40
+ this.setupDragAndDrop()
36
41
  }
37
42
 
38
43
  public createElement = () => {
@@ -66,6 +71,13 @@ export class HeadshotElement extends BaseNodeView<
66
71
  handleEnterKey(this.handleDeleteHeadshot)
67
72
  )
68
73
  this.container.appendChild(this.deleteHeadshotButton)
74
+
75
+ const dragIcon = document.createElement('div')
76
+ dragIcon.className = 'headshot-drag-icon'
77
+ dragIcon.contentEditable = 'false'
78
+ dragIcon.innerHTML = draggableIcon
79
+ dragIcon.draggable = false
80
+ this.container.appendChild(dragIcon)
69
81
  }
70
82
  }
71
83
 
@@ -86,11 +98,25 @@ export class HeadshotElement extends BaseNodeView<
86
98
  dispatch(tr)
87
99
  }
88
100
 
101
+ private setupDragAndDrop() {
102
+ const can = this.props.getCapabilities()
103
+ this.cleanupDragDrop = new DragDropManager().setup({
104
+ element: this.dom,
105
+ getContext: () => ({
106
+ view: this.view,
107
+ node: this.node,
108
+ pos: this.getPos(),
109
+ }),
110
+ disabled: !can.editArticle,
111
+ })
112
+ }
113
+
89
114
  public destroy() {
90
115
  this.deleteHeadshotButton?.removeEventListener(
91
116
  'click',
92
117
  this.handleDeleteHeadshot
93
118
  )
119
+ this.cleanupDragDrop?.()
94
120
  super.destroy()
95
121
  }
96
122
  }
@@ -35,6 +35,8 @@ export class HeadshotGridView extends BlockView<HeadshotGridNode> {
35
35
  private addHeadshotButton: HTMLElement
36
36
  private removeKeydownListener?: () => void
37
37
 
38
+ public ignoreMutation = () => true
39
+
38
40
  public createElement = () => {
39
41
  this.container = document.createElement('div')
40
42
  this.container.classList.add('block', 'headshot-grid-container')
@@ -824,7 +824,7 @@ span.comment-marker {
824
824
  .selected-suggestion:not(.block-container):not(.graphical-abstract):not(
825
825
  .keywords
826
826
  ):not(figure):not(figure .equation):not(.inconsistency-highlight):not(
827
- .supplement-item:is([data-track-op='move'])
827
+ .supplement-item:is([data-track-op='move']), .headshot-element:is([data-track-op='move'])
828
828
  ),
829
829
  .footnote-marker-selected {
830
830
  border-width: 2px 0 2px 0 !important;
@@ -1780,7 +1780,7 @@ th:hover > .table-context-menu-button,
1780
1780
 
1781
1781
  .ProseMirror .headshot-grid-container .headshot-grid-block .headshot-element {
1782
1782
  border-radius: 4px;
1783
- border: 1px solid #E2E2E2 !important;
1783
+ border: 1px solid #E2E2E2;
1784
1784
  background: #FFF;
1785
1785
  position: relative;
1786
1786
  }
@@ -1900,6 +1900,24 @@ th:hover > .table-context-menu-button,
1900
1900
  z-index: 10;
1901
1901
  }
1902
1902
 
1903
+ .ProseMirror .headshot-drag-icon {
1904
+ position: absolute;
1905
+ top: 41%;
1906
+ left: -6px;
1907
+ border-radius: 8px;
1908
+ border: 1px solid #E2E2E2;
1909
+ background: #FFF;
1910
+ display: flex;
1911
+ flex-direction: column;
1912
+ justify-content: center;
1913
+ align-items: center;
1914
+ width: 10px;
1915
+ height: 22px;
1916
+ padding: 3px 0;
1917
+ cursor: grab;
1918
+ visibility: hidden;
1919
+ }
1920
+
1903
1921
  .ProseMirror .headshot-figure .headshot-remove-button {
1904
1922
  margin: 0;
1905
1923
  top: -6%;
@@ -1917,6 +1935,7 @@ th:hover > .table-context-menu-button,
1917
1935
  height: 8px;
1918
1936
  }
1919
1937
 
1938
+ .ProseMirror .headshot-grid-block .headshot-element:not(:only-child):hover .headshot-drag-icon,
1920
1939
  .ProseMirror .headshot-element:hover .block > .headshot-remove-button,
1921
1940
  .ProseMirror .headshot-figure:hover .headshot-remove-button,
1922
1941
  .ProseMirror .headshot-element:focus-within .headshot-remove-button {
@@ -2044,8 +2063,8 @@ th:hover > .table-context-menu-button,
2044
2063
  position: relative;
2045
2064
  }
2046
2065
 
2047
- /* Right padding so abstract titles do not run under translation widgets.
2048
- * If we later block editing these titles, this padding can go away.
2066
+ /* Right padding so abstract titles do not run under translation widgets.
2067
+ * If we later block editing these titles, this padding can go away.
2049
2068
  */
2050
2069
  .ProseMirror .abstracts .block-section .block-section_title h1,
2051
2070
  .ProseMirror .abstracts .block-trans_abstract .block-section_title h1{
package/styles/Editor.css CHANGED
@@ -1309,11 +1309,24 @@
1309
1309
  font-style: normal;
1310
1310
  }
1311
1311
 
1312
+ .ProseMirror .headshot-element.drop-target-above {
1313
+ border-top: 3px solid #20aedf !important;
1314
+ border-top-left-radius: 0 !important;
1315
+ border-top-right-radius: 0 !important;
1316
+ }
1317
+
1318
+ .ProseMirror .headshot-element.drop-target-below {
1319
+ border-bottom: 3px solid #20aedf !important;
1320
+ border-bottom-left-radius: 0 !important;
1321
+ border-bottom-right-radius: 0 !important;
1322
+ }
1323
+
1312
1324
  .ProseMirror .supplement-item.drop-target-above,
1313
1325
  .ProseMirror .supplement-item.drop-target-below {
1314
1326
  border: 2px dotted #20aedf !important;
1315
1327
  }
1316
1328
 
1329
+ .ProseMirror .headshot-element.dragging,
1317
1330
  .ProseMirror .supplement-item.dragging {
1318
1331
  background-color: #f2f2f2 !important;
1319
1332
  opacity: 0.3;