1/** 2 * Attaches diagram functionality in the MediaManager and MediaPopup 3 */ 4class DiagramsMediaManager { 5 6 /** 7 * Attach the handlers 8 */ 9 constructor() { 10 const tree = document.querySelector('#media__tree'); 11 if (tree) { 12 const createLink = document.createElement('a'); 13 createLink.addEventListener('click', this.#showCreationDialog.bind(this)); 14 createLink.className = 'plugin_diagrams_create'; 15 createLink.innerText = LANG.plugins.diagrams.createLink; 16 createLink.href = '#'; 17 tree.prepend(createLink); 18 } 19 20 const filePanel = document.querySelector('#mediamanager__page .panel.file'); 21 if (filePanel) { 22 const observer = new MutationObserver(this.#addEditButton); 23 observer.observe(filePanel, {childList: true, subtree: true}); 24 } 25 } 26 27 /** 28 * Observer callback to add the edit button in the detail panel of the media manager 29 * 30 * @param mutationsList 31 * @param observer 32 */ 33 async #addEditButton(mutationsList, observer) { 34 for (let mutation of mutationsList) { 35 // div.file has been filled with new content? 36 if (mutation.type !== 'childList') continue; 37 38 // is it an SVG file? 39 const svgLink = mutation.target.querySelector('a.mf_svg'); 40 if (!svgLink) continue; 41 42 const actionList = mutation.target.querySelector('ul.actions'); 43 if (actionList.querySelector('button.diagrams-btn')) continue; // already added 44 45 // ensure media file is actually an editable diagram 46 const response = await fetch( 47 DOKU_BASE + 'lib/exe/ajax.php?call=plugin_diagrams_mediafile_editcheck&diagrams=' + 48 encodeURIComponent(JSON.stringify([svgLink.textContent])), 49 { 50 method: 'GET', 51 cache: 'no-cache', 52 } 53 ); 54 55 if (response.ok && (await response.json())[0] === svgLink.textContent) { 56 const editButton = document.createElement('button'); 57 editButton.classList.add('diagrams-btn'); 58 editButton.innerText = LANG.plugins.diagrams.editButton; 59 editButton.addEventListener('click', async () => { 60 const editor = new DiagramsEditor(); 61 await editor.editMediaFile(svgLink.textContent); 62 }); 63 actionList.appendChild(editButton); 64 } 65 } 66 } 67 68 /** 69 * Show the dialog to create a new diagram 70 * 71 * Uses JQuery UI 72 * 73 * @param {Event} event 74 * @returns {Promise<void>} 75 */ 76 async #showCreationDialog(event) { 77 event.preventDefault(); 78 event.stopPropagation(); 79 const namespace = this.#getNamespace(); 80 81 if (!await this.#checkACLs(namespace)) { 82 alert(LANG.plugins.diagrams.createForbidden); 83 return; 84 } 85 86 const $form = jQuery(this.#buildForm(namespace)); 87 $form.dialog({ 88 title: LANG.plugins.diagrams.createLink, 89 width: 600, 90 appendTo: '.dokuwiki', 91 modal: true, 92 close: function () { 93 // do not reuse the dialog 94 // https://stackoverflow.com/a/2864783 95 jQuery(this).dialog('destroy').remove(); 96 } 97 }); 98 } 99 100 /** 101 * Check if the user has the right to create a diagram in the given namespace 102 * 103 * @param {string} namespace 104 * @returns {Promise<boolean>} 105 */ 106 async #checkACLs(namespace) { 107 const url = DOKU_BASE + 'lib/exe/ajax.php' + 108 '?call=plugin_diagrams_mediafile_nscheck' + 109 '&ns=' + encodeURIComponent(namespace); 110 111 112 const response = await fetch(url, { 113 cache: 'no-cache', 114 }); 115 116 return response.json(); 117 } 118 119 /** 120 * Extract the namespace from the page 121 * 122 * @returns {string} 123 */ 124 #getNamespace() { 125 const fullScreenNS = document.querySelector('#mediamanager__page .panelHeader h3 strong'); 126 const popupNS = document.querySelector('#media__manager #media__ns'); 127 128 let namespace = ''; 129 if (fullScreenNS) { 130 namespace = fullScreenNS.textContent; 131 } else if (popupNS) { 132 namespace = popupNS.textContent; 133 } else { 134 throw new Error('Could not find namespace'); //should not happen 135 } 136 137 // Media Manager encloses the root dir in [] so let's strip that 138 // because it is not a real namespace 139 return namespace.replace(/^:|\[.*\]$/, ''); 140 } 141 142 /** 143 * Create the form to ask for the diagram name 144 * @param ns 145 * @returns {HTMLDivElement} 146 */ 147 #buildForm(ns) { 148 const wrapper = document.createElement('div'); 149 const form = document.createElement('form'); 150 wrapper.appendChild(form); 151 152 const intro = document.createElement('p'); 153 intro.innerText = LANG.plugins.diagrams.createIntro; 154 const namespace = document.createElement('strong'); 155 namespace.innerText = ':'+ns; 156 intro.appendChild(namespace); 157 form.appendChild(intro); 158 159 const input = document.createElement('input'); 160 input.type = 'text'; 161 input.className = 'edit'; 162 input.name = 'diagrams-create-filename'; 163 form.appendChild(input); 164 165 const button = document.createElement('button'); 166 button.innerText = LANG.plugins.diagrams.createButton; 167 form.appendChild(button); 168 169 form.addEventListener('submit', this.#createDiagram.bind(this, ns, input)); 170 return wrapper; 171 } 172 173 /** 174 * Open the diagram editor for the given namespace and filename 175 * 176 * @param {string} namespace The current namespace 177 * @param {HTMLInputElement} input The input element containing the filename 178 * @param {Event} event 179 */ 180 async #createDiagram(namespace, input, event) { 181 event.preventDefault(); 182 event.stopPropagation(); 183 184 const id = input.value; 185 186 // check for validity 187 if (id.length < 0 || !/^[\w][\w\.\-]*$/.test(id)) { 188 alert(LANG.plugins.diagrams.errorInvalidId); 189 return; 190 } 191 const svg = namespace + ':' + id + '.svg'; 192 193 if (await this.#checkOverwrite(svg)) { 194 alert('File exists already! Choose a different name!'); 195 return; 196 } 197 198 const editor = new DiagramsEditor(() => { 199 let url = new URL(window.location.href); 200 url.searchParams.set('ns', namespace); 201 // these will be ignored in the popup: 202 url.searchParams.set('image', svg); 203 url.searchParams.set('tab_details', 'view'); 204 url.searchParams.set('tab_files', 'files'); 205 window.location.href = url.toString(); 206 }); 207 editor.editMediaFile(svg); 208 } 209 210 /** 211 * Check with backend if a file with the given name already exists 212 * 213 * @param {string} svg 214 * @returns {Promise<boolean>} 215 */ 216 async #checkOverwrite(svg) { 217 const url = DOKU_BASE + 'lib/exe/ajax.php' + 218 '?call=plugin_diagrams_mediafile_existscheck' + 219 '&mediaId=' + encodeURIComponent(svg); 220 221 const response = await fetch(url, { 222 cache: 'no-cache', 223 }); 224 225 return response.json(); 226 } 227} 228 229// initialize 230document.addEventListener('DOMContentLoaded', () => { 231 new DiagramsMediaManager(); 232}); 233