Lexical: Remove DraggableBlockPlugin

Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
marcin mikołajczak 2023-05-16 23:40:24 +02:00
parent 65db6f503d
commit 89cef774c8
2 changed files with 0 additions and 371 deletions

View file

@ -34,7 +34,6 @@ const LINK_MATCHERS = [
import nodes from './nodes';
import AutosuggestPlugin from './plugins/autosuggest-plugin';
import DraggableBlockPlugin from './plugins/draggable-block-plugin';
import FloatingLinkEditorPlugin from './plugins/floating-link-editor-plugin';
import FloatingTextFormatToolbarPlugin from './plugins/floating-text-format-toolbar-plugin';
import MentionPlugin from './plugins/mention-plugin';
@ -183,7 +182,6 @@ const ComposeEditor = React.forwardRef<string, IComposeEditor>(({
{features.richText && <ListPlugin />}
{features.richText && floatingAnchorElem && (
<>
<DraggableBlockPlugin anchorElem={floatingAnchorElem} />
<FloatingTextFormatToolbarPlugin anchorElem={floatingAnchorElem} />
<FloatingLinkEditorPlugin anchorElem={floatingAnchorElem} />
</>

View file

@ -1,369 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
import { eventFiles } from '@lexical/rich-text';
import { mergeRegister } from '@lexical/utils';
import clsx from 'clsx';
import {
$getNearestNodeFromDOMNode,
$getNodeByKey,
$getRoot,
COMMAND_PRIORITY_HIGH,
COMMAND_PRIORITY_LOW,
DRAGOVER_COMMAND,
DROP_COMMAND,
LexicalEditor,
} from 'lexical';
import * as React from 'react';
import { DragEvent as ReactDragEvent, useEffect, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import SvgIcon from 'soapbox/components/ui/icon/svg-icon';
import isHTMLElement from '../utils/is-html-element';
import Point from '../utils/point';
import Rect from '../utils/rect';
const SPACE = 4;
const TARGET_LINE_HALF_HEIGHT = 2;
const DRAG_DATA_FORMAT = 'application/x-lexical-drag-block';
const TEXT_BOX_HORIZONTAL_PADDING = 28;
const Downward = 1;
const Upward = -1;
const Indeterminate = 0;
let prevIndex = Infinity;
const getCurrentIndex = (keysLength: number): number => {
if (keysLength === 0) {
return Infinity;
}
if (prevIndex >= 0 && prevIndex < keysLength) {
return prevIndex;
}
return Math.floor(keysLength / 2);
};
const getTopLevelNodeKeys = (editor: LexicalEditor): string[] => editor.getEditorState().read(() => $getRoot().getChildrenKeys());
const getBlockElement = (
anchorElem: HTMLElement,
editor: LexicalEditor,
event: MouseEvent,
): HTMLElement | null => {
const anchorElementRect = anchorElem.getBoundingClientRect();
const topLevelNodeKeys = getTopLevelNodeKeys(editor);
let blockElem: HTMLElement | null = null;
editor.getEditorState().read(() => {
let index = getCurrentIndex(topLevelNodeKeys.length);
let direction = Indeterminate;
while (index >= 0 && index < topLevelNodeKeys.length) {
const key = topLevelNodeKeys[index];
const elem = editor.getElementByKey(key);
if (elem === null) {
break;
}
const point = new Point(event.x, event.y);
const domRect = Rect.fromDOM(elem);
const { marginTop, marginBottom } = window.getComputedStyle(elem);
const rect = domRect.generateNewRect({
bottom: domRect.bottom + parseFloat(marginBottom),
left: anchorElementRect.left,
right: anchorElementRect.right,
top: domRect.top - parseFloat(marginTop),
});
const {
result,
reason: { isOnTopSide, isOnBottomSide },
} = rect.contains(point);
if (result) {
blockElem = elem;
prevIndex = index;
break;
}
if (direction === Indeterminate) {
if (isOnTopSide) {
direction = Upward;
} else if (isOnBottomSide) {
direction = Downward;
} else {
// stop search block element
direction = Infinity;
}
}
index += direction;
}
});
return blockElem;
};
const isOnMenu = (element: HTMLElement): boolean => !!element.closest('.draggable-block-menu');
const setMenuPosition = (
targetElem: HTMLElement | null,
floatingElem: HTMLElement,
anchorElem: HTMLElement,
) => {
if (!targetElem) {
floatingElem.style.opacity = '0';
floatingElem.style.transform = 'translate(-10000px, -10000px)';
return;
}
const targetRect = targetElem.getBoundingClientRect();
const targetStyle = window.getComputedStyle(targetElem);
const floatingElemRect = floatingElem.getBoundingClientRect();
const anchorElementRect = anchorElem.getBoundingClientRect();
const top =
targetRect.top +
(parseInt(targetStyle.lineHeight, 10) - floatingElemRect.height) / 2 -
anchorElementRect.top;
const left = SPACE;
floatingElem.style.opacity = '1';
floatingElem.style.transform = `translate(${left}px, ${top}px)`;
};
const setDragImage = (
dataTransfer: DataTransfer,
draggableBlockElem: HTMLElement,
) => {
const { transform } = draggableBlockElem.style;
// Remove dragImage borders
draggableBlockElem.style.transform = 'translateZ(0)';
dataTransfer.setDragImage(draggableBlockElem, 0, 0);
setTimeout(() => {
draggableBlockElem.style.transform = transform;
});
};
const setTargetLine = (
targetLineElem: HTMLElement,
targetBlockElem: HTMLElement,
mouseY: number,
anchorElem: HTMLElement,
) => {
const targetStyle = window.getComputedStyle(targetBlockElem);
const { top: targetBlockElemTop, height: targetBlockElemHeight } =
targetBlockElem.getBoundingClientRect();
const { top: anchorTop, width: anchorWidth } =
anchorElem.getBoundingClientRect();
let lineTop = targetBlockElemTop;
// At the bottom of the target
if (mouseY - targetBlockElemTop > targetBlockElemHeight / 2) {
lineTop += targetBlockElemHeight + parseFloat(targetStyle.marginBottom);
} else {
lineTop -= parseFloat(targetStyle.marginTop);
}
const top = lineTop - anchorTop - TARGET_LINE_HALF_HEIGHT;
const left = TEXT_BOX_HORIZONTAL_PADDING - SPACE;
targetLineElem.style.transform = `translate(${left}px, ${top}px)`;
targetLineElem.style.width = `${
anchorWidth - (TEXT_BOX_HORIZONTAL_PADDING - SPACE) * 2
}px`;
targetLineElem.style.opacity = '.4';
};
const hideTargetLine = (targetLineElem: HTMLElement | null) => {
if (targetLineElem) {
targetLineElem.style.opacity = '0';
targetLineElem.style.transform = 'translate(-10000px, -10000px)';
}
};
const useDraggableBlockMenu = (
editor: LexicalEditor,
anchorElem: HTMLElement,
isEditable: boolean,
): JSX.Element => {
const scrollerElem = anchorElem.parentElement;
const menuRef = useRef<HTMLDivElement>(null);
const targetLineRef = useRef<HTMLDivElement>(null);
const [draggableBlockElem, setDraggableBlockElem] =
useState<HTMLElement | null>(null);
useEffect(() => {
const onMouseMove = (event: MouseEvent) => {
const target = event.target;
if (!isHTMLElement(target)) {
setDraggableBlockElem(null);
return;
}
if (isOnMenu(target)) {
return;
}
const _draggableBlockElem = getBlockElement(anchorElem, editor, event);
setDraggableBlockElem(_draggableBlockElem);
};
const onMouseLeave = () => setDraggableBlockElem(null);
scrollerElem?.addEventListener('mousemove', onMouseMove);
scrollerElem?.addEventListener('mouseleave', onMouseLeave);
return () => {
scrollerElem?.removeEventListener('mousemove', onMouseMove);
scrollerElem?.removeEventListener('mouseleave', onMouseLeave);
};
}, [scrollerElem, anchorElem, editor]);
useEffect(() => {
if (menuRef.current) {
setMenuPosition(draggableBlockElem, menuRef.current, anchorElem);
}
}, [anchorElem, draggableBlockElem]);
useEffect(() => {
const onDragover = (event: DragEvent): boolean => {
const [isFileTransfer] = eventFiles(event);
if (isFileTransfer) {
return false;
}
const { pageY, target } = event;
if (!isHTMLElement(target)) {
return false;
}
const targetBlockElem = getBlockElement(anchorElem, editor, event);
const targetLineElem = targetLineRef.current;
if (targetBlockElem === null || targetLineElem === null) {
return false;
}
setTargetLine(targetLineElem, targetBlockElem, pageY, anchorElem);
// Prevent default event to be able to trigger onDrop events
event.preventDefault();
return true;
};
const onDrop = (event: DragEvent): boolean => {
const [isFileTransfer] = eventFiles(event);
if (isFileTransfer) {
return false;
}
const { target, dataTransfer, pageY } = event;
const dragData = dataTransfer?.getData(DRAG_DATA_FORMAT) || '';
const draggedNode = $getNodeByKey(dragData);
if (!draggedNode) {
return false;
}
if (!isHTMLElement(target)) {
return false;
}
const targetBlockElem = getBlockElement(anchorElem, editor, event);
if (!targetBlockElem) {
return false;
}
const targetNode = $getNearestNodeFromDOMNode(targetBlockElem);
if (!targetNode) {
return false;
}
if (targetNode === draggedNode) {
return true;
}
const { top, height } = targetBlockElem.getBoundingClientRect();
const shouldInsertAfter = pageY - top > height / 2;
if (shouldInsertAfter) {
targetNode.insertAfter(draggedNode);
} else {
targetNode.insertBefore(draggedNode);
}
setDraggableBlockElem(null);
return true;
};
return mergeRegister(
editor.registerCommand(
DRAGOVER_COMMAND,
(event) => {
return onDragover(event);
},
COMMAND_PRIORITY_LOW,
),
editor.registerCommand(
DROP_COMMAND,
(event) => {
return onDrop(event);
},
COMMAND_PRIORITY_HIGH,
),
);
}, [anchorElem, editor]);
const onDragStart = (event: ReactDragEvent<HTMLDivElement>): void => {
const dataTransfer = event.dataTransfer;
if (!dataTransfer || !draggableBlockElem) {
return;
}
setDragImage(dataTransfer, draggableBlockElem);
let nodeKey = '';
editor.update(() => {
const node = $getNearestNodeFromDOMNode(draggableBlockElem);
if (node) {
nodeKey = node.getKey();
}
});
dataTransfer.setData(DRAG_DATA_FORMAT, nodeKey);
};
const onDragEnd = (): void => {
hideTargetLine(targetLineRef.current);
};
return createPortal(
<>
<div
className='draggable-block-menu absolute right-0 top-0 cursor-grab rounded px-[1px] py-0.5 opacity-0 will-change-transform hover:bg-gray-100 active:cursor-grabbing hover:dark:bg-primary-700'
ref={menuRef}
draggable
onDragStart={onDragStart}
onDragEnd={onDragEnd}
>
<SvgIcon
src={require('@tabler/icons/grip-vertical.svg')}
className={clsx(isEditable && 'pointer-events-none h-4 w-4 text-gray-700 dark:text-gray-600')}
/>
</div>
<div className='pointer-events-none absolute left-0 top-0 h-1 bg-secondary-400 opacity-0 will-change-transform' ref={targetLineRef} />
</>,
anchorElem,
);
};
const DraggableBlockPlugin = ({
anchorElem = document.body,
}: {
anchorElem?: HTMLElement
}): JSX.Element => {
const [editor] = useLexicalComposerContext();
return useDraggableBlockMenu(editor, anchorElem, editor._editable);
};
export default DraggableBlockPlugin;