@manuscripts/body-editor 3.13.15 → 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.
- package/dist/cjs/lib/drag-drop-manager.js +128 -0
- package/dist/cjs/versions.js +1 -1
- package/dist/cjs/views/headshot_element.js +22 -0
- package/dist/cjs/views/headshot_grid.js +1 -0
- package/dist/es/lib/drag-drop-manager.js +124 -0
- package/dist/es/versions.js +1 -1
- package/dist/es/views/headshot_element.js +23 -1
- package/dist/es/views/headshot_grid.js +1 -0
- package/dist/types/lib/drag-drop-manager.d.ts +31 -0
- package/dist/types/versions.d.ts +1 -1
- package/dist/types/views/headshot_element.d.ts +3 -0
- package/dist/types/views/headshot_grid.d.ts +1 -0
- package/package.json +1 -1
- package/src/lib/drag-drop-manager.ts +198 -0
- package/src/versions.ts +1 -1
- package/src/views/headshot_element.ts +27 -1
- package/src/views/headshot_grid.ts +2 -0
- package/styles/AdvancedEditor.css +23 -4
- package/styles/Editor.css +13 -0
|
@@ -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;
|
package/dist/cjs/versions.js
CHANGED
|
@@ -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');
|
|
@@ -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;
|
package/dist/es/versions.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const VERSION = '3.13.
|
|
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 {};
|
package/dist/types/versions.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION = "3.13.
|
|
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.
|
|
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",
|
|
@@ -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
|
@@ -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
|
|
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;
|