1 import {positionHandlesAtCorners, removeHandles, renderHandlesAtCorners} from "./node-view-utils";
2 import {NodeSelection} from "prosemirror-state";
8 * @param {(function(): number)} getPos
10 constructor(node, view, getPos) {
11 this.dom = document.createElement('div');
12 this.dom.classList.add('ProseMirror-imagewrap');
14 this.image = document.createElement("img");
15 this.image.src = node.attrs.src;
16 this.image.alt = node.attrs.alt;
17 if (node.attrs.width) {
18 this.image.width = node.attrs.width;
20 if (node.attrs.height) {
21 this.image.height = node.attrs.height;
24 this.dom.appendChild(this.image);
27 this.handleDragStartInfo = null;
28 this.handleDragMoveDimensions = null;
29 this.removeHandlesListener = this.removeHandlesListener.bind(this);
30 this.handleMouseMove = this.handleMouseMove.bind(this);
31 this.handleMouseUp = this.handleMouseUp.bind(this);
32 this.handleMouseDown = this.handleMouseDown.bind(this);
34 this.dom.addEventListener("click", event => {
38 // Show handles if selected
39 if (view.state.selection.node === node) {
40 window.setTimeout(() => {
45 this.updateImageDimensions = function (width, height) {
46 const attrs = Object.assign({}, node.attrs, {width, height});
47 let tr = view.state.tr;
48 const position = getPos();
49 tr = tr.setNodeMarkup(position, null, attrs)
50 tr = tr.setSelection(NodeSelection.create(tr.doc, position));
57 if (this.handles.length === 0) {
58 this.image.dataset.showHandles = 'true';
59 window.addEventListener('click', this.removeHandlesListener);
60 this.handles = renderHandlesAtCorners(this.image);
61 for (const handle of this.handles) {
62 handle.addEventListener('mousedown', this.handleMouseDown);
67 removeHandlesListener(event) {
68 console.log(this.dom.contains(event.target), event.target);
69 if (!this.dom.contains(event.target)) {
76 removeHandles(this.handles);
77 window.removeEventListener('click', this.removeHandlesListener);
78 delete this.image.dataset.showHandles;
86 * @param {MouseEvent} event
88 handleMouseDown(event) {
89 event.preventDefault();
91 const imageBounds = this.image.getBoundingClientRect();
92 const handle = event.target;
93 this.handleDragStartInfo = {
96 ratio: imageBounds.width / imageBounds.height,
98 handleX: handle.dataset.x,
99 handleY: handle.dataset.y,
102 this.createDragDummy(imageBounds);
103 this.dom.appendChild(this.dragDummy);
105 window.addEventListener('mousemove', this.handleMouseMove);
106 window.addEventListener('mouseup', this.handleMouseUp);
110 * @param {DOMRect} bounds
112 createDragDummy(bounds) {
113 this.dragDummy = this.image.cloneNode();
114 this.dragDummy.style.opacity = '0.5';
115 this.dragDummy.classList.add('ProseMirror-dragdummy');
116 this.dragDummy.style.width = bounds.width + 'px';
117 this.dragDummy.style.height = bounds.height + 'px';
121 * @param {MouseEvent} event
123 handleMouseUp(event) {
124 if (this.handleDragMoveDimensions) {
125 const {width, height} = this.handleDragMoveDimensions;
126 this.updateImageDimensions(String(width), String(height));
129 window.removeEventListener('mousemove', this.handleMouseMove);
130 window.removeEventListener('mouseup', this.handleMouseUp);
131 this.handleDragStartInfo = null;
132 this.handleDragMoveDimensions = null;
133 this.dragDummy.remove();
134 positionHandlesAtCorners(this.image, this.handles);
138 * @param {MouseEvent} event
140 handleMouseMove(event) {
141 const originalBounds = this.handleDragStartInfo.bounds;
143 // Calculate change in x & y, flip amounts depending on handle
144 let xChange = event.screenX - this.handleDragStartInfo.x;
145 if (this.handleDragStartInfo.handleX === 'left') {
148 let yChange = event.screenY - this.handleDragStartInfo.y;
149 if (this.handleDragStartInfo.handleY === 'top') {
153 // Prevent images going too small or into negative bounds
154 if (originalBounds.width + xChange < 10) {
155 xChange = -originalBounds.width + 10;
157 if (originalBounds.height + yChange < 10) {
158 yChange = -originalBounds.height + 10;
161 // Choose the larger dimension change and align the other to keep
162 // image aspect ratio, aligning growth/reduction direction
163 if (Math.abs(xChange) > Math.abs(yChange)) {
164 yChange = Math.floor(xChange * this.handleDragStartInfo.ratio);
165 if (yChange * xChange < 0) {
169 xChange = Math.floor(yChange / this.handleDragStartInfo.ratio);
170 if (xChange * yChange < 0) {
175 // Calculate our new sizes
176 const newWidth = originalBounds.width + xChange;
177 const newHeight = originalBounds.height + yChange;
179 // Apply the sizes and positioning to our ghost dummy
180 this.dragDummy.style.width = `${newWidth}px`;
181 if (this.handleDragStartInfo.handleX === 'left') {
182 this.dragDummy.style.left = `${-xChange}px`;
184 this.dragDummy.style.height = `${newHeight}px`;
185 if (this.handleDragStartInfo.handleY === 'top') {
186 this.dragDummy.style.top = `${-yChange}px`;
189 // Update corners and track dimension changes for later application
190 positionHandlesAtCorners(this.dragDummy, this.handles);
191 this.handleDragMoveDimensions = {
198 export default ImageView;