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 img.dataset.relid = getRelativeID(result.id); 93 } catch (e) { 94 console.error(e); 95 } 96 })); 97 98 box.remove(); 99 return div; 100 } 101 102 /** 103 * Tell the backend to download the given URL and return the new ID 104 * 105 * @param {string} imgUrl 106 * @returns {Promise<object>} The JSON response 107 */ 108 async function downloadData(imgUrl) { 109 const formData = new FormData(); 110 formData.append('call', 'plugin_imgpaste'); 111 formData.append('url', imgUrl); 112 formData.append('id', JSINFO.id); 113 114 const response = await fetch( 115 DOKU_BASE + 'lib/exe/ajax.php', 116 { 117 method: 'POST', 118 body: formData 119 } 120 ); 121 122 if (!response.ok) { 123 throw new Error(response.statusText); 124 } 125 126 return await response.json(); 127 } 128 129 /** 130 * Uploads the given dataURL to the server and displays a progress dialog 131 * 132 * @param {string} dataURL 133 */ 134 function uploadData(dataURL) { 135 const box = progressDialog(); 136 137 // upload via AJAX 138 jQuery.ajax({ 139 url: DOKU_BASE + 'lib/exe/ajax.php', 140 type: 'POST', 141 data: { 142 call: 'plugin_imgpaste', 143 data: dataURL, 144 id: JSINFO.id 145 }, 146 147 // insert syntax and close dialog 148 success: function (data) { 149 box.classList.remove('info'); 150 box.classList.add('success'); 151 box.innerText = data.message; 152 setTimeout(() => { 153 box.remove(); 154 }, 1000); 155 insertSyntax(data.id); 156 }, 157 158 // display error and close dialog 159 error: function (xhr, status, error) { 160 box.classList.remove('info'); 161 box.classList.add('error'); 162 box.innerText = error; 163 setTimeout(() => { 164 box.remove(); 165 }, 1000); 166 } 167 }); 168 } 169 170 /** 171 * Create a link ID for the given ID, preferrably relative to the current page 172 * 173 * @param {string} id 174 * @returns {string} 175 */ 176 function getRelativeID(id) { 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 return id; 184 } 185 186 /** 187 * Inserts the given ID into the current editor 188 * 189 * @todo add support for other editors like CKEditor 190 * @param {string} id The newly uploaded file ID 191 */ 192 function insertSyntax(id) { 193 id = getRelativeID(id); 194 195 if (typeof window.proseMirrorIsActive !== 'undefined' && window.proseMirrorIsActive === true) { 196 const pm = window.Prosemirror.view; 197 const imageNode = pm.state.schema.nodes.image.create({id: id}); 198 pm.dispatch(pm.state.tr.replaceSelectionWith(imageNode)); 199 } else { 200 insertAtCarret('wiki__text', '{{' + id + '}}'); 201 } 202 } 203 204 // main 205 window.addEventListener('paste', handlePaste, true); 206 207})(); 208