xref: /plugin/catmenu/script.js (revision 6983cdfd4483215ff5a1e573925c9c612964e790)
1(function loadCatmenuProsemirrorAddon() {
2    if (window.__catmenuPmAddonRequested) return;
3    window.__catmenuPmAddonRequested = true;
4
5    var base = (typeof DOKU_BASE !== 'undefined' && DOKU_BASE) ? DOKU_BASE : '/';
6    var src = base + 'lib/plugins/catmenu/script/prosemirror.js';
7    var script = document.createElement('script');
8    script.src = src;
9    script.defer = true;
10    document.head.appendChild(script);
11})();
12
13function catmenu_adjustSubmenuHeight(submenu, menuContainer) {
14    var allTitles = menuContainer.querySelectorAll(`.menu-item .header:not(.menu-item .menu-item .header)`);
15
16    // Hauteur totale du conteneur
17    var containerHeight = menuContainer.clientHeight;
18
19    // Calculer la hauteur des titres en incluant bordures, padding et margin-top
20    var titlesHeight = Array.from(allTitles).reduce((sum, title) => {
21        var computedStyle = window.getComputedStyle(title.parentElement);
22        var marginTop = parseInt(computedStyle.marginTop) || 0;
23        return sum + title.offsetHeight + marginTop;
24    }, 0);
25
26    // Définir la hauteur maximale en prenant en compte le padding et le margin-top
27    var availableHeight = Math.max(Math.max(containerHeight, window.innerHeight) - titlesHeight - menuContainer.getBoundingClientRect().top, 500);
28
29    submenu.style.maxHeight = availableHeight + "px";
30
31    return availableHeight;
32}
33
34function copyToClipboard(text) {
35    if (!text) return;
36
37    // Méthode moderne (HTTPS requis)
38    if (navigator.clipboard && window.isSecureContext) {
39        navigator.clipboard.writeText(text)
40            .then(() => {
41                alert("URL copiée dans le presse-papiers !");
42            })
43            .catch(err => {
44                console.error("Erreur lors de la copie :", err);
45            });
46    }
47    // Fallback pour anciens navigateurs
48    else {
49        const textarea = document.createElement("textarea");
50        textarea.value = text;
51        textarea.style.position = "fixed";
52        textarea.style.opacity = "0";
53        document.body.appendChild(textarea);
54        textarea.focus();
55        textarea.select();
56
57        try {
58            document.execCommand("copy");
59            alert("URL copiée dans le presse-papiers !");
60        } catch (err) {
61            console.error("Erreur lors de la copie :", err);
62        }
63
64        document.body.removeChild(textarea);
65    }
66}
67
68// Appeler cette fonction à l'ouverture d'un sous-menu
69function catmenu_toggleSectionMenu(menuItem, menuContainer) {
70    let isOpen = !menuItem.classList.contains('open');
71
72    let topMenuItems = Array.from(menuContainer.querySelectorAll('.menu-item:not(.menu-item .menu-item)'));
73
74    let currentTopMenu = topMenuItems.find(item => item.contains(menuItem));
75    let othersTopMenu = Array.from(topMenuItems).filter(item => item !== currentTopMenu);
76
77    othersTopMenu.forEach(item => {
78        item.classList.remove("open");
79        let submenu = item.getElementsByClassName('submenu')[0];
80        if(submenu) {
81            submenu.style.maxHeight = null;
82        }
83    });
84
85    let currentTopSubmenu = currentTopMenu.getElementsByClassName('submenu')[0];
86    if(isOpen) {
87        let newHeight = catmenu_adjustSubmenuHeight(currentTopSubmenu, menuContainer);
88
89        setTimeout(() => {
90            let hasOverflow = currentTopSubmenu.scrollHeight > newHeight;
91            if(hasOverflow) {
92                console.log(menuItem.offsetTop-currentTopSubmenu.offsetTop, currentTopSubmenu, currentTopSubmenu.offsetTop, menuItem, menuItem.offsetTop);
93                currentTopSubmenu.scrollTo({
94                    top: menuItem.offsetTop-currentTopSubmenu.offsetTop-5-10
95                });
96            }
97        }, 0);
98    }
99    else {
100        currentTopSubmenu.style.maxHeight = null;
101    }
102    menuItem.classList.toggle('open');
103}
104
105function catmenu_generateSectionMenu(conf, menuData, parentElement, menuContainer = null) {
106    const URL_SEPARATOR = conf.userewrite == 1? '/' : ':';
107
108    const AUTH_READ = 1;
109    const AUTH_EDIT = 2;
110    const AUTH_CREATE = 4;
111    const AUTH_UPLOAD = 8;
112    const AUTH_DELETE = 16;
113
114    menuData.forEach(item => { // Pour tous les éléments
115        let menuItem = document.createElement("div");
116        menuItem.classList.add("menu-item");
117
118        let header = document.createElement("div");
119        header.classList.add("header");
120        header.dataset.folderNamespace = item.folderNamespace;
121        header.dataset.permission = item.permission;
122        header.dataset.pagesiconUploadUrl = item.pagesiconUploadUrl || '';
123
124        if (item.icon) {
125            let icon = document.createElement("img");
126            icon.classList.add("icon");
127            icon.loading = "lazy";
128            icon.src = item.icon;
129            header.appendChild(icon);
130        }
131
132        let title;
133        if(item.url) {
134            title = document.createElement("a");
135            title.href = item.url;
136            header.dataset.href = item.url;
137        }
138        else {
139            title = document.createElement("span");
140        }
141        title.textContent = item.title;
142        header.title = item.title;
143        header.appendChild(title);
144
145        let isCurrent = (':' + JSINFO.id + ':').indexOf(':' + item.namespace + ':') >= 0;
146        if(isCurrent) {
147            header.classList.add("current");
148            menuItem.classList.add("open");
149        }
150        menuItem.appendChild(header);
151
152        parentElement.appendChild(menuItem);
153
154        if (item.subtree && item.subtree.length > 0) {
155            header.classList.add("arrow");
156
157            let submenu = document.createElement("div");
158            submenu.classList.add("submenu");
159
160            catmenu_generateSectionMenu(conf, item.subtree, submenu, menuContainer ?? parentElement);
161            menuItem.appendChild(submenu);
162
163            header.addEventListener("click", (event) => {
164                const isLinkClick = !!event.target.closest("a");
165                if (isLinkClick) {
166                    // Clicking section link should never close it.
167                    if (!menuItem.classList.contains("open")) {
168                        catmenu_toggleSectionMenu(menuItem, menuContainer ?? parentElement);
169                    }
170                    return;
171                }
172                catmenu_toggleSectionMenu(menuItem, menuContainer ?? parentElement);
173            });
174        }
175    });
176
177
178    if(!menuContainer) {
179        window.addEventListener('resize', () => {
180            let openedTopSubmenu = parentElement.querySelector('.menu-item:not(.menu-item .menu-item).open > .submenu');
181            if(openedTopSubmenu) {
182                catmenu_adjustSubmenuHeight(openedTopSubmenu, parentElement);
183            }
184        }, false);
185
186        let contextMenu = document.getElementById('catmenu_contextMenu');
187        if(!contextMenu) {
188            contextMenu = document.createElement('div');
189            contextMenu.id = 'catmenu_contextMenu';
190            document.body.appendChild(contextMenu);
191            document.addEventListener('click', function() {
192                contextMenu.style.display = 'none';
193            });
194
195            contextMenu.addEventListener('click', function() {
196                console.log(`Click`, contextMenu.dataset.namespace);
197            });
198        }
199
200        function appendToUrl(base, params) {
201            return base + (base.indexOf('?') >= 0? '&' : '?') + params;
202        }
203
204        document.addEventListener('contextmenu', function(event) {
205            let header = event.target.closest(".header"); // Vérifie si clic sur un menu
206            if (!header || !header.closest('.catmenu')) {
207                contextMenu.style.display = 'none';
208                return;
209            }
210
211            if(!parentElement.contains(header)) {
212                return;
213            }
214            event.preventDefault(); // Empêche le menu par défaut
215
216            let permission = header.dataset.permission;
217
218            let htmlActions = '';
219            if(permission >= AUTH_CREATE) {
220                let baseHref = header.dataset.href;
221                if(baseHref.endsWith(URL_SEPARATOR + conf.start)) {
222                    baseHref = baseHref.slice(0, -(URL_SEPARATOR + conf.start).length);
223                }
224                htmlActions += '<div class="button" data-action="newPage" onclick="let nomPage = prompt(\'Identifiant de la page à créer ?\'); if(nomPage) window.location.href = \'' + appendToUrl(baseHref + URL_SEPARATOR + '\' + encodeURIComponent(nomPage) + \'' + URL_SEPARATOR + conf.start, 'do=edit') + '\';">�� Créer une nouvelle page</div>';
225            }
226            if(header.dataset.href && permission >= AUTH_EDIT) {
227                htmlActions += '<a class="button" data-action="reload" href="' + appendToUrl(header.dataset.href, 'purge=true') + '">�� Recharger le cache</a>';
228            }
229            if(permission >= AUTH_UPLOAD) {
230                htmlActions += '<a class="button" data-action="medias" target="_blank" href="' + appendToUrl('/lib/exe/mediamanager.php', 'ns=' + header.dataset.folderNamespace) + '" onclick="event.preventDefault();window.open(\'' + appendToUrl('/lib/exe/mediamanager.php', 'ns=' + header.dataset.folderNamespace) + '\', \'MediasPopup\', \'width=800,height=600,resizable=yes,scrollbars=yes\');">��️ Gérer les médias</a>';
231                if (header.dataset.pagesiconUploadUrl) {
232                    htmlActions += '<a class="button" data-action="icon" target="_blank" href="' + header.dataset.pagesiconUploadUrl + '">��️ Gérer l\'icône</a>';
233                }
234            }
235            htmlActions += '<div class="button" data-action="copy" onclick="copyToClipboard(\'' + header.dataset.href + '\')">�� Copier l\'url</div>';
236
237            if(htmlActions) {
238                let html = '<p><b>' + header.innerText + '</b></p>' + htmlActions;
239                contextMenu.style.left = `${event.clientX}px`;
240                contextMenu.style.top = `${event.clientY}px`;
241                contextMenu.style.display = "block";
242                contextMenu.innerHTML = html;
243
244                // Stocke l'élément cliqué pour utilisation
245                contextMenu.dataset.namespace = header.dataset.namespace;
246                contextMenu.dataset.folderNamespace = header.dataset.folderNamespace;
247            }
248        }, false);
249    }
250}
251
252// The syntax renderer calls this function from inline <script>.
253// Expose it explicitly on window because bundled scopes may hide declarations.
254if (typeof window !== 'undefined') {
255    window.catmenu_generateSectionMenu = catmenu_generateSectionMenu;
256    if (typeof document !== 'undefined') {
257        document.dispatchEvent(new Event('catmenu:ready'));
258    }
259}
260