// ==UserScript== // @name academyFIVE::ResizeNavbar2 // @namespace dakp/academyfive // @version 2026-06-19 // @description Ermöglicht es, die Breite des Baummenüs per Drag & Drop dynamisch anzupassen. Die Icons laufen dabei automatisch mit. // @author Dims Akpan // @match https://a5.fhdw-hannover.de/* // @match https://a5.fhdw.de/* // @icon https://www.academyfive.com/typo3conf/ext/sitepackage/Resources/Public/build/assets/images/favicon-academyfive.ico // @grant none // ==/UserScript== (function() { 'use strict'; const MIN_WIDTH = 300; const MAX_WIDTH_FACTOR = 0.5; // 50% des Viewports const ICON_OFFSET = 80; // Abstand der controlLayer-Icons vom rechten Rand der Navbar const iframe = document.getElementById('nav'); const navbarCell = document.getElementById('navTree'); if (!iframe || !navbarCell) { return; } // --- Unsichtbarer Resize-Griff am rechten Rand der Navbar --- const navbarCellPosition = window.getComputedStyle(navbarCell).position; if (navbarCellPosition === 'static') { navbarCell.style.position = 'relative'; } const grip = document.createElement('div'); grip.style.position = 'absolute'; grip.style.top = '0'; grip.style.right = '0'; grip.style.bottom = '0'; grip.style.width = '6px'; grip.style.background = 'transparent'; grip.style.cursor = 'ew-resize'; grip.style.zIndex = '9999'; grip.style.transition = 'background 0.15s ease'; navbarCell.appendChild(grip); let isDragging = false; let startX = 0; let startWidth = 0; let activePointerId = null; /** Beschränkt einen Wert auf den Bereich [min, max]. */ function clamp(value, min, max) { if (value < min) return min; if (value > max) return max; return value; } /** Setzt die Breite von navbarCell und iframe auf den angegebenen Wert. */ function applyWidth(newWidth) { navbarCell.style.width = newWidth + 'px'; navbarCell.setAttribute('width', newWidth); iframe.style.width = newWidth + 'px'; iframe.setAttribute('width', newWidth); } grip.addEventListener('mouseenter', function() { grip.style.background = 'rgba(0, 0, 0, 0.25)'; }); grip.addEventListener('mouseleave', function() { if (!isDragging) { grip.style.background = 'transparent'; } }); grip.addEventListener('pointerdown', function(e) { if (e.button !== 0) { return; } isDragging = true; startX = e.clientX; startWidth = navbarCell.offsetWidth; activePointerId = e.pointerId; grip.setPointerCapture(e.pointerId); document.body.style.userSelect = 'none'; document.body.style.cursor = 'ew-resize'; e.preventDefault(); }); grip.addEventListener('pointermove', function(e) { if (!isDragging) { return; } const delta = e.clientX - startX; const maxWidth = window.innerWidth * MAX_WIDTH_FACTOR; applyWidth(clamp(startWidth + delta, MIN_WIDTH, maxWidth)); }); /** Beendet den Drag-Vorgang und setzt Cursor/Selektion zurück. */ function endDrag() { if (!isDragging) { return; } isDragging = false; document.body.style.userSelect = ''; document.body.style.cursor = ''; grip.style.background = 'transparent'; if (activePointerId !== null) { try { grip.releasePointerCapture(activePointerId); } catch (err) { // bereits aufgeloest } activePointerId = null; } } grip.addEventListener('pointerup', endDrag); grip.addEventListener('pointercancel', endDrag); // --- controlLayer-Icons synchron mit der Navbar-Breite bewegen --- // // Wichtig: controlLayer lebt INNERHALB des iframes (nav.php4). // Wir muessen ueber iframe.contentDocument darauf zugreifen. // Das iframe wird auch durch Klick auf das "Aktualisieren"-Icon // neu geladen -> wir reagieren auf jeden load-Event. /** Verschiebt die controlLayer-Icons im iframe passend zur aktuellen Navbar-Breite. */ function syncControlLayer() { let iframeDoc; try { iframeDoc = iframe.contentDocument; } catch (err) { // Same-Origin verletzt return; } if (!iframeDoc) { return; } const controlLayer = iframeDoc.getElementById('controlLayer'); if (!controlLayer) { return; } const iconLeft = navbarCell.offsetWidth - ICON_OFFSET; controlLayer.style.left = iconLeft + 'px'; } // Wenn das iframe schon fertig ist, sofort synchronisieren, // ansonsten auf das load-Event warten. /** Führt syncControlLayer() aus, sofern das iframe bereits vollständig geladen ist. */ function trySetupControlLayer() { if (iframe.contentDocument && iframe.contentDocument.readyState === 'complete') { syncControlLayer(); } } iframe.addEventListener('load', function() { // Kurze Verzoegerung: das DOM des iframes ist zwar 'complete', // aber einzelne Elemente werden teils erst danach verfuegbar. setTimeout(syncControlLayer, 50); }); // Erst-Initialisierung trySetupControlLayer(); // Reagiert auf Aenderungen der Navbar-Groesse if (typeof ResizeObserver !== 'undefined') { new ResizeObserver(syncControlLayer).observe(navbarCell); } // Bei Fenster-Resize aendert sich ggf. die Maximalbreite window.addEventListener('resize', syncControlLayer); // --- showHideTree() ueberschreiben, damit Einklappen auch bei aktivem // Resize-Griff funktioniert. Die Originalfunktion setzt nur das HTML- // Attribut "width", aber unser Inline-CSS (style.width) hat Vorrang. // Wir rufen das Original danach trotzdem auf, falls es noch mehr macht. const originalShowHideTree = window.showHideTree; if (typeof originalShowHideTree !== 'function') { window.showHideTree = function() {}; } window.showHideTree = (function(original) { return function() { const navExpandCell = jQuery('#navigationExpandCell'); // expandCell sichtbar = Nav ist eingeklappt -> wir wollen ausklappen const navIsCollapsed = navExpandCell.css('display') !== 'none'; if (navIsCollapsed) { applyWidth(MIN_WIDTH); } else { applyWidth(0); } return original.apply(this, arguments); }; })(window.showHideTree); })();