import {registerTableResizer} from "./ui/framework/helpers/table-resizer";
import {EditorUiContext} from "./ui/framework/core";
import {listen as listenToCommonEvents} from "./services/common-events";
-import {handleDropEvents} from "./services/drop-handling";
+import {registerDropPasteHandling} from "./services/drop-paste-handling";
import {registerTaskListHandler} from "./ui/framework/helpers/task-list-handler";
import {registerTableSelectionHandler} from "./ui/framework/helpers/table-selection-handler";
import {el} from "./utils/dom";
registerTableResizer(editor, editWrap),
registerTableSelectionHandler(editor),
registerTaskListHandler(editor, editArea),
+ registerDropPasteHandling(context),
);
listenToCommonEvents(editor);
- handleDropEvents(editor);
setEditorContentFromHtml(editor, htmlContent);
+++ /dev/null
-import {
- $isDecoratorNode,
- LexicalEditor,
- LexicalNode
-} from "lexical";
-import {$insertNewBlockNodesAtSelection, $selectSingleNode} from "../utils/selection";
-import {$getNearestBlockNodeForCoords, $htmlToBlockNodes} from "../utils/nodes";
-
-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);
-
- if (positionNode) {
- $selectSingleNode(positionNode);
- }
-
- $insertNewBlockNodesAtSelection(nodes, true);
-
- if (!$isDecoratorNode(positionNode) || !positionNode?.getTextContent()) {
- positionNode?.remove();
- }
-}
-
-async function insertTemplateToEditor(editor: LexicalEditor, templateId: string, event: DragEvent) {
- const resp = await window.$http.get(`/templates/${templateId}`);
- const data = (resp.data || {html: ''}) as {html: string}
- const html: string = data.html || '';
-
- editor.update(() => {
- const newNodes = $htmlToBlockNodes(editor, html);
- $insertNodesAtEvent(newNodes, event, editor);
- });
-}
-
-function createDropListener(editor: LexicalEditor): (event: DragEvent) => void {
- return (event: DragEvent) => {
- // Template handling
- const templateId = event.dataTransfer?.getData('bookstack/template') || '';
- if (templateId) {
- insertTemplateToEditor(editor, templateId, event);
- event.preventDefault();
- return;
- }
-
- // HTML contents drop
- const html = event.dataTransfer?.getData('text/html') || '';
- if (html) {
- editor.update(() => {
- const newNodes = $htmlToBlockNodes(editor, html);
- $insertNodesAtEvent(newNodes, event, editor);
- });
- event.preventDefault();
- return;
- }
- };
-}
-
-export function handleDropEvents(editor: LexicalEditor) {
- const dropListener = createDropListener(editor);
-
- editor.registerRootListener((rootElement, prevRootElement) => {
- rootElement?.addEventListener('drop', dropListener);
- prevRootElement?.removeEventListener('drop', dropListener);
- });
-}
\ No newline at end of file
--- /dev/null
+import {
+ $insertNodes,
+ $isDecoratorNode, COMMAND_PRIORITY_HIGH, DROP_COMMAND,
+ LexicalEditor,
+ LexicalNode, PASTE_COMMAND
+} from "lexical";
+import {$insertNewBlockNodesAtSelection, $selectSingleNode} from "../utils/selection";
+import {$getNearestBlockNodeForCoords, $htmlToBlockNodes} from "../utils/nodes";
+import {Clipboard} from "../../services/clipboard";
+import {$createImageNode} from "../nodes/image";
+import {$createCustomParagraphNode} from "../nodes/custom-paragraph";
+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);
+
+ if (positionNode) {
+ $selectSingleNode(positionNode);
+ }
+
+ $insertNewBlockNodesAtSelection(nodes, true);
+
+ if (!$isDecoratorNode(positionNode) || !positionNode?.getTextContent()) {
+ positionNode?.remove();
+ }
+}
+
+async function insertTemplateToEditor(editor: LexicalEditor, templateId: string, event: DragEvent) {
+ const resp = await window.$http.get(`/templates/${templateId}`);
+ const data = (resp.data || {html: ''}) as {html: string}
+ const html: string = data.html || '';
+
+ editor.update(() => {
+ const newNodes = $htmlToBlockNodes(editor, html);
+ $insertNodesAtEvent(newNodes, event, editor);
+ });
+}
+
+function handleMediaInsert(data: DataTransfer, context: EditorUiContext): boolean {
+ const clipboard = new Clipboard(data);
+ let handled = false;
+
+ // Don't handle the event ourselves if no items exist of contains table-looking data
+ if (!clipboard.hasItems() || clipboard.containsTabularData()) {
+ return handled;
+ }
+
+ const images = clipboard.getImages();
+ if (images.length > 0) {
+ handled = true;
+ }
+
+ context.editor.update(async () => {
+ for (const imageFile of images) {
+ const loadingImage = window.baseUrl('/loading.gif');
+ const loadingNode = $createImageNode(loadingImage);
+ const imageWrap = $createCustomParagraphNode();
+ imageWrap.append(loadingNode);
+ $insertNodes([imageWrap]);
+
+ try {
+ const respData: EditorImageData = await uploadImageFile(imageFile, context.options.pageId);
+ const safeName = respData.name.replace(/"/g, '');
+ context.editor.update(() => {
+ const finalImage = $createImageNode(respData.thumbs?.display || '', {
+ alt: safeName,
+ });
+ const imageLink = $createLinkNode(respData.url, {target: '_blank'});
+ imageLink.append(finalImage);
+ loadingNode.replace(imageLink);
+ });
+ } catch (err: any) {
+ context.editor.update(() => {
+ loadingNode.remove(false);
+ });
+ window.$events.error(err?.data?.message || context.options.translations.imageUploadErrorText);
+ console.error(err);
+ }
+ }
+ });
+
+ return handled;
+}
+
+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) {
+ insertTemplateToEditor(editor, templateId, event);
+ event.preventDefault();
+ return true;
+ }
+
+ // HTML contents drop
+ const html = event.dataTransfer?.getData('text/html') || '';
+ if (html) {
+ editor.update(() => {
+ const newNodes = $htmlToBlockNodes(editor, html);
+ $insertNodesAtEvent(newNodes, event, editor);
+ });
+ event.preventDefault();
+ return true;
+ }
+
+ if (event.dataTransfer) {
+ const handled = handleMediaInsert(event.dataTransfer, context);
+ if (handled) {
+ event.preventDefault();
+ return true;
+ }
+ }
+
+ return false;
+ };
+}
+
+function createPasteListener(context: EditorUiContext): (event: ClipboardEvent) => boolean {
+ return (event: ClipboardEvent) => {
+ if (!event.clipboardData) {
+ return false;
+ }
+
+ const handled = handleMediaInsert(event.clipboardData, context);
+ if (handled) {
+ event.preventDefault();
+ }
+
+ return handled;
+ };
+}
+
+export function registerDropPasteHandling(context: EditorUiContext): () => void {
+ const dropListener = createDropListener(context);
+ const pasteListener = createPasteListener(context);
+
+ const unregisterDrop = context.editor.registerCommand(DROP_COMMAND, dropListener, COMMAND_PRIORITY_HIGH);
+ const unregisterPaste = context.editor.registerCommand(PASTE_COMMAND, pasteListener, COMMAND_PRIORITY_HIGH);
+
+ return () => {
+ unregisterDrop();
+ unregisterPaste();
+ };
+}
\ No newline at end of file