1 import {Clipboard} from '../services/clipboard';
4 let draggedContentEditable;
6 function hasTextContent(node) {
7 return node && !!(node.textContent || node.innerText);
11 * Upload an image file to the server
15 async function uploadImageFile(file, pageId) {
16 if (file === null || file.type.indexOf('image') !== 0) {
17 throw new Error('Not an image file');
20 const remoteFilename = file.name || `image-${Date.now()}.png`;
21 const formData = new FormData();
22 formData.append('file', file, remoteFilename);
23 formData.append('uploaded_to', pageId);
25 const resp = await window.$http.post(window.baseUrl('/images/gallery'), formData);
30 * Handle pasting images from clipboard.
31 * @param {Editor} editor
32 * @param {WysiwygConfigOptions} options
33 * @param {ClipboardEvent|DragEvent} event
35 function paste(editor, options, event) {
36 const clipboard = new Clipboard(event.clipboardData || event.dataTransfer);
38 // Don't handle the event ourselves if no items exist of contains table-looking data
39 if (!clipboard.hasItems() || clipboard.containsTabularData()) {
43 const images = clipboard.getImages();
44 for (const imageFile of images) {
45 const id = `image-${Math.random().toString(16).slice(2)}`;
46 const loadingImage = window.baseUrl('/loading.gif');
47 event.preventDefault();
50 editor.insertContent(`<p><img src="${loadingImage}" id="${id}"></p>`);
52 uploadImageFile(imageFile, options.pageId).then(resp => {
53 const safeName = resp.name.replace(/"/g, '');
54 const newImageHtml = `<img src="${resp.thumbs.display}" alt="${safeName}" />`;
56 const newEl = editor.dom.create('a', {
61 editor.dom.replace(newEl, id);
63 editor.dom.remove(id);
64 window.$events.error(err?.data?.message || options.translations.imageUploadErrorText);
72 * @param {Editor} editor
74 function dragStart(editor) {
75 const node = editor.selection.getNode();
77 if (node.nodeName === 'IMG') {
78 wrap = editor.dom.getParent(node, '.mceTemp');
80 if (!wrap && node.parentNode.nodeName === 'A' && !hasTextContent(node.parentNode)) {
81 wrap = node.parentNode;
85 // Track dragged contenteditable blocks
86 if (node.hasAttribute('contenteditable') && node.getAttribute('contenteditable') === 'false') {
87 draggedContentEditable = node;
92 * @param {Editor} editor
93 * @param {WysiwygConfigOptions} options
94 * @param {DragEvent} event
96 function drop(editor, options, event) {
98 const rng = window.tinymce.dom.RangeUtils.getCaretRangeFromPoint(
104 // Template insertion
105 const templateId = event.dataTransfer && event.dataTransfer.getData('bookstack/template');
107 event.preventDefault();
108 window.$http.get(`/templates/${templateId}`).then(resp => {
109 editor.selection.setRng(rng);
110 editor.undoManager.transact(() => {
111 editor.execCommand('mceInsertContent', false, resp.data.html);
116 // Don't allow anything to be dropped in a captioned image.
117 if (dom.getParent(rng.startContainer, '.mceTemp')) {
118 event.preventDefault();
120 event.preventDefault();
122 editor.undoManager.transact(() => {
123 editor.selection.setRng(rng);
124 editor.selection.setNode(wrap);
129 // Handle contenteditable section drop
130 if (!event.isDefaultPrevented() && draggedContentEditable) {
131 event.preventDefault();
132 editor.undoManager.transact(() => {
133 const selectedNode = editor.selection.getNode();
134 const range = editor.selection.getRng();
135 const selectedNodeRoot = selectedNode.closest('body > *');
136 if (range.startOffset > (range.startContainer.length / 2)) {
137 selectedNodeRoot.after(draggedContentEditable);
139 selectedNodeRoot.before(draggedContentEditable);
144 // Handle image insert
145 if (!event.isDefaultPrevented()) {
146 paste(editor, options, event);
153 * @param {Editor} editor
154 * @param {DragEvent} event
156 function dragOver(editor, event) {
157 // This custom handling essentially emulates the default TinyMCE behaviour while allowing us
158 // to specifically call preventDefault on the event to allow the drop of custom elements.
159 event.preventDefault();
161 const rangeUtils = window.tinymce.dom.RangeUtils;
162 const range = rangeUtils.getCaretRangeFromPoint(event.clientX ?? 0, event.clientY ?? 0, editor.getDoc());
163 editor.selection.setRng(range);
167 * @param {Editor} editor
168 * @param {WysiwygConfigOptions} options
170 export function listenForDragAndPaste(editor, options) {
171 editor.on('dragover', event => dragOver(editor, event));
172 editor.on('dragstart', () => dragStart(editor));
173 editor.on('drop', event => drop(editor, options, event));
174 editor.on('paste', event => paste(editor, options, event));