/** * Attaches diagram functionality in the MediaManager and MediaPopup */ class DiagramsMediaManager { /** * Attach the handlers */ constructor() { const tree = document.querySelector('#media__tree'); if (tree) { const createLink = document.createElement('a'); createLink.addEventListener('click', this.#showCreationDialog.bind(this)); createLink.className = 'plugin_diagrams_create'; createLink.innerText = LANG.plugins.diagrams.createLink; createLink.href = '#'; tree.prepend(createLink); } const filePanel = document.querySelector('#mediamanager__page .panel.file'); if (filePanel) { const observer = new MutationObserver(this.#addEditButton); observer.observe(filePanel, {childList: true, subtree: true}); } } /** * Observer callback to add the edit button in the detail panel of the media manager * * @param mutationsList * @param observer */ async #addEditButton(mutationsList, observer) { for (let mutation of mutationsList) { // div.file has been filled with new content? if (mutation.type !== 'childList') continue; // is it an SVG file? const svgLink = mutation.target.querySelector('a.mf_svg'); if (!svgLink) continue; const actionList = mutation.target.querySelector('ul.actions'); if (actionList.querySelector('button.diagrams-btn')) continue; // already added // ensure media file is actually an editable diagram const response = await fetch( DOKU_BASE + 'lib/exe/ajax.php?call=plugin_diagrams_mediafile_editcheck&diagrams=' + encodeURIComponent(JSON.stringify([svgLink.textContent])), { method: 'GET', cache: 'no-cache', } ); if (response.ok && (await response.json())[0] === svgLink.textContent) { const editButton = document.createElement('button'); editButton.classList.add('diagrams-btn'); editButton.innerText = LANG.plugins.diagrams.editButton; editButton.addEventListener('click', async () => { const editor = new DiagramsEditor(); await editor.editMediaFile(svgLink.textContent); }); actionList.appendChild(editButton); } } } /** * Show the dialog to create a new diagram * * Uses JQuery UI * * @param {Event} event * @returns {Promise} */ async #showCreationDialog(event) { event.preventDefault(); event.stopPropagation(); const namespace = this.#getNamespace(); if (!await this.#checkACLs(namespace)) { alert(LANG.plugins.diagrams.createForbidden); return; } const $form = jQuery(this.#buildForm(namespace)); $form.dialog({ title: LANG.plugins.diagrams.createLink, width: 600, appendTo: '.dokuwiki', modal: true, close: function () { // do not reuse the dialog // https://stackoverflow.com/a/2864783 jQuery(this).dialog('destroy').remove(); } }); } /** * Check if the user has the right to create a diagram in the given namespace * * @param {string} namespace * @returns {Promise} */ async #checkACLs(namespace) { const url = DOKU_BASE + 'lib/exe/ajax.php' + '?call=plugin_diagrams_mediafile_nscheck' + '&ns=' + encodeURIComponent(namespace); const response = await fetch(url, { cache: 'no-cache', }); return response.json(); } /** * Extract the namespace from the page * * @returns {string} */ #getNamespace() { const fullScreenNS = document.querySelector('#mediamanager__page .panelHeader h3 strong'); const popupNS = document.querySelector('#media__manager #media__ns'); let namespace = ''; if (fullScreenNS) { namespace = fullScreenNS.textContent; } else if (popupNS) { namespace = popupNS.textContent; } else { throw new Error('Could not find namespace'); //should not happen } // Media Manager encloses the root dir in [] so let's strip that // because it is not a real namespace return namespace.replace(/^:|\[.*\]$/, ''); } /** * Create the form to ask for the diagram name * @param ns * @returns {HTMLDivElement} */ #buildForm(ns) { const wrapper = document.createElement('div'); const form = document.createElement('form'); wrapper.appendChild(form); const intro = document.createElement('p'); intro.innerText = LANG.plugins.diagrams.createIntro; const namespace = document.createElement('strong'); namespace.innerText = ':'+ns; intro.appendChild(namespace); form.appendChild(intro); const input = document.createElement('input'); input.type = 'text'; input.className = 'edit'; input.name = 'diagrams-create-filename'; form.appendChild(input); const button = document.createElement('button'); button.innerText = LANG.plugins.diagrams.createButton; form.appendChild(button); form.addEventListener('submit', this.#createDiagram.bind(this, ns, input)); return wrapper; } /** * Open the diagram editor for the given namespace and filename * * @param {string} namespace The current namespace * @param {HTMLInputElement} input The input element containing the filename * @param {Event} event */ async #createDiagram(namespace, input, event) { event.preventDefault(); event.stopPropagation(); const id = input.value; // check for validity if (id.length < 0 || !/^[\w][\w\.\-]*$/.test(id)) { alert(LANG.plugins.diagrams.errorInvalidId); return; } const svg = namespace + ':' + id + '.svg'; if (await this.#checkOverwrite(svg)) { alert('File exists already! Choose a different name!'); return; } const editor = new DiagramsEditor(() => { let url = new URL(window.location.href); url.searchParams.set('ns', namespace); // these will be ignored in the popup: url.searchParams.set('image', svg); url.searchParams.set('tab_details', 'view'); url.searchParams.set('tab_files', 'files'); window.location.href = url.toString(); }); editor.editMediaFile(svg); } /** * Check with backend if a file with the given name already exists * * @param {string} svg * @returns {Promise} */ async #checkOverwrite(svg) { const url = DOKU_BASE + 'lib/exe/ajax.php' + '?call=plugin_diagrams_mediafile_existscheck' + '&mediaId=' + encodeURIComponent(svg); const response = await fetch(url, { cache: 'no-cache', }); return response.json(); } } // initialize document.addEventListener('DOMContentLoaded', () => { new DiagramsMediaManager(); });