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