xref: /plugin/diagrams/script/DiagramsMediaManager.js (revision a561c5b18fa1bef7343a9999f88e1f2e9f88fcd8)
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