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