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