1(function () { 2 3 /** 4 * Handle pasting of files 5 * 6 * @param {ClipboardEvent} e 7 */ 8 function handlePaste(e) { 9 if (!document.getElementById('wiki__text')) return; // only when editing 10 11 const items = (e.clipboardData || e.originalEvent.clipboardData).items; 12 13 14 // When running prosemirror, check for HTML paste first 15 if (typeof window.proseMirrorIsActive !== 'undefined' && window.proseMirrorIsActive === true) { 16 for (let index in items) { 17 const item = items[index]; 18 if (item.kind === 'string' && item.type === 'text/html') { 19 e.preventDefault(); 20 e.stopPropagation(); 21 22 item.getAsString(async html => { 23 html = await processHTML(html); 24 const pm = window.Prosemirror.view; 25 const parser = window.Prosemirror.classes.DOMParser.fromSchema(pm.state.schema); 26 const nodes = parser.parse(html); 27 pm.dispatch(pm.state.tr.replaceSelectionWith(nodes)); 28 } 29 ); 30 31 return; // we found an HTML item, no need to continue 32 } 33 } 34 } 35 36 // if we're still here, handle files 37 for (let index in items) { 38 const item = items[index]; 39 40 if (item.kind === 'file') { 41 const reader = new FileReader(); 42 reader.onload = event => { 43 uploadData(event.target.result); 44 }; 45 reader.readAsDataURL(item.getAsFile()); 46 47 // we had at least one file, prevent default 48 e.preventDefault(); 49 e.stopPropagation(); 50 } 51 } 52 } 53 54 /** 55 * Creates and shows the progress dialog 56 * 57 * @returns {HTMLDivElement} 58 */ 59 function progressDialog() { 60 // create dialog 61 const offset = document.querySelectorAll('.plugin_imagepaste').length * 3; 62 const box = document.createElement('div'); 63 box.className = 'plugin_imagepaste'; 64 box.innerText = LANG.plugins.imgpaste.inprogress; 65 box.style.position = 'fixed'; 66 box.style.top = offset + 'em'; 67 box.style.left = '1em'; 68 document.querySelector('.dokuwiki').append(box); 69 return box; 70 } 71 72 /** 73 * Processes the given HTML and downloads all images 74 * 75 * @param html 76 * @returns {Promise<HTMLDivElement>} 77 */ 78 async function processHTML(html) { 79 const box = progressDialog(); 80 81 const div = document.createElement('div'); 82 div.innerHTML = html; 83 const imgs = Array.from(div.querySelectorAll('img')); 84 await Promise.all(imgs.map(async img => { 85 if (!img.src.match(/^https?:\/\//i)) return; // skip non-external images 86 if (img.src.startsWith(DOKU_BASE)) return; // skip local images 87 88 try { 89 result = await downloadData(img.src); 90 img.src = result.url; 91 img.className = 'media'; 92 } catch (e) { 93 console.error(e); 94 } 95 })); 96 97 box.remove(); 98 return div; 99 } 100 101 /** 102 * Tell the backend to download the given URL and return the new ID 103 * 104 * @param {string} imgUrl 105 * @returns {Promise<object>} The JSON response 106 */ 107 async function downloadData(imgUrl) { 108 const formData = new FormData(); 109 formData.append('call', 'plugin_imgpaste'); 110 formData.append('url', imgUrl); 111 formData.append('id', JSINFO.id); 112 113 const response = await fetch( 114 DOKU_BASE + 'lib/exe/ajax.php', 115 { 116 method: 'POST', 117 body: formData 118 } 119 ); 120 121 if (!response.ok) { 122 throw new Error(response.statusText); 123 } 124 125 return await response.json(); 126 } 127 128 /** 129 * Uploads the given dataURL to the server and displays a progress dialog 130 * 131 * @param {string} dataURL 132 */ 133 function uploadData(dataURL) { 134 const box = progressDialog(); 135 136 // upload via AJAX 137 jQuery.ajax({ 138 url: DOKU_BASE + 'lib/exe/ajax.php', 139 type: 'POST', 140 data: { 141 call: 'plugin_imgpaste', 142 data: dataURL, 143 id: JSINFO.id 144 }, 145 146 // insert syntax and close dialog 147 success: function (data) { 148 box.classList.remove('info'); 149 box.classList.add('success'); 150 box.innerText = data.message; 151 setTimeout(() => { 152 box.remove(); 153 }, 1000); 154 insertSyntax(data.id); 155 }, 156 157 // display error and close dialog 158 error: function (xhr, status, error) { 159 box.classList.remove('info'); 160 box.classList.add('error'); 161 box.innerText = error; 162 setTimeout(() => { 163 box.remove(); 164 }, 1000); 165 } 166 }); 167 } 168 169 /** 170 * Inserts the given ID into the current editor 171 * 172 * @todo add support for other editors like CKEditor 173 * @param {string} id The newly uploaded file ID 174 */ 175 function insertSyntax(id) { 176 177 // TODO remove the "if" check after LinkWizard.createRelativeID() is available in stable (after Kaos) 178 if (typeof LinkWizard !== 'undefined' && typeof LinkWizard.createRelativeID === 'function') { 179 id = LinkWizard.createRelativeID(JSINFO.id, id); 180 } else { 181 id = ':' + id; 182 } 183 184 if (typeof window.proseMirrorIsActive !== 'undefined' && window.proseMirrorIsActive === true) { 185 const pm = window.Prosemirror.view; 186 const imageNode = pm.state.schema.nodes.image.create({id: id}); 187 pm.dispatch(pm.state.tr.replaceSelectionWith(imageNode)); 188 } else { 189 insertAtCarret('wiki__text', '{{' + id + '}}'); 190 } 191 } 192 193 // main 194 window.addEventListener('paste', handlePaste, true); 195 196})(); 197