diff --git a/helpers/handlebars/middleware.js b/helpers/handlebars/middleware.js index 51e9fb9a5f..9e8e998649 100644 --- a/helpers/handlebars/middleware.js +++ b/helpers/handlebars/middleware.js @@ -6,6 +6,8 @@ const { FEATURE_EXTENSIONS_ENABLED, NOTIFICATION_SERVICE_ENABLED, FEATURE_TEAMS_ENABLED, + ALERT_STATUS_URL, + SC_THEME, } = require('../../config/global'); const makeActive = (items, currentUrl) => { @@ -89,6 +91,7 @@ module.exports = (req, res, next) => { icon: 'folder-open-outline', link: '/files/', excludedPermission: 'COLLABORATIVE_FILES_ONLY', + groupName: 'files', children: [ { name: res.$t('global.link.filesPersonal'), @@ -141,7 +144,7 @@ module.exports = (req, res, next) => { name: res.$t('global.link.lernstore'), testId: 'Lern-Store', icon: // eslint-disable-next-line max-len - '', + '', isExternalIcon: true, link: '/content/', permission: 'LERNSTORE_VIEW', @@ -237,6 +240,10 @@ module.exports = (req, res, next) => { }); } + res.locals.sidebarItems.push({ + name: 'divider', + }); + res.locals.sidebarItems.push({ name: res.$t('global.link.management'), testId: 'Verwaltung', @@ -244,6 +251,7 @@ module.exports = (req, res, next) => { link: '/administration/', permission: 'STUDENT_LIST', excludedPermission: 'ADMIN_VIEW', + groupName: 'administration', children: teacherChildren, }); @@ -254,6 +262,7 @@ module.exports = (req, res, next) => { link: '/administration/', permission: 'TEACHER_LIST', excludedPermission: ['ADMIN_VIEW', 'STUDENT_LIST'], + groupName: 'administration', children: teacherChildrenWithoutStudents, }); @@ -323,18 +332,10 @@ module.exports = (req, res, next) => { icon: 'cog-outline', link: '/administration/', permission: 'ADMIN_VIEW', + groupName: 'administration', children: adminChildItems, }); - // beta user view - res.locals.sidebarItems.push({ - name: res.$t('global.headline.myMaterial'), - testId: 'Meine Materialien', - icon: 'book', - link: '/my-material/', - permission: 'BETA_FEATURES', - }); - // team feature toggle const teamsEnabled = FEATURE_TEAMS_ENABLED === 'true'; if (teamsEnabled) { @@ -366,6 +367,7 @@ module.exports = (req, res, next) => { testId: 'Hilfebereich', icon: 'help-circle-outline', link: '/help/', + groupName: 'help', children: [ { name: res.$t('help.headline.helpSection'), @@ -386,8 +388,84 @@ module.exports = (req, res, next) => { link: 'https://lernen.cloud/', isExternalLink: true, }, + // new sidebar + // { + // name: res.$t("lib.help_menu.link.releaseNotes"), + // link: "/help/releases", + // testId: "releases", + // }, ], }); + + // new sidebar + + // system group + let systemLinks = [{ + name: res.$t("lib.global.link.github"), + link: "https://github.com/hpi-schul-cloud", + testId: "github", + isExternalLink: true, + }] + + if (ALERT_STATUS_URL) { + systemLinks.push({ + link: ALERT_STATUS_URL, + name: res.$t("lib.global.link.status"), + testId: "status", + isExternalLink: true, + }); + } + if (SC_THEME === "default") { + systemLinks.push({ + link: "/security", + name: res.$t("lib.global.link.safety"), + testId: "security", + }); + } + + // res.locals.sidebarItems.push( + // { + // name: res.$t("global.sidebar.link.system"), + // icon: "application-brackets-outline", + // testId: "system", + // groupName: "system", + // children: systemLinks, + // } + // ); + + // a11y group + let a11yLinks = []; + if (Configuration.get("ACCESSIBILITY_REPORT_EMAIL")) { + a11yLinks.push({ + link: + "mailto:" + + Configuration.get("ACCESSIBILITY_REPORT_EMAIL") + + "?subject=" + + "global.link.accessibilityReport", + name: res.$t("lib.global.link.accessibilityReport"), + testId: "report-accessibility", + isExternalLink: true, + }); + } + a11yLinks.push({ + link: res.locals.theme.documents.specificFiles.accessibilityStatement, + name: res.$t("lib.global.link.accessibilityStatement"), + testId: "accessibility-statement", + isExternalLink: true, + }); + + // if (SC_THEME !== "default") { + // res.locals.sidebarItems.push({ + // name: res.$t("global.sidebar.link.accessibility"), + // icon: "human", + // testId: "accessibility", + // groupName: "accessibility", + // children: a11yLinks, + // }) + // } + + // end new sidebar + makeActive(res.locals.sidebarItems, url.parse(req.url).pathname); let notificationsPromise = []; diff --git a/locales/de.json b/locales/de.json index 3e32305e84..53d2a813e5 100644 --- a/locales/de.json +++ b/locales/de.json @@ -1694,8 +1694,8 @@ "courseDataContent": "Hier findest du alle Dateien, die in den jeweiligen Kursen im Unterricht verwendet werden. Alle Teilnehmer:innen des Kurses, also die Lehrkraft und die Schüler:innen, haben auf diese Dateien Zugriff.", "files": "Dateien", "filesContent": "Hier findest du alle Dateien, die in den jeweiligen Teams verwendet werden. Alle Teilnehmer:innen des Teams haben auf diese Dateien Zugriff.", - "filesPersonal": "persönliche Dateien", - "filesShared": "geteilte Dateien", + "filesPersonal": "Persönliche Dateien", + "filesShared": "Geteilte Dateien", "firstSteps": "Erste Schritte", "footerSkipLink": "Zum Footer wechseln", "helpArea": "Hilfebereich", @@ -1751,6 +1751,8 @@ "administration": "Administration", "administrationClasses": "Klassen", "administrationCourses": "Kurse", + "system": "System", + "accessibility": "Barrierefreiheit", "mediaShelf": "Medienregal" } }, @@ -2434,7 +2436,8 @@ "printQrCode": "QR-Code drucken", "qrCode": "QR-Code", "search": "Suche", - "thereIsAProblem": "Es gibt ein Problem" + "thereIsAProblem": "Es gibt ein Problem", + "sidebarMenu": "Seitennavigation öffnen" }, "tab_label": { "settings": "Einstellungen", diff --git a/locales/en.json b/locales/en.json index b063a8fce9..b99ece80fc 100644 --- a/locales/en.json +++ b/locales/en.json @@ -1694,8 +1694,8 @@ "courseDataContent": "Here you will find all files used in the respective courses in class. All participants of the course, i.e. the teacher and the students, have access to these files.", "files": "Files", "filesContent": "Here you can find all files used in the respective teams. All participants of the team have access to these files.", - "filesPersonal": "personal files", - "filesShared": "shared files", + "filesPersonal": "Personal Files", + "filesShared": "Shared Files", "firstSteps": "First steps", "footerSkipLink": "Skip to footer", "helpArea": "Help section", @@ -1751,6 +1751,8 @@ "administration": "Administration", "administrationClasses": "Classes", "administrationCourses": "Courses", + "system": "System", + "accessibility": "Accessibility", "mediaShelf": "Media shelf" } }, @@ -2434,7 +2436,8 @@ "printQrCode": "Print QR code", "qrCode": "QR-Code", "search": "Search", - "thereIsAProblem": "There is a problem" + "thereIsAProblem": "There is a problem", + "sidebarMenu": "Open side navigation" }, "tab_label": { "settings": "Settings", diff --git a/locales/es.json b/locales/es.json index 01c414030c..b16a2a800a 100644 --- a/locales/es.json +++ b/locales/es.json @@ -1694,8 +1694,8 @@ "courseDataContent": "Aquí encontrarás todos los archivos utilizados en los respectivos cursos de la clase. Todos los participantes del curso, es decir, tanto el profesor como los alumnos, tienen acceso a estos archivos.", "files": "Archivos", "filesContent": "Aquí puedes encontrar todos los archivos utilizados en los respectivos equipos. Todos los participantes del equipo tienen acceso a estos archivos.", - "filesPersonal": "archivos personales", - "filesShared": "archivos compartidos", + "filesPersonal": "Archivos personales", + "filesShared": "Archivos compartidos", "firstSteps": "Primeros pasos", "footerSkipLink": "Saltar al pie de página", "helpArea": "Sección de ayuda", @@ -1751,6 +1751,8 @@ "administration": "Administración", "administrationClasses": "Clases", "administrationCourses": "Cursos", + "system": "Sistema", + "accessibility": "Accesibilidad", "mediaShelf": "Estante multimedia" } }, @@ -2434,7 +2436,8 @@ "printQrCode": "Imprimir código QR", "qrCode": "Código QR", "search": "Buscar", - "thereIsAProblem": "Hay un problema" + "thereIsAProblem": "Hay un problema", + "sidebarMenu": "Abrir navegación lateral" }, "tab_label": { "settings": "Configuración", diff --git a/locales/uk.json b/locales/uk.json index 35e83fa156..20a130563c 100644 --- a/locales/uk.json +++ b/locales/uk.json @@ -187,8 +187,8 @@ "calendar": "Календар", "contact": "Контакт", "files": "Файли", - "filesPersonal": "персональні файли", - "filesShared": "поширені файли", + "filesPersonal": "Особисті файли", + "filesShared": "Спільні файли", "firstSteps": "Перші кроки", "footerSkipLink": "Нижній колонтитул", "helpArea": "Довідковий розділ", @@ -253,6 +253,8 @@ "administrationClasses": "Класи", "administrationCourses": "Курси", "administration": "Адміністрація", + "system": "Cистема", + "accessibility": "Доступність", "mediaShelf": "Полиця для медіа" } }, @@ -2621,7 +2623,8 @@ "printQrCode": "Видрукувати QR-код", "notifications": "Сповіщення", "currentUser": "Поточний користувач:", - "search": "Пошук" + "search": "Пошук", + "sidebarMenu": "Відкрити бічну навігацію" }, "aria_label": { "mobileNavMenu": "Меню мобільної навігації" diff --git a/static/scripts/loggedin.js b/static/scripts/loggedin.js index ff8d63d67b..c9c71a2102 100644 --- a/static/scripts/loggedin.js +++ b/static/scripts/loggedin.js @@ -22,6 +22,94 @@ function showHideGlobalAnnouncement() { } } +// new sidebar +// function toggleSidebarItemGroup(groupName, e) { +// if (e) { +// e.stopImmediatePropagation(); +// } + +// const itemGroup = document.querySelector(`.${groupName}`); +// if (itemGroup) { +// if (itemGroup.classList.contains('show-subgroup')) { +// itemGroup.classList.remove('show-subgroup'); +// itemGroup.classList.add('hide-subgroup'); +// } else { +// itemGroup.classList.add('show-subgroup'); +// itemGroup.classList.remove('hide-subgroup'); +// } +// } +// } + +// function adjustContentWidth(sidebar) { +// const contentWrapper = document.querySelector('.content-wrapper'); +// if (contentWrapper) { +// if (sidebar.classList.contains('hidden')) { +// contentWrapper.style.paddingLeft = '0px'; +// } else { +// contentWrapper.style.paddingLeft = '255px'; +// } +// } +// } + +// function toggleSidebar() { +// const sidebar = document.querySelector('.sidebar'); +// const overlay = document.querySelector('.overlay'); + +// if (sidebar) { +// if (sidebar.classList.contains('hidden')) { +// sidebar.classList.remove('hidden'); +// sidebar.classList.add('visible'); + +// if (window.innerWidth <= 1279) { +// overlay.style.display = "block"; +// } +// } else { +// sidebar.classList.remove('visible'); +// sidebar.classList.add('hidden'); + +// if (window.innerWidth <= 1279) { +// overlay.style.display = "none"; +// } +// } + +// if (window.innerWidth <= 1279) return; +// adjustContentWidth(sidebar); +// } +// } + +// function toggleSidebarOnWindowWidth(sidebar) { +// const overlay = document.querySelector('.overlay'); + +// if (window.innerWidth <= 1279) { +// sidebar.classList.remove('visible'); +// sidebar.classList.add('hidden'); +// } +// if (window.innerWidth >= 1280) { +// sidebar.classList.remove('hidden'); +// sidebar.classList.add('visible'); +// overlay.style.display = "none"; +// } +// if (sidebar.classList.contains('hidden')) { +// overlay.style.display = "none"; +// } +// adjustContentWidth(sidebar); +// } + +// let priorWidth = window.innerWidth; +// window.addEventListener('resize', () => { +// const sidebar = document.querySelector('.sidebar'); +// const currentWidth = window.innerWidth; + +// if (window.innerWidth <= 1279 && sidebar.classList.contains('visible') && priorWidth <= currentWidth) { +// return; +// } +// if (currentWidth >= 1280 && sidebar.classList.contains('hidden') && priorWidth >= currentWidth) { +// return; +// } +// toggleSidebarOnWindowWidth(sidebar); +// priorWidth = currentWidth; +// }); + function toggleMobileNav() { document.querySelector('aside.nav-sidebar').classList.toggle('active'); showHideGlobalAnnouncement(); @@ -76,6 +164,46 @@ $(document).ready(() => { }); $(document).ready(function () { + // new sidebar + // const groupToggleBtns = document.querySelectorAll('.group-toggle-btn'); + // if (groupToggleBtns) { + // groupToggleBtns.forEach((btn) => { + // btn.addEventListener('click', (e) => toggleSidebarItemGroup(btn.dataset.groupName, e)); + // btn.addEventListener('keypress', (e) => { + // if (e.key === 'Enter' || e.key === ' ') { + // e.preventDefault(); + // document.activeElement.click(); + // } + // }); + + // if (btn.classList.contains('child-active')) { + // toggleSidebarItemGroup(btn.dataset.groupName) + // } + // }) + // } + + // const sidebarToggle = document.querySelector('.sidebar-toggle'); + // if (sidebarToggle) { + // sidebarToggle.addEventListener('click', toggleSidebar); + // sidebarToggle.addEventListener('keypress', (e) => { + // if (e.key === 'Enter' || e.key === ' ') { + // e.preventDefault(); + // toggleSidebar(); + // } + // }) + // } + // const sidebar = document.querySelector('.sidebar'); + // toggleSidebarOnWindowWidth(sidebar); + + // const overlay = document.querySelector('.overlay'); + // if (overlay) { + // overlay.addEventListener('click', () => { + // sidebar.classList.remove('visible'); + // sidebar.classList.add('hidden'); + // overlay.style.display = "none"; + // }); + // } + // Init mobile nav var mobileNavToggle = document.querySelector('.mobile-nav-toggle'); var mobileSearchToggle = document.querySelector('.mobile-search-toggle'); diff --git a/static/styles/lib/loggedin.scss b/static/styles/lib/loggedin.scss index 126e039685..d19752520d 100644 --- a/static/styles/lib/loggedin.scss +++ b/static/styles/lib/loggedin.scss @@ -3,6 +3,7 @@ @import "./editor"; @import "./alerts.scss"; @import "./bootstrap/scss/mixins/grid"; +@import "./sidebar.scss"; $translateModal: translate(50px, 0px); @@ -390,7 +391,14 @@ header { min-height: 100vh; display: flex; flex-direction: column; + transition: 0.2s cubic-bezier(0.4, 0, 0.2, 1); + // new sidebar + // @media only screen and (max-width: 1279px) { + // padding: 0; + // } + + // remove when new sidebar @include media-breakpoint-down(md) { padding-left: 60px; } diff --git a/static/styles/lib/sidebar.scss b/static/styles/lib/sidebar.scss new file mode 100644 index 0000000000..7201bb5913 --- /dev/null +++ b/static/styles/lib/sidebar.scss @@ -0,0 +1,260 @@ +@import "./bootstrap/scss/bootstrap-flex"; +@import "./bootstrap/scss/mixins/grid"; +@import "./colors"; + +.sidebar { + z-index: 1006; + position: fixed; + top: 0; + left: 0; + overflow: auto; + width: 256px; + height: 100vh; + margin: 0; + color: #727272; + background-color: $colorWhite; + border-right: 1px solid rgba(0, 0, 0, 0.12); + display: flex; + flex-direction: column; + transition-duration: 0.2s; + transition-property: box-shadow, transform, visibility, width, height, left, right, top, bottom; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + + &.hidden { + transform: translateX(-110%); + } + + &.visible { + transform: translateX(0%); + } + + @media screen and (max-width: 1279px) { + transform: translateX(-110%); + box-shadow: 0px 8px 10px -5px rgba(0, 0, 0, 0.2), 0px 16px 24px 2px rgba(0, 0, 0, 0.14), 0px 6px 30px 5px rgba(0, 0, 0, 0.12); + } + + nav { + display: flex; + flex-direction: column; + flex: 1; + } + + .divider { + display: block; + flex: 1 1 100%; + height: 0px; + max-height: 0px; + opacity: var(--v-border-opacity); + transition: inherit; + border-style: solid; + border-width: thin 0 0 0; + margin: 12px 0px; + + &.legal { + margin: 0px; + } + } + + .logo-wrapper { + display: block; + padding: 0px; + font-size: 24px; + color: whitesmoke; + text-decoration: none; + margin-bottom: 1px; + + .logo { + display: inline-block; + vertical-align: top; + width: 100%; + height: 30px; + margin: 0px; + margin-left: -8px; + padding: 30px; + background: $logoBackground; + background-size: contain; + } + + .sc-title { + display: none; + max-width: calc(100% - 4px); + word-break: break-word; + } + } + + ul { + flex: 1; + width: 100%; + list-style-type: none; + padding: 12px 0px; + margin: 0px; + + &.subitems { + padding: 0px; + max-height: 0px; + visibility: hidden; + transition: max-height 0.3s cubic-bezier(0.4, 0, 0.2, 1), visibility 0.2s cubic-bezier(0.4, 0, 0.2, 1); + + &.show-subgroup { + max-height: 500px; + visibility: visible; + transition: max-height 0.3s cubic-bezier(0.4, 0, 0.2, 1), visibility 0.2s cubic-bezier(0.4, 0, 0.2, 1); + } + } + + li { + width: 100%; + + .subitem { + display: flex; + align-items: center; + color: $secondaryColor; + padding-left: 56px; + font-size: 16px; + text-decoration: none; + overflow-x: visible; + white-space: nowrap; + height: 40px; + letter-spacing: 0.009375em; + border-bottom: 1px solid transparent; + + .link-name { + margin-bottom: 2px; + } + + &:hover { + background: rgba(0, 0, 0, .07); + cursor: pointer; + } + + &.active { + background: $colorLightGrey; + background: $primaryColorOverlay; + color: $primaryColor; + } + } + + .sidebar-item { + display: flex; + align-items: center; + width: 100%; + height: 48px; + color: $secondaryColor; + padding: 4px 16px; + font-size: 16px; + text-decoration: none; + overflow-x: visible; + white-space: nowrap; + border-bottom: 1px solid transparent; + + &:not(a) { + justify-content: space-between; + } + + > div { + display: flex; + align-items: center; + justify-content: start; + } + + &.child-active { + color: $primaryColor; + } + + &.active { + background: $colorLightGrey; + background: $primaryColorOverlay; + color: $primaryColor; + + svg { + fill: $primaryColor; + } + } + + &[href]:hover { + background: $colorLightGrey; + cursor: pointer; + } + + i.mdi { + font-size: 24px; + width: 24px; + display: inline-block; + text-align: center; + vertical-align: text-top; + + &:before { + vertical-align: inherit; + } + } + + svg { + width: 24px; + display: inline-block; + text-align: center; + height: 24px; + } + + span { + display: inline-block; + text-decoration: none; + margin-left: 16px; + margin-bottom: 2px; + line-height: 24px; + letter-spacing: 0.009375em; + text-overflow: ellipsis; + overflow: hidden; + } + } + } + } + + .sidebar-item.child-active ~ .subitems { + max-height: 500px; + visibility: visible; + + &.show-subgroup { + max-height: 500px; + visibility: visible; + transition: max-height 0.3s cubic-bezier(0.4, 0, 0.2, 1), visibility 0.2s cubic-bezier(0.4, 0, 0.2, 1); + } + + &.hide-subgroup { + max-height: 0px; + visibility: hidden; + transition: max-height 0.3s cubic-bezier(0.4, 0, 0.2, 1), visibility 0.2s cubic-bezier(0.4, 0, 0.2, 1); + } + } + + .legal-link { + display: flex; + align-items: center; + color: $secondaryColor; + padding-left: 16px; + font-size: 16px; + text-decoration: none; + overflow-x: visible; + white-space: nowrap; + height: 40px; + letter-spacing: 0.009375em; + border-bottom: 1px solid transparent; + + &:hover { + background: rgba(0, 0, 0, .07); + cursor: pointer; + } + } +} + +.overlay { + display: none; + z-index: 1005; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: black; + opacity: 0.2; + transition: opacity 0.2s cubic-bezier(0.4, 0, 0.2, 1); +} diff --git a/theme/brb/style.scss b/theme/brb/style.scss index 1bf676d2ae..cfd4c0680e 100644 --- a/theme/brb/style.scss +++ b/theme/brb/style.scss @@ -1,4 +1,5 @@ $primaryColor: #0a9396; +$primaryColorOverlay: rgba(10, 147, 150, 0.12); $secondaryColor: #294c5a; $accentColor: #E4032E; $grayDarkColor: #294c5a; diff --git a/theme/default/style.scss b/theme/default/style.scss index 125809a6d4..a288f9a80c 100644 --- a/theme/default/style.scss +++ b/theme/default/style.scss @@ -1,4 +1,5 @@ $primaryColor: #9e292b; +$primaryColorOverlay: rgba(158, 41, 43, 0.12); $secondaryColor: #3a424b; $accentColor: #e98404; $grayDarkColor: #3a424b; diff --git a/theme/n21/style.scss b/theme/n21/style.scss index 4d3ee8b4ee..6a359d06f7 100644 --- a/theme/n21/style.scss +++ b/theme/n21/style.scss @@ -1,5 +1,6 @@ $primaryColor: #2876D0; +$primaryColorOverlay: rgba(40, 118, 208, 0.12); $secondaryColor: #0F3551; $accentColor: #970000; $grayDarkColor: #0F3551; diff --git a/theme/thr/style.scss b/theme/thr/style.scss index 7bc0f37315..aae27ec4e5 100644 --- a/theme/thr/style.scss +++ b/theme/thr/style.scss @@ -1,5 +1,6 @@ $primaryColor: #2876D0; +$primaryColorOverlay: rgba(40, 118, 208, 0.12); $secondaryColor: #0f3551; $accentColor: #f56b00; $grayDarkColor: #0f3551; diff --git a/views/lib/loggedin.hbs b/views/lib/loggedin.hbs index df28f5ffdd..0126096cae 100644 --- a/views/lib/loggedin.hbs +++ b/views/lib/loggedin.hbs @@ -45,6 +45,8 @@ {{/if}} + {{!-- new sidebar --}} + {{!-- {{#embed "lib/sidebar"}}{{/embed}} --}}