mirror of
https://github.com/BookStackApp/BookStack.git
synced 2026-07-15 21:31:36 +03:00
Lexical: Improved content insert on drop handling
- Adds specific support for inline content handling. - Adds attempting to use caret position at drop location for accurate placement.
This commit is contained in:
@@ -1,40 +1,35 @@
|
||||
import {
|
||||
$createParagraphNode,
|
||||
$insertNodes,
|
||||
$isDecoratorNode, COMMAND_PRIORITY_HIGH, DROP_COMMAND,
|
||||
$isDecoratorNode, $isTextNode, $setSelection, COMMAND_PRIORITY_HIGH, DROP_COMMAND,
|
||||
LexicalEditor,
|
||||
LexicalNode, PASTE_COMMAND
|
||||
} from "lexical";
|
||||
import {$insertNewBlockNodesAtSelection, $selectSingleNode} from "../utils/selection";
|
||||
import {$getNearestBlockNodeForCoords, $htmlToBlockNodes} from "../utils/nodes";
|
||||
import {$insertNewNodesAtSelection, $selectSingleNode} from "../utils/selection";
|
||||
import {$getNodePositionFromMouseEvent, $htmlToBlockNodes} from "../utils/nodes";
|
||||
import {Clipboard} from "../../services/clipboard";
|
||||
import {$createImageNode} from "@lexical/rich-text/LexicalImageNode";
|
||||
import {$createLinkNode} from "@lexical/link";
|
||||
import {EditorImageData, uploadImageFile} from "../utils/images";
|
||||
import {EditorUiContext} from "../ui/framework/core";
|
||||
|
||||
function $getNodeFromMouseEvent(event: MouseEvent, editor: LexicalEditor): LexicalNode|null {
|
||||
const x = event.clientX;
|
||||
const y = event.clientY;
|
||||
const dom = document.elementFromPoint(x, y);
|
||||
if (!dom) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $getNearestBlockNodeForCoords(editor, event.clientX, event.clientY);
|
||||
}
|
||||
|
||||
function $insertNodesAtEvent(nodes: LexicalNode[], event: DragEvent, editor: LexicalEditor) {
|
||||
const positionNode = $getNodeFromMouseEvent(event, editor);
|
||||
const position = $getNodePositionFromMouseEvent(event, editor);
|
||||
|
||||
if (positionNode) {
|
||||
$selectSingleNode(positionNode);
|
||||
if (position && $isTextNode(position.node)) {
|
||||
const selection = position.node.select(position.offset, position.offset);
|
||||
$setSelection(selection);
|
||||
} else if (position) {
|
||||
$selectSingleNode(position.node);
|
||||
}
|
||||
|
||||
$insertNewBlockNodesAtSelection(nodes, true);
|
||||
$insertNewNodesAtSelection(nodes);
|
||||
|
||||
if (!$isDecoratorNode(positionNode) || !positionNode?.getTextContent()) {
|
||||
positionNode?.remove();
|
||||
if (position) {
|
||||
if (!$isDecoratorNode(position.node) && !position.node?.getTextContent()) {
|
||||
position.node.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,6 +108,7 @@ function handleImageLinkInsert(data: DataTransfer, context: EditorUiContext): bo
|
||||
function createDropListener(context: EditorUiContext): (event: DragEvent) => boolean {
|
||||
const editor = context.editor;
|
||||
return (event: DragEvent): boolean => {
|
||||
|
||||
// Template handling
|
||||
const templateId = event.dataTransfer?.getData('bookstack/template') || '';
|
||||
if (templateId) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
$createParagraphNode,
|
||||
$getNearestNodeFromDOMNode,
|
||||
$getRoot,
|
||||
$isDecoratorNode,
|
||||
$isElementNode, $isRootNode,
|
||||
@@ -64,6 +65,27 @@ export function $getAllNodesOfType(matcher: LexicalNodeMatcher, root?: ElementNo
|
||||
return matches;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the node based on the given mouse event.
|
||||
*/
|
||||
export function $getNodePositionFromMouseEvent(event: MouseEvent, editor: LexicalEditor): {node: LexicalNode, offset: number}|null {
|
||||
const x = event.clientX;
|
||||
const y = event.clientY;
|
||||
const caretPosition = window.document.caretPositionFromPoint(event.x, event.y);
|
||||
if (!caretPosition) {
|
||||
const backup = $getNearestBlockNodeForCoords(editor, x, y);
|
||||
return backup ? {node: backup, offset: 0} : null;
|
||||
}
|
||||
|
||||
const node = $getNearestNodeFromDOMNode(caretPosition.offsetNode);
|
||||
if (!node) {
|
||||
const backup = $getNearestBlockNodeForCoords(editor, x, y);
|
||||
return backup ? {node: backup, offset: 0} : null;
|
||||
}
|
||||
|
||||
return {node, offset: caretPosition.offset};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the nearest root/block level node for the given position.
|
||||
*/
|
||||
|
||||
@@ -109,6 +109,45 @@ export function $insertNewBlockNodesAtSelection(nodes: LexicalNode[], insertAfte
|
||||
}
|
||||
}
|
||||
|
||||
export function $insertNewNodesAtSelection(nodes: LexicalNode[]) {
|
||||
const selection = $getSelection();
|
||||
const selectionPoints = selection?.getStartEndPoints();
|
||||
let target: LexicalNode|null = null;
|
||||
let targetBlock: LexicalNode|null = null;
|
||||
if (selectionPoints) {
|
||||
const selectionEnd = selectionPoints[1];
|
||||
target = selectionEnd.getNode();
|
||||
targetBlock = target ? $getNearestNodeBlockParent(target) : null;
|
||||
}
|
||||
|
||||
for (const node of nodes) {
|
||||
const isBlock = $isBlockElementNode(node);
|
||||
|
||||
if (isBlock && !targetBlock) {
|
||||
// Append to the root if its a block and we can't determine position
|
||||
$getRoot().append(node);
|
||||
} else if (isBlock && targetBlock) {
|
||||
// Insert after the target block if we have a block
|
||||
targetBlock.insertAfter(node);
|
||||
} else if (!isBlock && selection) {
|
||||
// Insert at selection if likely inline
|
||||
selection.insertNodes(nodes);
|
||||
} else if (!isBlock && $isElementNode(targetBlock)) {
|
||||
// Append inside the target block if inline but we don't have
|
||||
// a selection (typically used by the case below)
|
||||
targetBlock.append(node);
|
||||
} else {
|
||||
// Otherwise (where inline) create a new root level paragraph
|
||||
// and insert content into that. Update the target block
|
||||
// for re-use by other inline elements.
|
||||
const paragraph = $createParagraphNode();
|
||||
paragraph.append(node);
|
||||
$getRoot().append(paragraph);
|
||||
targetBlock = paragraph;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function $selectSingleNode(node: LexicalNode) {
|
||||
const nodeSelection = $createNodeSelection();
|
||||
nodeSelection.add(node.getKey());
|
||||
|
||||
Reference in New Issue
Block a user