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