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 #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 // create the button FIXME shouldn't we check if this is a diagram? 46 const editButton = document.createElement('button'); 47 editButton.classList.add('diagrams-btn'); 48 editButton.innerText = LANG.plugins.diagrams.editButton; 49 editButton.addEventListener('click', async () => { 50 const editor = new DiagramsEditor(); 51 await editor.editMediaFile(svgLink.innerText); 52 }); 53 actionList.appendChild(editButton); 54 } 55 } 56 57 /** 58 * Show the dialog to create a new diagram 59 * 60 * Uses JQuery UI 61 * 62 * @param {Event} event 63 * @returns {Promise<void>} 64 */ 65 async #showCreationDialog(event) { 66 event.preventDefault(); 67 event.stopPropagation(); 68 const namespace = this.#getNamespace(); 69 70 if (!await this.#checkACLs(namespace)) { 71 alert(LANG.plugins.diagrams.createForbidden); 72 return; 73 } 74 75 const $form = jQuery(this.#buildForm(namespace)); 76 $form.dialog({ 77 title: LANG.plugins.diagrams.createLink, 78 width: 600, 79 appendTo: '.dokuwiki', 80 modal: true, 81 close: function () { 82 // do not reuse the dialog 83 // https://stackoverflow.com/a/2864783 84 jQuery(this).dialog('destroy').remove(); 85 } 86 }); 87 } 88 89 /** 90 * Check if the user has the right to create a diagram in the given namespace 91 * 92 * @param {string} namespace 93 * @returns {Promise<boolean>} 94 */ 95 async #checkACLs(namespace) { 96 const url = DOKU_BASE + 'lib/exe/ajax.php' + 97 '?call=plugin_diagrams_mediafile_nscheck' + 98 '&ns=' + encodeURIComponent(namespace); 99 100 101 const response = await fetch(url, { 102 cache: 'no-cache', 103 }); 104 105 return response.json(); 106 } 107 108 /** 109 * Extract the namespace from the page 110 * 111 * @returns {string} 112 */ 113 #getNamespace() { 114 const fullScreenNS = document.querySelector('#mediamanager__page .panelHeader h3 strong'); 115 const popupNS = document.querySelector('#media__manager #media__ns'); 116 117 let namespace = ''; 118 if (fullScreenNS) { 119 namespace = fullScreenNS.innerText; 120 } else if (popupNS) { 121 namespace = popupNS.innerText; 122 } else { 123 throw new Error('Could not find namespace'); //should not happen 124 } 125 126 // Media Manager encloses the root dir in [] so let's strip that 127 // because it is not a real namespace 128 return namespace.replace(/^:|\[.*\]$/, ''); 129 } 130 131 /** 132 * Create the form to ask for the diagram name 133 * @param ns 134 * @returns {HTMLDivElement} 135 */ 136 #buildForm(ns) { 137 const wrapper = document.createElement('div'); 138 const form = document.createElement('form'); 139 wrapper.appendChild(form); 140 141 const intro = document.createElement('p'); 142 intro.innerText = LANG.plugins.diagrams.createIntro; 143 const namespace = document.createElement('strong'); 144 namespace.innerText = ':'+ns; 145 intro.appendChild(namespace); 146 form.appendChild(intro); 147 148 const input = document.createElement('input'); 149 input.type = 'text'; 150 input.className = 'edit'; 151 input.name = 'diagrams-create-filename'; 152 form.appendChild(input); 153 154 const button = document.createElement('button'); 155 button.innerText = LANG.plugins.diagrams.createButton; 156 form.appendChild(button); 157 158 form.addEventListener('submit', this.#createDiagram.bind(this, ns, input)); 159 return wrapper; 160 } 161 162 /** 163 * Open the diagram editor for the given namespace and filename 164 * 165 * @param {string} namespace The current namespace 166 * @param {HTMLInputElement} input The input element containing the filename 167 * @param {Event} event 168 */ 169 #createDiagram(namespace, input, event) { 170 event.preventDefault(); 171 event.stopPropagation(); 172 173 const id = input.value; 174 175 // check for validity 176 if (id.length < 0 || !/^[\w][\w\.\-]*$/.test(id)) { 177 alert(LANG.plugins.diagrams.errorInvalidId); 178 return; 179 } 180 const svg = namespace + ':' + id + '.svg'; 181 182 const editor = new DiagramsEditor(() => { 183 let url = new URL(window.location.href); 184 url.searchParams.set('ns', namespace); 185 // these will be ignored in the popup: 186 url.searchParams.set('image', svg); 187 url.searchParams.set('tab_details', 'view'); 188 url.searchParams.set('tab_files', 'files'); 189 window.location.href = url.toString(); 190 }); 191 editor.editMediaFile(svg); 192 } 193} 194 195// initialize 196document.addEventListener('DOMContentLoaded', () => { 197 new DiagramsMediaManager(); 198}); 199