From 9c70010bb5cf44626a5f3761ec944bde927c3f42 Mon Sep 17 00:00:00 2001 From: Yuriy Demidov Date: Mon, 13 Nov 2023 22:15:37 +0300 Subject: [PATCH] feat(YfmCut): auto-opening yfm-cut when dragging over it (#152) --- .../yfm/YfmCut/plugins/auto-open.ts | 69 ++++++++++++++++++- 1 file changed, 66 insertions(+), 3 deletions(-) diff --git a/src/extensions/yfm/YfmCut/plugins/auto-open.ts b/src/extensions/yfm/YfmCut/plugins/auto-open.ts index d359175d..3550f163 100644 --- a/src/extensions/yfm/YfmCut/plugins/auto-open.ts +++ b/src/extensions/yfm/YfmCut/plugins/auto-open.ts @@ -1,21 +1,26 @@ -import {Plugin, PluginKey} from 'prosemirror-state'; +import throttle from 'lodash/throttle'; +import {Plugin, PluginKey, PluginView} from 'prosemirror-state'; import type {EditorView} from 'prosemirror-view'; import type {ResolvedPos} from 'prosemirror-model'; +import {findDomRefAtPos} from 'prosemirror-utils'; import {isTextSelection} from '../../../../utils/selection'; import {cutContentType, cutType} from '../const'; const key = new PluginKey('yfm-cut-auto-open'); -export const cutAutoOpenPlugin = () => - new Plugin({ +export const cutAutoOpenPlugin = () => { + return new Plugin({ key, view(view) { update(view); + const dragHandler = new CutAutoOpenOnDragOver(view); return { update: (view) => update(view), + destroy: () => dragHandler.destroy(), }; }, }); +}; function update(view: EditorView) { const sel = view.state.selection; @@ -44,3 +49,61 @@ function openParentYfmCuts($pos: ResolvedPos, domAtPos: EditorView['domAtPos']): depth--; } } + +class CutAutoOpenOnDragOver implements PluginView { + private static readonly YFM_CUT_SELECTOR = '.yfm-cut:not(.open)'; + private static readonly OPEN_TIMEOUT = 500; //ms + private static readonly THROTTLE_WAIT = 50; //ms + + private _cutElem: HTMLElement | null = null; + private _editorView: EditorView; + private _timeout: ReturnType | null = null; + private readonly _docListener; + + constructor(view: EditorView) { + this._editorView = view; + this._docListener = throttle( + this._onDocEvent.bind(this), + CutAutoOpenOnDragOver.THROTTLE_WAIT, + ); + document.addEventListener('mousemove', this._docListener); + document.addEventListener('dragover', this._docListener); + } + + destroy(): void { + this._clear(); + this._docListener.cancel(); + document.removeEventListener('mousemove', this._docListener); + document.removeEventListener('dragover', this._docListener); + } + + private _onDocEvent(event: MouseEvent) { + const view = this._editorView; + if (!view.dragging) return; + const pos = view.posAtCoords({left: event.clientX, top: event.clientY}); + if (!pos) return; + const elem = findDomRefAtPos(pos.pos, view.domAtPos.bind(view)) as HTMLElement; + const cutElem = elem.closest(CutAutoOpenOnDragOver.YFM_CUT_SELECTOR); + if (cutElem === this._cutElem) return; + this._clear(); + if (cutElem) this._setCutElem(cutElem as HTMLElement); + } + + private _clear() { + if (this._timeout !== null) clearTimeout(this._timeout); + this._timeout = null; + this._cutElem = null; + } + + private _setCutElem(elem: HTMLElement) { + this._cutElem = elem; + this._timeout = setTimeout(this._openCut.bind(this), CutAutoOpenOnDragOver.OPEN_TIMEOUT); + } + + private _openCut() { + if (this._editorView.dragging) { + this._cutElem?.classList.add('open'); + } + this._clear(); + } +}