]> BookStack Code Mirror - bookstack/blob - resources/js/wysiwyg/drop-paste-handling.js
Ran eslint fix on existing codebase
[bookstack] / resources / js / wysiwyg / drop-paste-handling.js
1 import Clipboard from '../services/clipboard';
2
3 let wrap;
4 let draggedContentEditable;
5
6 function hasTextContent(node) {
7     return node && !!(node.textContent || node.innerText);
8 }
9
10 /**
11  * Handle pasting images from clipboard.
12  * @param {Editor} editor
13  * @param {WysiwygConfigOptions} options
14  * @param {ClipboardEvent|DragEvent} event
15  */
16 function paste(editor, options, event) {
17     const clipboard = new Clipboard(event.clipboardData || event.dataTransfer);
18
19     // Don't handle the event ourselves if no items exist of contains table-looking data
20     if (!clipboard.hasItems() || clipboard.containsTabularData()) {
21         return;
22     }
23
24     const images = clipboard.getImages();
25     for (const imageFile of images) {
26         const id = `image-${Math.random().toString(16).slice(2)}`;
27         const loadingImage = window.baseUrl('/loading.gif');
28         event.preventDefault();
29
30         setTimeout(() => {
31             editor.insertContent(`<p><img src="${loadingImage}" id="${id}"></p>`);
32
33             uploadImageFile(imageFile, options.pageId).then(resp => {
34                 const safeName = resp.name.replace(/"/g, '');
35                 const newImageHtml = `<img src="${resp.thumbs.display}" alt="${safeName}" />`;
36
37                 const newEl = editor.dom.create('a', {
38                     target: '_blank',
39                     href: resp.url,
40                 }, newImageHtml);
41
42                 editor.dom.replace(newEl, id);
43             }).catch(err => {
44                 editor.dom.remove(id);
45                 window.$events.emit('error', options.translations.imageUploadErrorText);
46                 console.log(err);
47             });
48         }, 10);
49     }
50 }
51
52 /**
53  * Upload an image file to the server
54  * @param {File} file
55  * @param {int} pageId
56  */
57 async function uploadImageFile(file, pageId) {
58     if (file === null || file.type.indexOf('image') !== 0) {
59         throw new Error('Not an image file');
60     }
61
62     const remoteFilename = file.name || `image-${Date.now()}.png`;
63     const formData = new FormData();
64     formData.append('file', file, remoteFilename);
65     formData.append('uploaded_to', pageId);
66
67     const resp = await window.$http.post(window.baseUrl('/images/gallery'), formData);
68     return resp.data;
69 }
70
71 /**
72  * @param {Editor} editor
73  * @param {WysiwygConfigOptions} options
74  */
75 function dragStart(editor, options) {
76     const node = editor.selection.getNode();
77
78     if (node.nodeName === 'IMG') {
79         wrap = editor.dom.getParent(node, '.mceTemp');
80
81         if (!wrap && node.parentNode.nodeName === 'A' && !hasTextContent(node.parentNode)) {
82             wrap = node.parentNode;
83         }
84     }
85
86     // Track dragged contenteditable blocks
87     if (node.hasAttribute('contenteditable') && node.getAttribute('contenteditable') === 'false') {
88         draggedContentEditable = node;
89     }
90 }
91
92 /**
93  * @param {Editor} editor
94  * @param {WysiwygConfigOptions} options
95  * @param {DragEvent} event
96  */
97 function drop(editor, options, event) {
98     const {dom} = editor;
99     const rng = tinymce.dom.RangeUtils.getCaretRangeFromPoint(event.clientX, event.clientY, editor.getDoc());
100
101     // Template insertion
102     const templateId = event.dataTransfer && event.dataTransfer.getData('bookstack/template');
103     if (templateId) {
104         event.preventDefault();
105         window.$http.get(`/templates/${templateId}`).then(resp => {
106             editor.selection.setRng(rng);
107             editor.undoManager.transact(() => {
108                 editor.execCommand('mceInsertContent', false, resp.data.html);
109             });
110         });
111     }
112
113     // Don't allow anything to be dropped in a captioned image.
114     if (dom.getParent(rng.startContainer, '.mceTemp')) {
115         event.preventDefault();
116     } else if (wrap) {
117         event.preventDefault();
118
119         editor.undoManager.transact(() => {
120             editor.selection.setRng(rng);
121             editor.selection.setNode(wrap);
122             dom.remove(wrap);
123         });
124     }
125
126     // Handle contenteditable section drop
127     if (!event.isDefaultPrevented() && draggedContentEditable) {
128         event.preventDefault();
129         editor.undoManager.transact(() => {
130             const selectedNode = editor.selection.getNode();
131             const range = editor.selection.getRng();
132             const selectedNodeRoot = selectedNode.closest('body > *');
133             if (range.startOffset > (range.startContainer.length / 2)) {
134                 selectedNodeRoot.after(draggedContentEditable);
135             } else {
136                 selectedNodeRoot.before(draggedContentEditable);
137             }
138         });
139     }
140
141     // Handle image insert
142     if (!event.isDefaultPrevented()) {
143         paste(editor, options, event);
144     }
145
146     wrap = null;
147 }
148
149 /**
150  * @param {Editor} editor
151  * @param {WysiwygConfigOptions} options
152  */
153 export function listenForDragAndPaste(editor, options) {
154     editor.on('dragstart', () => dragStart(editor, options));
155     editor.on('drop', event => drop(editor, options, event));
156     editor.on('paste', event => paste(editor, options, event));
157 }