4 $isDecoratorNode, COMMAND_PRIORITY_HIGH, DROP_COMMAND,
6 LexicalNode, PASTE_COMMAND
8 import {$insertNewBlockNodesAtSelection, $selectSingleNode} from "../utils/selection";
9 import {$getNearestBlockNodeForCoords, $htmlToBlockNodes} from "../utils/nodes";
10 import {Clipboard} from "../../services/clipboard";
11 import {$createImageNode} from "@lexical/rich-text/LexicalImageNode";
12 import {$createLinkNode} from "@lexical/link";
13 import {EditorImageData, uploadImageFile} from "../utils/images";
14 import {EditorUiContext} from "../ui/framework/core";
16 function $getNodeFromMouseEvent(event: MouseEvent, editor: LexicalEditor): LexicalNode|null {
17 const x = event.clientX;
18 const y = event.clientY;
19 const dom = document.elementFromPoint(x, y);
24 return $getNearestBlockNodeForCoords(editor, event.clientX, event.clientY);
27 function $insertNodesAtEvent(nodes: LexicalNode[], event: DragEvent, editor: LexicalEditor) {
28 const positionNode = $getNodeFromMouseEvent(event, editor);
31 $selectSingleNode(positionNode);
34 $insertNewBlockNodesAtSelection(nodes, true);
36 if (!$isDecoratorNode(positionNode) || !positionNode?.getTextContent()) {
37 positionNode?.remove();
41 async function insertTemplateToEditor(editor: LexicalEditor, templateId: string, event: DragEvent) {
42 const resp = await window.$http.get(`/templates/${templateId}`);
43 const data = (resp.data || {html: ''}) as {html: string}
44 const html: string = data.html || '';
47 const newNodes = $htmlToBlockNodes(editor, html);
48 $insertNodesAtEvent(newNodes, event, editor);
52 function handleMediaInsert(data: DataTransfer, context: EditorUiContext): boolean {
53 const clipboard = new Clipboard(data);
56 // Don't handle the event ourselves if no items exist of contains table-looking data
57 if (!clipboard.hasItems() || clipboard.containsTabularData()) {
61 const images = clipboard.getImages();
62 if (images.length > 0) {
66 context.editor.update(async () => {
67 for (const imageFile of images) {
68 const loadingImage = window.baseUrl('/loading.gif');
69 const loadingNode = $createImageNode(loadingImage);
70 const imageWrap = $createParagraphNode();
71 imageWrap.append(loadingNode);
72 $insertNodes([imageWrap]);
75 const respData: EditorImageData = await uploadImageFile(imageFile, context.options.pageId);
76 const safeName = respData.name.replace(/"/g, '');
77 context.editor.update(() => {
78 const finalImage = $createImageNode(respData.thumbs?.display || '', {
81 const imageLink = $createLinkNode(respData.url, {target: '_blank'});
82 imageLink.append(finalImage);
83 loadingNode.replace(imageLink);
86 context.editor.update(() => {
87 loadingNode.remove(false);
89 window.$events.error(err?.data?.message || context.options.translations.imageUploadErrorText);
98 function createDropListener(context: EditorUiContext): (event: DragEvent) => boolean {
99 const editor = context.editor;
100 return (event: DragEvent): boolean => {
102 const templateId = event.dataTransfer?.getData('bookstack/template') || '';
104 insertTemplateToEditor(editor, templateId, event);
105 event.preventDefault();
106 event.stopPropagation();
110 // HTML contents drop
111 const html = event.dataTransfer?.getData('text/html') || '';
113 editor.update(() => {
114 const newNodes = $htmlToBlockNodes(editor, html);
115 $insertNodesAtEvent(newNodes, event, editor);
117 event.preventDefault();
118 event.stopPropagation();
122 if (event.dataTransfer) {
123 const handled = handleMediaInsert(event.dataTransfer, context);
125 event.preventDefault();
126 event.stopPropagation();
135 function createPasteListener(context: EditorUiContext): (event: ClipboardEvent) => boolean {
136 return (event: ClipboardEvent) => {
137 if (!event.clipboardData) {
141 const handled = handleMediaInsert(event.clipboardData, context);
143 event.preventDefault();
150 export function registerDropPasteHandling(context: EditorUiContext): () => void {
151 const dropListener = createDropListener(context);
152 const pasteListener = createPasteListener(context);
154 const unregisterDrop = context.editor.registerCommand(DROP_COMMAND, dropListener, COMMAND_PRIORITY_HIGH);
155 const unregisterPaste = context.editor.registerCommand(PASTE_COMMAND, pasteListener, COMMAND_PRIORITY_HIGH);
156 context.scrollDOM.addEventListener('drop', dropListener);
161 context.scrollDOM.removeEventListener('drop', dropListener);