From a70a5f70aadf1cba165c41b33f3485a22979338b Mon Sep 17 00:00:00 2001
From: Thomas Hackl <hackl@data-quest.de>
Date: Sat, 15 Jul 2023 20:54:20 +0000
Subject: [PATCH] Sidebar in Veranstaltungen->Administration scrollt nicht mit,
 closes #2255

Closes #2255

Merge request studip/studip!1772
---
 .../assets/javascripts/bootstrap/forms.js     |   2 +-
 .../assets/javascripts/bootstrap/sidebar.js   |  14 +++
 resources/assets/javascripts/chunks/vue.js    |  24 ++++
 resources/assets/javascripts/entry-base.js    |   1 +
 resources/assets/javascripts/lib/sidebar.js   | 108 ++++++++++++++++--
 .../assets/stylesheets/scss/responsive.scss   |   6 +-
 .../assets/stylesheets/scss/sidebar.scss      |  63 +++++++++-
 7 files changed, 202 insertions(+), 16 deletions(-)
 create mode 100644 resources/assets/javascripts/bootstrap/sidebar.js

diff --git a/resources/assets/javascripts/bootstrap/forms.js b/resources/assets/javascripts/bootstrap/forms.js
index 1a6bc53e152..41c2c7733db 100644
--- a/resources/assets/javascripts/bootstrap/forms.js
+++ b/resources/assets/javascripts/bootstrap/forms.js
@@ -201,7 +201,7 @@ function createSelect2(element) {
         minimumResultsForSearch: $(element).closest('#sidebar').length > 0 ? 15 : 10,
         placeholder: placeholder,
         dropdownAutoWidth: dropdownAutoWidth,
-        dropdownParent: $(element).closest('.ui-dialog,#sidebar,body'),
+        dropdownParent: $(element).closest('.ui-dialog,.sidebar-widget,#sidebar,#content-wrapper,body'),
         templateResult: function(data, container) {
             if (data.element) {
                 let option_classes = $(data.element).attr('class'),
diff --git a/resources/assets/javascripts/bootstrap/sidebar.js b/resources/assets/javascripts/bootstrap/sidebar.js
new file mode 100644
index 00000000000..5e89a7fc472
--- /dev/null
+++ b/resources/assets/javascripts/bootstrap/sidebar.js
@@ -0,0 +1,14 @@
+import eventBus from '../lib/event-bus.ts';
+
+STUDIP.ready(() => {
+    // Manually nudge sidebar under main header.
+    STUDIP.Sidebar.place();
+
+    STUDIP.Sidebar.observeBody();
+    STUDIP.Sidebar.observeFooter();
+    STUDIP.Sidebar.observeSidebar();
+
+    document.defaultView.addEventListener('resize',() => {
+        STUDIP.Sidebar.reset();
+    });
+});
diff --git a/resources/assets/javascripts/chunks/vue.js b/resources/assets/javascripts/chunks/vue.js
index cf95ed3c5f7..5ee9389e002 100644
--- a/resources/assets/javascripts/chunks/vue.js
+++ b/resources/assets/javascripts/chunks/vue.js
@@ -41,6 +41,30 @@ Vue.mixin({
         },
         getStudipConfig: store.getters['studip/getConfig']
     },
+    beforeCreate() {
+        eventBus.emit('vue:app:will-create', this);
+    },
+    created() {
+        eventBus.emit('vue:app:did-create', this);
+    },
+    beforeMount() {
+        eventBus.emit('vue:app:will-mount', this);
+    },
+    mounted() {
+        eventBus.emit('vue:app:did-mount', this);
+    },
+    beforeUpdate() {
+        eventBus.emit('vue:app:will-update', this);
+    },
+    updated() {
+        eventBus.emit('vue:app:did-update', this);
+    },
+    beforeDestroy() {
+        eventBus.emit('vue:app:will-destroy', this);
+    },
+    destroyed() {
+        eventBus.emit('vue:app:did-destroy', this);
+    }
 });
 
 Vue.use(CKEditor);
diff --git a/resources/assets/javascripts/entry-base.js b/resources/assets/javascripts/entry-base.js
index 9fcb57ba97d..82e5640ee8f 100644
--- a/resources/assets/javascripts/entry-base.js
+++ b/resources/assets/javascripts/entry-base.js
@@ -86,6 +86,7 @@ import "./bootstrap/contentmodules.js"
 import "./bootstrap/responsive-navigation.js"
 import "./bootstrap/treeview.js"
 import "./bootstrap/stock-images.js"
+import "./bootstrap/sidebar.js"
 
 import "./mvv_course_wizard.js"
 import "./mvv.js"
diff --git a/resources/assets/javascripts/lib/sidebar.js b/resources/assets/javascripts/lib/sidebar.js
index 3cb1c05eba8..eefd317beaf 100644
--- a/resources/assets/javascripts/lib/sidebar.js
+++ b/resources/assets/javascripts/lib/sidebar.js
@@ -1,20 +1,104 @@
-import Scroll from './scroll.js';
-
 const Sidebar = {
-    open () {
-        this.toggle(true);
+
+    place() {
+        const header = document.getElementById('main-header');
+        document.getElementById('sidebar').style.top =
+            header.offsetTop + header.offsetHeight + 'px';
+    },
+
+    observeSidebar() {
+        const options = {
+            root: null,
+            rootMargin: '0px',
+            threshold: 1
+        };
+
+        /**
+         * Observe if sidebar fits into viewport.
+         */
+        const sObserver = new IntersectionObserver(STUDIP.Sidebar.fits, options);
+        sObserver.observe(document.getElementById('sidebar'));
+    },
+
+    observeBody() {
+        const sidebar = document.getElementById('sidebar');
+        /**
+         * Observe body for class changes. If "fixed" is added or removed, we are in scroll mode
+         * where the top navigation is removed or visible again.
+         */
+        const mObserver = new MutationObserver(mutations => {
+            for (const mutation of mutations) {
+                if ((!mutation.oldValue || mutation.oldValue.indexOf('fixed') === -1)
+                    && mutation.target.classList.contains('fixed')) {
+                    sidebar.classList.add('fixed');
+                    sidebar.style.top = '';
+                } else if (mutation.oldValue && mutation.oldValue.indexOf('fixed') !== -1
+                    && !mutation.target.classList.contains('fixed')) {
+                    sidebar.classList.remove('fixed');
+                }
+            }
+        });
+
+        // Observe body for class changes.
+        mObserver.observe(document.body, {
+            attributes: true,
+            attributeOldValue : true,
+            attributeFilter: ['class']
+        });
     },
-    close () {
-        this.toggle(false);
+
+    observeFooter() {
+        const options = {
+            root: null,
+            rootMargin: '0px',
+            threshold: 1
+        };
+
+        /**
+         * Observe if the footer is visible in viewport.
+         */
+        const fObserver = new IntersectionObserver(STUDIP.Sidebar.footerVisible, options);
+        fObserver.observe(document.getElementById('main-footer'));
+
     },
-    toggle (visible = null) {
-        visible = visible ?? !$('#sidebar').hasClass('visible-sidebar');
 
-        // Hide navigation
-        $('#responsive-toggle').prop('checked', false);
-        $('#responsive-navigation').removeClass('visible');
+    reset() {
+        const sidebar = document.getElementById('sidebar');
+        if (sidebar) {
+            sidebar.classList.remove('oversized', 'adjusted', 'fixed');
+            sidebar.style.top = '';
+        }
+        STUDIP.Sidebar.observe();
+    },
+
+    fits(entries, observer) {
+        const sidebar = document.getElementById('sidebar');
+        if (sidebar) {
+            entries.forEach(entry => {
+                // Sidebar fits onto current page.
+                if (entry.isIntersecting) {
+                    sidebar.classList.remove('oversized');
+                } else {
+                    sidebar.classList.add('oversized', 'adjusted');
+                }
+            });
+        }
+    },
 
-        $('#sidebar').toggleClass('visible-sidebar', visible);
+    footerVisible(entries, observer) {
+        const sidebar = document.getElementById('sidebar');
+        entries.forEach(entry => {
+            // Footer is visible on current page.
+            if (entry.isIntersecting) {
+                if (sidebar.classList.contains('no-footer')) {
+                    sidebar.classList.remove('no-footer');
+                }
+            } else {
+                if (!sidebar.classList.contains('no-footer')) {
+                    sidebar.classList.add('no-footer');
+                }
+            }
+        });
     }
 };
 
diff --git a/resources/assets/stylesheets/scss/responsive.scss b/resources/assets/stylesheets/scss/responsive.scss
index cbf2e752059..9226db82fd5 100644
--- a/resources/assets/stylesheets/scss/responsive.scss
+++ b/resources/assets/stylesheets/scss/responsive.scss
@@ -337,9 +337,13 @@ $sidebarOut: -330px;
             @media not prefers-reduced-motion {
                 animation: slide-in $transition-duration forwards;
             }
-            position: sticky;
+
             top: 100px;
             visibility: visible;
+
+            &:not(.oversized) {
+                position: sticky;
+            }
         }
 
         .sidebar-image {
diff --git a/resources/assets/stylesheets/scss/sidebar.scss b/resources/assets/stylesheets/scss/sidebar.scss
index f03d7a2e8fb..60114c7dbab 100644
--- a/resources/assets/stylesheets/scss/sidebar.scss
+++ b/resources/assets/stylesheets/scss/sidebar.scss
@@ -2,16 +2,49 @@
     background: $white;
     border-left: 0;
     display: inline-block;
-    height: max-content;
     margin-bottom: $page-margin + 35px;
     margin-top: 15px;
     padding: 0 5px 7px 15px;
     position: sticky;
     text-align: left;
-    top: 50px;
+    top: 75px;
     width: $sidebar-width;
     z-index: 2;
 
+    &.adjusted {
+        height: calc(100vh - 265px);
+        position: fixed;
+        overflow-x: hidden;
+        overflow-y: scroll;
+        top: 150px;
+        transition: all $transition-duration ease-in-out;
+
+        &::-webkit-scrollbar {
+            background-color: var(--white);
+            position: relative;
+            left: 35px;
+            width: 5px;
+        }
+
+        &::-webkit-scrollbar-thumb {
+            background-color: var(--light-gray-color-60);
+            border-radius: 5px;
+        }
+
+        &.no-footer {
+            height: calc(100vh - 225px);
+        }
+
+        &.fixed {
+            height: calc(100vh - 150px);
+            top: 40px;
+
+            &.no-footer {
+                height: calc(100vh - 125px);
+            }
+        }
+    }
+
     .sidebar-image {
         width: calc($sidebar-width - 2px);
         height: 60px;
@@ -88,6 +121,7 @@
         background: $white;
         border: 1px solid $content-color-40;
         margin: 15px 0 0;
+        position: relative;
         width: $sidebar-width - 5px;
     }
     .sidebar-widget-header,
@@ -112,6 +146,30 @@
     }
 }
 
+#admin-courses-index {
+    #sidebar {
+        top: 50px;
+
+        &.adjusted {
+            height: calc(100vh - 250px);
+            top: 155px;
+
+            &.no-footer {
+                height: calc(100vh - 200px);
+            }
+
+            &.fixed {
+                height: calc(100vh - 125px);
+                top: 35px;
+
+                &.no-footer {
+                    height: calc(100vh - 100px);
+                }
+            }
+        }
+    }
+}
+
 ul.widget-list {
     list-style: none;
     margin: 0;
@@ -352,3 +410,4 @@ select.sidebar-selectlist {
         }
     }
 }
+
-- 
GitLab