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