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