diff --git a/resources/assets/javascripts/bootstrap/forms.js b/resources/assets/javascripts/bootstrap/forms.js index 1a6bc53e152950d2b2c3c24e331f05db0e8cfe90..41c2c7733db69995c03cc39ca7e7a8ac0d20775c 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 0000000000000000000000000000000000000000..5e89a7fc472e03537df7709609ad8c47e407ed51 --- /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 cf95ed3c5f73c5fbf9766ddc2b64ec2c2b5baf75..5ee9389e002494f75d1fb5077372d4b8bb8a81bb 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 9fcb57ba97d4533387744bf94b46f3f8a3b87348..82e5640ee8f4a59fb1d6687e176833dda11bd3c4 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 3cb1c05eba8f9177d108fbe3f4d9e5167d7b9cad..eefd317beaf047ae706718323d3532787eb67248 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 cbf2e75205942836e51dabb2c1c4561fb46d1c39..9226db82fd59d9323666832aa5a55a5af150c374 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 f03d7a2e8fbf2b997922fc53503244775548f0c8..60114c7dbab95835f1e309f20e83f68896d1028f 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 { } } } +