From acdf14b2ba910ee274e3850915e5373b50499deb Mon Sep 17 00:00:00 2001 From: Dims Akpan Date: Mon, 22 Jun 2026 09:55:09 +0200 Subject: [PATCH] =?UTF-8?q?ResizeNavbar2=20hinzugef=C3=BCgt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Neues Skript zur dynamischen Anpassung der Navigationsleiste. --- academyFIVE__ResizeNavbar2.user.js | 194 +++++++++++++++++++++++++++++ 1 file changed, 194 insertions(+) create mode 100644 academyFIVE__ResizeNavbar2.user.js diff --git a/academyFIVE__ResizeNavbar2.user.js b/academyFIVE__ResizeNavbar2.user.js new file mode 100644 index 0000000..1bebd94 --- /dev/null +++ b/academyFIVE__ResizeNavbar2.user.js @@ -0,0 +1,194 @@ +// ==UserScript== +// @name academyFIVE::ResizeNavbar +// @namespace tvog/academyfive +// @version 2024-05-16 +// @description Ermöglicht es, die Breite des Baummenüs per Drag & Drop dynamisch anzupassen. Die Icons laufen dabei automatisch mit. +// @author Tobias Vogler +// @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; + + function clamp(value, min, max) { + if (value < min) return min; + if (value > max) return max; + return value; + } + + 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)); + }); + + 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. + + 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. + 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); + +})(); \ No newline at end of file