/** * [[include:plugins/drag-and-drop/README.md]] * @packageDocumentation * @module plugins/drag-and-drop */ /** * TODO: need remove `drag-and-drop-element` plugin and use full custom moving inside this plugin. */ import type { IPoint, IViewComponent } from 'jodit/types'; import { TEXT_HTML, TEXT_PLAIN } from 'jodit/core/constants'; import { Dom } from 'jodit/core/dom/dom'; import { attr, ctrlKey, dataBind, getDataTransfer } from 'jodit/core/helpers'; import { Plugin } from 'jodit/core/plugin'; import { autobind, throttle } from 'jodit/core/decorators'; import { isFileBrowserFilesItem } from 'jodit/modules/file-browser'; import { pluginSystem } from 'jodit/core/global'; /** * Process drag and drop image from FileBrowser and movev image inside the editor */ export class dragAndDrop extends Plugin { private isFragmentFromEditor: boolean = false; private isCopyMode: boolean = false; private startDragPoint: IPoint = { x: 0, y: 0 }; private draggable: HTMLElement | null = null; private bufferRange: Range | null = null; /** @override */ afterInit(): void { this.j.e.on( [window, this.j.ed, this.j.editor], 'dragstart.DragAndDrop', this.onDragStart ); } /** @override */ beforeDestruct(): void { this.onDragEnd(); this.j.e .off(window, '.DragAndDrop') .off('.DragAndDrop') .off( [window, this.j.ed, this.j.editor], 'dragstart.DragAndDrop', this.onDragStart ); } @autobind private onDragStart(event: DragEvent): void { let target: HTMLElement = event.target as HTMLElement; this.onDragEnd(); // remove old draggable this.isFragmentFromEditor = Dom.isOrContains( this.j.editor, target, true ); this.isCopyMode = this.isFragmentFromEditor ? ctrlKey(event) : true; // we can move only element from editor if (this.isFragmentFromEditor) { const sel = this.j.s.sel; const range: Range | null = sel && sel.rangeCount ? sel.getRangeAt(0) : null; if (range) { this.bufferRange = range.cloneRange(); } } else { this.bufferRange = null; } this.startDragPoint.x = event.clientX; this.startDragPoint.y = event.clientY; if (isFileBrowserFilesItem(target)) { target = target.querySelector('img') as HTMLElement; } if (Dom.isTag(target, 'img')) { this.draggable = target.cloneNode(true) as HTMLElement; dataBind(this.draggable, 'target', target); } this.addDragListeners(); } private addDragListeners(): void { this.j.e .on('dragover', this.onDrag) .on('drop.DragAndDrop', this.onDrop) .on( window, 'dragend.DragAndDrop drop.DragAndDrop mouseup.DragAndDrop', this.onDragEnd ); } private removeDragListeners(): void { this.j.e .off('dragover', this.onDrag) .off('drop.DragAndDrop', this.onDrop) .off( window, 'dragend.DragAndDrop drop.DragAndDrop mouseup.DragAndDrop', this.onDragEnd ); } @throttle(ctx => ctx.defaultTimeout / 10) private onDrag(event: DragEvent): void { console.log('onDrag event', event); if (this.draggable) { this.j.e.fire('hidePopup'); this.j.s.insertCursorAtPoint(event.clientX, event.clientY); event.preventDefault(); event.stopPropagation(); } } @autobind private onDragEnd(): void { if (this.draggable) { Dom.safeRemove(this.draggable); this.draggable = null; } this.isCopyMode = false; this.removeDragListeners(); } @autobind private onDrop(event: DragEvent): false | void { console.log('onDrop event', event); if ( !event.dataTransfer || !event.dataTransfer.files || !event.dataTransfer.files.length ) { if (!this.isFragmentFromEditor && !this.draggable) { this.j.e.fire('paste', event); event.preventDefault(); event.stopPropagation(); return false; } const sel = this.j.s.sel; const range: Range | null = this.bufferRange || (sel && sel.rangeCount ? sel.getRangeAt(0) : null); let fragment: DocumentFragment | HTMLElement | null = null; if (!this.draggable && range) { fragment = this.isCopyMode ? range.cloneContents() : range.extractContents(); } else if (this.draggable) { if (this.isCopyMode) { const [tagName, field] = attr(this.draggable, '-is-file') === '1' ? ['a', 'href'] : ['img', 'src']; fragment = this.j.createInside.element(tagName); fragment.setAttribute( field, attr(this.draggable, 'data-src') || attr(this.draggable, 'src') || '' ); if (tagName === 'a') { fragment.textContent = attr(fragment, field) || ''; } } else { fragment = dataBind(this.draggable, 'target'); } } else if (this.getText(event)) { fragment = this.j.createInside.fromHTML( this.getText(event) as string ); } sel && sel.removeAllRanges(); this.j.s.insertCursorAtPoint(event.clientX, event.clientY); if (fragment) { this.j.s.insertNode(fragment, false, false); if (range && fragment.firstChild && fragment.lastChild) { range.setStartBefore(fragment.firstChild); range.setEndAfter(fragment.lastChild); this.j.s.selectRange(range); this.j.e.fire('synchro'); } if (Dom.isTag(fragment, 'img') && this.j.events) { this.j.e.fire('afterInsertImage', fragment); } } event.preventDefault(); event.stopPropagation(); } this.isFragmentFromEditor = false; this.removeDragListeners(); } private getText = (event: DragEvent): string | null => { const dt = getDataTransfer(event); return dt ? dt.getData(TEXT_HTML) || dt.getData(TEXT_PLAIN) : null; }; } pluginSystem.add('dragAndDrop', dragAndDrop);