From 936d1c00ea05bb0a5c56f441ff2eece6ccefada6 Mon Sep 17 00:00:00 2001 From: Thomas Hackl <hackl@data-quest.de> Date: Mon, 12 Dec 2022 15:20:27 +0000 Subject: [PATCH] Resolve "StEP00348: Responsive Navigation Stud.IP 5.x" Closes #32 Merge request studip/studip!65 --- app/controllers/oer/market.php | 9 +- app/views/profile/extern.php | 2 +- app/views/profile/index.php | 2 +- lib/classes/ContentBar.php | 34 +- lib/classes/ResponsiveHelper.php | 173 +++- lib/classes/SkipLinks.php | 24 +- lib/classes/WidgetContainer.php | 20 +- lib/classes/sidebar/Sidebar.php | 6 +- lib/include/html_head.inc.php | 6 - lib/modules/ConsultationModule.class.php | 8 +- lib/modules/CoursewareModule.class.php | 1 + lib/navigation/FooterNavigation.php | 6 +- lib/wiki.inc.php | 20 +- .../images/icons/black/focusmode-off.svg | 1 + .../images/icons/blue/focusmode-off.svg | 1 + .../images/icons/blue/fullscreen-off.svg | 2 +- .../images/icons/blue/fullscreen-off2.svg | 1 + .../images/icons/blue/fullscreen-off3.svg | 1 + .../images/icons/blue/fullscreen-on.svg | 2 +- .../images/icons/blue/fullscreen-on2.svg | 1 + .../images/icons/blue/fullscreen-on3.svg | 1 + .../images/icons/blue/fullscreen-on4.svg | 1 + .../images/icons/blue/fullscreen-on5.svg | 1 + public/assets/images/icons/blue/maximise.svg | 1 + .../assets/images/icons/blue/sidebar-open.svg | 1 + public/assets/images/icons/blue/sidebar.svg | 1 + public/assets/images/icons/blue/sidebar3.svg | 1 + public/assets/images/icons/blue/zoom-in.svg | 2 +- public/assets/images/icons/blue/zoom-in2.svg | 2 +- public/assets/images/icons/blue/zoom-out.svg | 2 +- public/assets/images/icons/blue/zoom-out2.svg | 2 +- .../images/icons/green/focusmode-off.svg | 1 + .../images/icons/grey/focusmode-off.svg | 1 + .../assets/images/icons/red/focusmode-off.svg | 1 + .../images/icons/white/focusmode-off.svg | 1 + .../images/icons/white/fullscreen-off.svg | 2 +- .../images/icons/white/fullscreen-off2.svg | 1 + .../images/icons/white/fullscreen-off3.svg | 1 + .../images/icons/white/fullscreen-on.svg | 2 +- .../images/icons/white/fullscreen-on2.svg | 1 + .../images/icons/white/fullscreen-on3.svg | 1 + .../images/icons/white/fullscreen-on4.svg | 1 + .../images/icons/yellow/focusmode-off.svg | 1 + .../bootstrap/responsive-navigation.js | 10 + .../javascripts/bootstrap/responsive.js | 5 + resources/assets/javascripts/entry-base.js | 1 + .../assets/javascripts/lib/header_magic.js | 21 - .../assets/javascripts/lib/responsive.js | 142 +--- .../assets/stylesheets/highcontrast.scss | 8 +- .../assets/stylesheets/less/breakpoints.less | 4 +- .../assets/stylesheets/less/responsive.less | 493 ----------- .../assets/stylesheets/less/visibility.less | 44 +- .../assets/stylesheets/scss/breakpoints.scss | 7 +- .../assets/stylesheets/scss/buttons.scss | 5 + .../assets/stylesheets/scss/contentbar.scss | 188 +++-- .../assets/stylesheets/scss/courseware.scss | 2 +- resources/assets/stylesheets/scss/header.scss | 29 +- .../assets/stylesheets/scss/helpbar.scss | 3 +- .../assets/stylesheets/scss/layouts.scss | 4 +- .../assets/stylesheets/scss/navigation.scss | 41 +- .../assets/stylesheets/scss/responsive.scss | 796 ++++++++++++++++++ .../stylesheets/scss/table_of_contents.scss | 25 +- .../assets/stylesheets/scss/visibility.scss | 49 +- resources/assets/stylesheets/studip.less | 3 - resources/assets/stylesheets/studip.scss | 1 + resources/vue/components/BlubberThread.vue | 2 +- .../courseware/CoursewareRibbon.vue | 10 +- .../CoursewareStructuralElement.vue | 2 +- .../components/responsive/NavigationItem.vue | 83 ++ .../responsive/ResponsiveContentBar.vue | 206 +++++ .../responsive/ResponsiveNavigation.vue | 525 ++++++++++++ .../responsive/ResponsiveSkipLinks.vue | 49 ++ .../responsive/ToggleFullscreen.vue | 51 ++ resources/vue/mixins/MyCoursesMixin.js | 4 +- templates/contentbar/contentbar.php | 57 +- templates/footer.php | 3 +- templates/header.php | 36 +- templates/layouts/base.php | 16 +- templates/skiplinks.php | 15 +- templates/tabs.php | 10 +- 80 files changed, 2356 insertions(+), 943 deletions(-) create mode 100644 public/assets/images/icons/black/focusmode-off.svg create mode 100644 public/assets/images/icons/blue/focusmode-off.svg create mode 100644 public/assets/images/icons/blue/fullscreen-off2.svg create mode 100644 public/assets/images/icons/blue/fullscreen-off3.svg create mode 100644 public/assets/images/icons/blue/fullscreen-on2.svg create mode 100644 public/assets/images/icons/blue/fullscreen-on3.svg create mode 100644 public/assets/images/icons/blue/fullscreen-on4.svg create mode 100644 public/assets/images/icons/blue/fullscreen-on5.svg create mode 100644 public/assets/images/icons/blue/maximise.svg create mode 100644 public/assets/images/icons/blue/sidebar-open.svg create mode 100644 public/assets/images/icons/blue/sidebar.svg create mode 100644 public/assets/images/icons/blue/sidebar3.svg create mode 100644 public/assets/images/icons/green/focusmode-off.svg create mode 100644 public/assets/images/icons/grey/focusmode-off.svg create mode 100644 public/assets/images/icons/red/focusmode-off.svg create mode 100644 public/assets/images/icons/white/focusmode-off.svg create mode 100644 public/assets/images/icons/white/fullscreen-off2.svg create mode 100644 public/assets/images/icons/white/fullscreen-off3.svg create mode 100644 public/assets/images/icons/white/fullscreen-on2.svg create mode 100644 public/assets/images/icons/white/fullscreen-on3.svg create mode 100644 public/assets/images/icons/white/fullscreen-on4.svg create mode 100644 public/assets/images/icons/yellow/focusmode-off.svg create mode 100644 resources/assets/javascripts/bootstrap/responsive-navigation.js delete mode 100644 resources/assets/stylesheets/less/responsive.less create mode 100644 resources/assets/stylesheets/scss/responsive.scss create mode 100644 resources/vue/components/responsive/NavigationItem.vue create mode 100644 resources/vue/components/responsive/ResponsiveContentBar.vue create mode 100644 resources/vue/components/responsive/ResponsiveNavigation.vue create mode 100644 resources/vue/components/responsive/ResponsiveSkipLinks.vue create mode 100644 resources/vue/components/responsive/ToggleFullscreen.vue diff --git a/app/controllers/oer/market.php b/app/controllers/oer/market.php index 23782a44e2a..4071d07b5ff 100644 --- a/app/controllers/oer/market.php +++ b/app/controllers/oer/market.php @@ -271,11 +271,10 @@ class Oer_MarketController extends StudipController } } - $this->contentbar = new ContentBar( - new TOCItem($this->material['name']), - $infotext, - Icon::create('oer-campus') - ); + $this->contentbar = ContentBar::get() + ->setTOC(new TOCItem($this->material['name'])) + ->setInfo($infotext) + ->setIcon(Icon::create('oer-campus')); } public function embed_action($material_id) diff --git a/app/views/profile/extern.php b/app/views/profile/extern.php index 9524837d8d1..60ec9612f3e 100644 --- a/app/views/profile/extern.php +++ b/app/views/profile/extern.php @@ -1,4 +1,4 @@ -<div class="responsive-visible"> +<div class="hidden-medium-up"> <img src="<?= htmlReady($user['avatar_url'] ?: Avatar::getNobody()->getURL(Avatar::NORMAL)) ?>"> </div> diff --git a/app/views/profile/index.php b/app/views/profile/index.php index 1062f74bcf5..72e9c9f603f 100644 --- a/app/views/profile/index.php +++ b/app/views/profile/index.php @@ -1,4 +1,4 @@ -<div class="responsive-visible"> +<div class="hidden-medium-up"> <?= Avatar::getAvatar($current_user->user_id)->getImageTag(Avatar::NORMAL) ?> </div> <section class="contentbox"> diff --git a/lib/classes/ContentBar.php b/lib/classes/ContentBar.php index 043fe1830bb..53b3dfce981 100644 --- a/lib/classes/ContentBar.php +++ b/lib/classes/ContentBar.php @@ -15,25 +15,41 @@ class ContentBar public $infoText = ''; public $icon = ''; public $toc = null; + public $actionMenu = null; + /** - * ContentBar constructor. - * - * Note: An icon for consumer mode is always shown, this would have to be changed via template. + * The singleton instance of the container + */ + protected static $instance = null; + + /** + * Returns the instance of this container to ensure there is only one + * instance. * * @param TOCItem $toc Table of contents object. * @param string $info Some information to show, like creation date, author etc. * @param Icon|null $icon An icon to show in content bar. * @param ActionMenu|null $actionMenu Optional action menu for page actions. + * @return ContentBar + * @static */ - public function __construct(TOCItem $toc, string $info = '', Icon $icon = null, ActionMenu $actionMenu = null) + public static function get(): ContentBar { - $this->infoText = $info; - $this->icon = $icon; - $this->toc = $toc; - $this->actionMenu = $actionMenu; + if (static::$instance === null) { + static::$instance = new static; + } + return static::$instance; } - public $actionMenu = null; + /** + * Private constructor to ensure that the singleton Get() method is always + * used. + * + * @see ContentBar::get + */ + protected function __construct() + { + } /** * Provide some info text. diff --git a/lib/classes/ResponsiveHelper.php b/lib/classes/ResponsiveHelper.php index eb7f41c636c..c2b201ef116 100644 --- a/lib/classes/ResponsiveHelper.php +++ b/lib/classes/ResponsiveHelper.php @@ -24,27 +24,54 @@ class ResponsiveHelper $link_params = array_fill_keys(array_keys(URLHelper::getLinkParams()), null); foreach (Navigation::getItem('/')->getSubNavigation() as $path => $nav) { - if (!$nav->isVisible(true)) { - continue; + $image = $nav->getImage(); + + $forceVisibility = false; + /* + * Special treatment for "browse" navigation which is normally hidden + * when we are inside a course. + */ + if ($path === 'browse' && !$image) { + $image = Icon::create('seminar'); + $forceVisibility = true; + } + /* + * Special treatment for "footer" navigation because + * the real footer is hidden in responsive view. + */ + if ($path === 'footer' && !$image) { + $image = Icon::create('info'); + $nav->setTitle(_('Impressum & Information')); + $forceVisibility = true; } - $image = $nav->getImage(); $image_src = $image ? $image->copyWithRole('info_alt')->asImagePath() : false; $item = [ - 'icon' => $image_src ? self::getAssetsURL($image_src) : false, - 'title' => (string) $nav->getTitle(), - 'url' => self::getURL($nav->getURL(), $link_params), + 'icon' => $image_src ? self::getAssetsURL($image_src) : false, + 'title' => (string) $nav->getTitle(), + 'url' => URLHelper::getURL($nav->getURL(), $link_params, true), + 'parent' => '/', + 'path' => $path, + 'visible' => $forceVisibility ? true : $nav->isVisible(true), + 'active' => $nav->isActive() ]; if ($nav->isActive()) { - $activated[] = $path; + // course navigation is integrated in course sub-navigation items + if ($path === 'course') { + $activated[] = 'browse/my_courses/' . (Context::get()->getId()); + } else { + $activated[] = $path; + } } - if ($nav->getSubnavigation() && $path != 'start') { + if ($nav->getSubnavigation() && $path != 'start') { $item['children'] = self::getChildren($nav, $path, $activated); } - $navigation[$path] = $item; + if ($path !== 'course') { + $navigation[$path] = $item; + } } return [$navigation, $activated]; @@ -54,35 +81,50 @@ class ResponsiveHelper * Recursively build a navigation array from the subnavigation/children * of a navigation object. * - * @param Navigation $navigation The navigation object - * @param String $path Current path segment - * @param array $activated Activated items + * @param Navigation $navigation The navigation object + * @param String $path Current path segment + * @param array $activated Activated items + * @param String|null $cid Optional context ID * @return Array containing the children (+ grandchildren...) */ - protected static function getChildren(Navigation $navigation, $path, &$activated = []) + protected static function getChildren(Navigation $navigation, $path, &$activated = [], string $cid = null) { $children = []; foreach ($navigation->getSubNavigation() as $subpath => $subnav) { - if (!$subnav->isVisible()) { + /*if (!$subnav->isVisible()) { continue; - } + }*/ + $originalSubpath = $subpath; $subpath = "{$path}/{$subpath}"; $item = [ - 'title' => (string) $subnav->getTitle(), - 'url' => self::getURL($subnav->getURL()), + 'title' => (string) $subnav->getTitle(), + 'url' => URLHelper::getURL($subnav->getURL(), $cid ? ['cid' => $cid] : []), + 'parent' => $path, + 'path' => $subpath, + 'visible' => $subnav->isVisible(), + 'active' => $subnav->isActive() ]; if ($subnav->isActive()) { - $activated[] = $subpath; + // course navigation is integrated in course sub-navigation items + if ($path === 'course') { + $activated[] = 'browse/my_courses/' . Context::get()->getId() . '/' . $originalSubpath; + } else { + $activated[] = $subpath; + } } if ($subnav->getSubNavigation()) { $item['children'] = self::getChildren($subnav, $subpath); } + if ($subpath === 'browse/my_courses') { + $item['children'] = array_merge($item['children'] ?? [], static::getMyCoursesNavigation($activated)); + } + $children[$subpath] = $item; } @@ -113,4 +155,99 @@ class ResponsiveHelper { return str_replace($GLOBALS['ASSETS_URL'], '', $url); } + + /** + * Specialty for responsive navigation: build navigation items + * for my courses in current semester. + * + * @return array + */ + protected static function getMyCoursesNavigation($activated): array + { + if (!$GLOBALS['perm']->have_perm('admin')) { + $sem_data = Semester::getAllAsArray(); + + $currentIndex = -1; + + foreach ($sem_data as $index => $semester) { + if ($semester['current']) { + $currentIndex = $index; + break; + } + } + + $params = [ + 'deputies_enabled' => Config::get()->DEPUTIES_ENABLE + ]; + + $courses = MyRealmModel::getCourses($currentIndex, $currentIndex, $params); + } else { + $courses = []; + } + + $items = []; + + $standardIcon = Icon::create('seminar', Icon::ROLE_INFO_ALT)->asImagePath(); + + // Add current course to list. + if (Context::get() && Context::isCourse()) { + $courses[] = Context::get(); + } + + foreach ($courses as $course) { + $avatar = CourseAvatar::getAvatar($course->id); + if ($avatar->is_customized()) { + $icon = $avatar->getURL(Avatar::SMALL); + } else { + $icon = $standardIcon; + } + + $cnav = [ + 'icon' => $icon, + 'title' => $course->getFullname(), + 'url' => URLHelper::getURL('dispatch.php/course/details', ['cid' => $course->id]), + 'parent' => 'browse/my_courses', + 'path' => 'browse/my_courses/' . $course->id, + 'visible' => true, + 'active' => Course::findCurrent() ? Course::findCurrent()->id === $course->id : false, + 'children' => [] + ]; + + foreach ($course->tools as $tool) { + if (Seminar_Perm::get()->have_studip_perm($tool->getVisibilityPermission(), $course->id)) { + + $path = 'browse/my_courses/' . $course->id; + + $studip_module = $tool->getStudipModule(); + if ($studip_module instanceof StudipModule) { + $tool_nav = $studip_module->getTabNavigation($course->id) ?: []; + foreach ($tool_nav as $nav_name => $navigation) { + if ($nav_name && is_a($navigation, 'Navigation')) { + $cnav['children'][$path . '/' . $nav_name] = [ + 'icon' => $navigation->getImage() ? $navigation->getImage()->asImagePath() : '', + 'title' => $tool->getDisplayname(), + 'url' => URLHelper::getURL($navigation->getURL(), ['cid' => $course->id]), + 'parent' => 'browse/my_courses/' . $course->id, + 'path' => 'browse/my_courses/' . $course->id . '/' . $nav_name, + 'visible' => true, + 'active' => $navigation->isActive(), + 'children' => static::getChildren( + $navigation, + 'browse/my_courses/' . $course->id . '/' . $nav_name, + $activated, + $course->id + ), + ]; + } + } + } + } + } + + $items['browse/my_courses/' . $course->id] = $cnav; + + } + + return $items; + } } diff --git a/lib/classes/SkipLinks.php b/lib/classes/SkipLinks.php index 684ae424189..796206d0f11 100644 --- a/lib/classes/SkipLinks.php +++ b/lib/classes/SkipLinks.php @@ -60,27 +60,29 @@ class SkipLinks * @param string $url the url of the links * @param integer $position the position of the link in the list */ - public static function addLink(string $name, string $url, $position = null) + public static function addLink(string $name, string $url, $position = null, bool $inFullscreen = true) { $position = (!$position || $position < 1) ? count(self::$links) + 100 : (int) $position; self::$links[$url] = [ - 'name' => $name, - 'url' => $url, - 'position' => $position, + 'name' => $name, + 'url' => $url, + 'position' => $position, + 'fullscreen' => $inFullscreen ]; } /** * Adds a link to an anker on the same page to the list of skip links. * - * @param string $name the displayed name of the links - * @param string $id the id of the anker - * @param integer $position the position of the link in the list + * @param string $name the displayed name of the links + * @param string $id the id of the anker + * @param integer $position the position of the link in the list + * @param bool $inFullscreen is this link relevant in fullscreen mode? */ - public static function addIndex($name, $id, $position = null) + public static function addIndex($name, $id, $position = null, bool $inFullscreen = true) { $url = '#' . $id; - self::addLink($name, $url, $position); + self::addLink($name, $url, $position, $inFullscreen); } /** @@ -99,12 +101,14 @@ class SkipLinks }); $navigation = new Navigation(''); + $fullscreen = []; foreach (array_values(self::$links) as $index => $link) { $navigation->addSubNavigation( "/skiplinks/link-{$index}", new Navigation($link['name'], $link['url']) ); + $fullscreen['/skiplinks/link-' . $index] = $link['fullscreen']; } - return $GLOBALS['template_factory']->render('skiplinks', compact('navigation')); + return $GLOBALS['template_factory']->render('skiplinks', compact('navigation', 'fullscreen')); } } diff --git a/lib/classes/WidgetContainer.php b/lib/classes/WidgetContainer.php index 108477a3844..c702d06b1c2 100644 --- a/lib/classes/WidgetContainer.php +++ b/lib/classes/WidgetContainer.php @@ -159,6 +159,24 @@ abstract class WidgetContainer unset($this->widgets[$index]); } + /** + * Returns the number of widgets in this container. Optionally constrained + * to widgets of a specific class. + * + * @param string|null $widget_class + * @return int + */ + public function countWidgets(string $widget_class = null): int + { + $widgets = $this->widgets; + if ($widget_class !== null) { + $widgets = array_filter($widgets, function (Widget $widget) use ($widget_class) { + return is_a($widget, $widget_class); + }); + } + return count($widgets); + } + /** * Returns whether this container has any widget. * @@ -167,7 +185,7 @@ abstract class WidgetContainer */ public function hasWidgets() { - return count($this->widgets) > 0; + return $this->countWidgets() > 0; } /** diff --git a/lib/classes/sidebar/Sidebar.php b/lib/classes/sidebar/Sidebar.php index 7536a7f9df0..00c3b8fae62 100644 --- a/lib/classes/sidebar/Sidebar.php +++ b/lib/classes/sidebar/Sidebar.php @@ -224,7 +224,8 @@ class Sidebar extends WidgetContainer SkipLinks::addIndex( _('Dritte Navigationsebene'), $widget->getId(), - 20 + 20, + false ); $navigation_widget_added = true; @@ -237,7 +238,8 @@ class Sidebar extends WidgetContainer SkipLinks::addIndex( _('Aktionen'), $widget->getId(), - 21 + 21, + false ); $actions_widget_added = true; diff --git a/lib/include/html_head.inc.php b/lib/include/html_head.inc.php index 4ab1c0ecd1e..a7f2c21e1c4 100644 --- a/lib/include/html_head.inc.php +++ b/lib/include/html_head.inc.php @@ -32,12 +32,6 @@ $lang_attr = str_replace('_', '-', $_SESSION['_language']); String.locale = "<?= htmlReady(strtr($_SESSION['_language'], '_', '-')) ?>"; document.querySelector('html').className = 'js'; - setTimeout(() => { - // This needs to be put in a timeout since otherwise it will not match - if (window.matchMedia('(max-width: 767px)').matches) { - document.querySelector('html').classList.add('responsive-display'); - } - }, 0); window.STUDIP = { ABSOLUTE_URI_STUDIP: "<?= $GLOBALS['ABSOLUTE_URI_STUDIP'] ?>", diff --git a/lib/modules/ConsultationModule.class.php b/lib/modules/ConsultationModule.class.php index acc2cb4cd6d..56717766df2 100644 --- a/lib/modules/ConsultationModule.class.php +++ b/lib/modules/ConsultationModule.class.php @@ -103,11 +103,13 @@ class ConsultationModule extends CorePlugin implements StudipModule, SystemPlugi */ public function getTabNavigation($course_id) { - if ($GLOBALS['user']->id === 'nobody') { - return []; + if ($GLOBALS['user']->id !== 'nobody') { + $navigation = new ConsultationNavigation(RangeFactory::find($course_id)); + $navigation->setImage(Icon::create('consultation', Icon::ROLE_INFO_ALT)); + return ['consultation' => $navigation]; } - return ['consultation' => new ConsultationNavigation(RangeFactory::find($course_id))]; + return []; } /** diff --git a/lib/modules/CoursewareModule.class.php b/lib/modules/CoursewareModule.class.php index 0d8dfe89dcb..e07d5d20aa8 100644 --- a/lib/modules/CoursewareModule.class.php +++ b/lib/modules/CoursewareModule.class.php @@ -39,6 +39,7 @@ class CoursewareModule extends CorePlugin implements SystemPlugin, StudipModule, _('Courseware'), URLHelper::getURL('dispatch.php/course/courseware/?cid='.$courseId) ); + $navigation->setImage(Icon::create('courseware', Icon::ROLE_INFO_ALT)); $navigation->addSubNavigation( 'content', new Navigation(_('Inhalt'), 'dispatch.php/course/courseware/?cid='.$courseId) diff --git a/lib/navigation/FooterNavigation.php b/lib/navigation/FooterNavigation.php index d3f8b83ac36..a24dc119b54 100644 --- a/lib/navigation/FooterNavigation.php +++ b/lib/navigation/FooterNavigation.php @@ -27,6 +27,9 @@ class FooterNavigation extends Navigation { parent::initSubNavigation(); + // imprint + $this->addSubNavigation('siteinfo', new Navigation(_('Impressum'), 'dispatch.php/siteinfo/show?cancel_login=1')); + // sitemap if (is_object($GLOBALS['user']) && $GLOBALS['user']->id !== 'nobody') { $this->addSubNavigation('sitemap', new Navigation(_('Sitemap'), 'dispatch.php/sitemap/')); @@ -35,9 +38,6 @@ class FooterNavigation extends Navigation //studip $this->addSubNavigation('studip', new Navigation(_('Stud.IP'), 'http://www.studip.de/')); - // imprint - $this->addSubNavigation('siteinfo', new Navigation(_('Impressum'), 'dispatch.php/siteinfo/show?cancel_login=1')); - // Datenschutzerklärung //Check if the privacy url is one of the Stud.IP pages: diff --git a/lib/wiki.inc.php b/lib/wiki.inc.php index 29e50e448be..6d37e541686 100644 --- a/lib/wiki.inc.php +++ b/lib/wiki.inc.php @@ -1031,8 +1031,14 @@ function wikiEdit($keyword, $wikiData, $user_id, $backpage=NULL, $ancestor=NULL) } // Create content bar. - $contentBar = new ContentBar($toc, - $page_string, Icon::create('wiki'), $actionMenu); + $contentBar = ContentBar::get() + ->setTOC(CoreWiki::getTOC(WikiPage::getStartPage(Context::getId()))) + ->setInfo($page_string) + ->setIcon(Icon::create('wiki')); + + if ($actionMenu) { + $contentBar->setActionMenu($actionMenu); + } $template = $GLOBALS['template_factory']->open('wiki/edit.php'); $template->keyword = $keyword; @@ -1590,8 +1596,14 @@ function showWikiPage($keyword, $version, $special="", $show_comments="icon", $h } // Create content bar. - $contentBar = new ContentBar($toc, - $page_string, Icon::create('wiki'), $actionMenu); + $contentBar = ContentBar::get() + ->setTOC(CoreWiki::getTOC(WikiPage::getStartPage(Context::getId()))) + ->setInfo($page_string) + ->setIcon(Icon::create('wiki')); + + if ($actionMenu) { + $contentBar->setActionMenu($actionMenu); + } if ($hilight) { // Highlighting must only take place outside HTML tags, so diff --git a/public/assets/images/icons/black/focusmode-off.svg b/public/assets/images/icons/black/focusmode-off.svg new file mode 100644 index 00000000000..d7fe2e4a128 --- /dev/null +++ b/public/assets/images/icons/black/focusmode-off.svg @@ -0,0 +1 @@ +<svg width="16" height="16" id="icons" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 54 54" fill="#000000"><g id="fullscreen-off"><polygon points="51 20.13 33.87 20.13 33.87 2.99 37.29 2.99 37.29 16.7 51 16.7 51 20.13"/><polygon points="33.86 50.87 33.86 33.73 51 33.73 51 37.16 37.29 37.16 37.29 50.87 33.86 50.87"/><polygon points="20.16 50.97 16.73 50.97 16.73 37.26 3.02 37.26 3.02 33.84 20.16 33.84 20.16 50.97"/><polygon points="3.02 20.02 3.02 16.6 16.73 16.6 16.73 2.89 20.16 2.89 20.16 20.02 3.02 20.02"/></g></svg> diff --git a/public/assets/images/icons/blue/focusmode-off.svg b/public/assets/images/icons/blue/focusmode-off.svg new file mode 100644 index 00000000000..b7e965abb2c --- /dev/null +++ b/public/assets/images/icons/blue/focusmode-off.svg @@ -0,0 +1 @@ +<?xml version="1.0" encoding="UTF-8"?><svg id="b" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 56 56"><defs><style>.e{fill:#28497c;}</style></defs><g id="c"><g id="d"><polygon class="e" points="56 20 36 20 36 0 40 0 40 16 56 16 56 20"/><polygon class="e" points="20 56 16 56 16 40 0 40 0 36 20 36 20 56"/><polygon class="e" points="36 56 36 36 56 36 56 40 40 40 40 56 36 56"/><polygon class="e" points="0 20 0 16 16 16 16 0 20 0 20 20 0 20"/></g></g></svg> diff --git a/public/assets/images/icons/blue/fullscreen-off.svg b/public/assets/images/icons/blue/fullscreen-off.svg index 9f60e1a7397..b7e965abb2c 100644 --- a/public/assets/images/icons/blue/fullscreen-off.svg +++ b/public/assets/images/icons/blue/fullscreen-off.svg @@ -1 +1 @@ -<svg width="16" height="16" id="icons" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 54 54" fill="#28497c"><g id="fullscreen-off"><polygon points="51 20.13 33.87 20.13 33.87 2.99 37.29 2.99 37.29 16.7 51 16.7 51 20.13"/><polygon points="33.86 50.87 33.86 33.73 51 33.73 51 37.16 37.29 37.16 37.29 50.87 33.86 50.87"/><polygon points="20.16 50.97 16.73 50.97 16.73 37.26 3.02 37.26 3.02 33.84 20.16 33.84 20.16 50.97"/><polygon points="3.02 20.02 3.02 16.6 16.73 16.6 16.73 2.89 20.16 2.89 20.16 20.02 3.02 20.02"/></g></svg> +<?xml version="1.0" encoding="UTF-8"?><svg id="b" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 56 56"><defs><style>.e{fill:#28497c;}</style></defs><g id="c"><g id="d"><polygon class="e" points="56 20 36 20 36 0 40 0 40 16 56 16 56 20"/><polygon class="e" points="20 56 16 56 16 40 0 40 0 36 20 36 20 56"/><polygon class="e" points="36 56 36 36 56 36 56 40 40 40 40 56 36 56"/><polygon class="e" points="0 20 0 16 16 16 16 0 20 0 20 20 0 20"/></g></g></svg> diff --git a/public/assets/images/icons/blue/fullscreen-off2.svg b/public/assets/images/icons/blue/fullscreen-off2.svg new file mode 100644 index 00000000000..c10c1d1e5fa --- /dev/null +++ b/public/assets/images/icons/blue/fullscreen-off2.svg @@ -0,0 +1 @@ +<?xml version="1.0" encoding="UTF-8"?><svg id="b" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 52.83 52.83"><defs><style>.e{fill:#28497c;}</style></defs><g id="c"><g id="d"><polygon class="e" points="50.45 22.45 30.45 22.45 30.45 2.45 34.45 2.45 34.45 18.45 50.45 18.45 50.45 22.45"/><polygon class="e" points="22.45 50.45 18.45 50.45 18.45 34.45 2.45 34.45 2.45 30.45 22.45 30.45 22.45 50.45"/><polygon class="e" points="30.45 50.45 30.45 30.45 50.45 30.45 50.45 34.45 34.45 34.45 34.45 50.45 30.45 50.45"/><polygon class="e" points="2.45 22.45 2.45 18.45 18.45 18.45 18.45 2.45 22.45 2.45 22.45 22.45 2.45 22.45"/><rect class="e" x="8.95" y="-2.49" width="4" height="26.87" transform="translate(-4.54 10.95) rotate(-45)"/><rect class="e" x="39.66" y="28.53" width="4" height="26.26" transform="translate(-17.26 41.66) rotate(-45)"/><rect class="e" x="39.9" y="-2.53" width="4" height="26.92" transform="translate(20 -26.42) rotate(45)"/><rect class="e" x="8.93" y="28.44" width="4" height="26.92" transform="translate(32.83 4.54) rotate(45)"/></g></g></svg> diff --git a/public/assets/images/icons/blue/fullscreen-off3.svg b/public/assets/images/icons/blue/fullscreen-off3.svg new file mode 100644 index 00000000000..6830b2e06e4 --- /dev/null +++ b/public/assets/images/icons/blue/fullscreen-off3.svg @@ -0,0 +1 @@ +<?xml version="1.0" encoding="UTF-8"?><svg id="b" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 56 56"><defs><style>.e{fill:#28497c;}</style></defs><g id="c"><g id="d"><polygon class="e" points="44.01 20.01 36.01 20.01 36.01 12.01 32.06 8.06 32.01 8.02 32.01 24.01 48 24.01 47.96 23.96 44.01 20.01"/><rect class="e" x="13" y="5.1" width="4" height="19.8" transform="translate(-6.21 15) rotate(-45)"/><rect class="e" x="39.5" y="30.89" width="4" height="21.21" transform="translate(-17.19 41.5) rotate(-45)"/><rect class="e" x="38.96" y="5.06" width="4" height="19.8" transform="translate(22.58 -24.58) rotate(45)"/><rect class="e" x="12.71" y="30.96" width="4" height="20.51" transform="translate(33.45 1.67) rotate(45)"/><path class="e" d="M52,4V52H4V4H52M56,0H0V56H56V0h0Z"/><polygon class="e" points="11.99 36.02 20 36.02 20 44.03 23.95 47.98 24 48.01 24 32.02 8.01 32.02 8.05 32.07 11.99 36.02"/><polygon class="e" points="36.01 44.03 36.01 36.02 44.01 36.02 47.96 32.07 48 32.02 32.01 32.02 32.01 48.01 32.06 47.98 36.01 44.03"/><polygon class="e" points="20 12.01 20 20.01 11.99 20.01 8.05 23.96 8.01 24.01 24 24.01 24 8.02 23.95 8.06 20 12.01"/></g></g></svg> diff --git a/public/assets/images/icons/blue/fullscreen-on.svg b/public/assets/images/icons/blue/fullscreen-on.svg index 0ecc3e6a275..08c249cf959 100644 --- a/public/assets/images/icons/blue/fullscreen-on.svg +++ b/public/assets/images/icons/blue/fullscreen-on.svg @@ -1 +1 @@ -<svg width="16" height="16" id="icons" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 54 54" fill="#28497c"><g id="zoom-in-3"><polygon points="51 20.12 47.57 20.12 47.57 6.42 33.86 6.42 33.86 2.99 51 2.99 51 20.12"/><polygon points="20.15 50.97 3.02 50.97 3.02 33.83 6.44 33.83 6.44 47.54 20.15 47.54 20.15 50.97"/><polygon points="33.86 50.97 33.86 47.54 47.57 47.54 47.57 33.83 51 33.83 51 50.97 33.86 50.97"/><polygon points="3.02 20.12 3.02 2.99 20.15 2.99 20.15 6.42 6.44 6.42 6.44 20.12 3.02 20.12"/></g></svg> +<?xml version="1.0" encoding="UTF-8"?><svg id="b" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 55.98 55.98"><defs><style>.e{fill:#28497c;}</style></defs><g id="c"><g id="d"><g><polygon class="e" points="35.99 0 35.99 4 51.98 4 51.98 19.99 55.98 19.99 55.98 0 35.99 0"/><polygon class="e" points="4 35.99 0 35.99 0 55.98 19.99 55.98 19.99 51.98 4 51.98 4 35.99"/></g><g><polygon class="e" points="55.98 35.99 51.98 35.99 51.98 51.98 35.99 51.98 35.99 55.98 55.98 55.98 55.98 35.99"/><polygon class="e" points="19.99 4 19.99 0 0 0 0 19.99 4 19.99 4 4 19.99 4"/></g></g></g></svg> diff --git a/public/assets/images/icons/blue/fullscreen-on2.svg b/public/assets/images/icons/blue/fullscreen-on2.svg new file mode 100644 index 00000000000..16ba091dd1b --- /dev/null +++ b/public/assets/images/icons/blue/fullscreen-on2.svg @@ -0,0 +1 @@ +<?xml version="1.0" encoding="UTF-8"?><svg id="b" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 55.98 55.98"><defs><style>.e{fill:#28497c;}</style></defs><g id="c"><g id="d"><g><polygon class="e" points="35.99 0 35.99 4 51.98 4 51.98 19.99 55.98 19.99 55.98 0 35.99 0"/><polygon class="e" points="4 35.99 0 35.99 0 55.98 19.99 55.98 19.99 51.98 4 51.98 4 35.99"/></g><g><polygon class="e" points="55.98 35.99 51.98 35.99 51.98 51.98 35.99 51.98 35.99 55.98 55.98 55.98 55.98 35.99"/><polygon class="e" points="19.99 4 19.99 0 0 0 0 19.99 4 19.99 4 4 19.99 4"/></g><rect class="e" x="9.44" y="-2.04" width="4" height="26.97" transform="translate(-4.74 11.44) rotate(-45)"/><rect class="e" x="42.48" y="31.04" width="4" height="26.87" transform="translate(-18.42 44.48) rotate(-45)"/><rect class="e" x="42.46" y="-2.03" width="4" height="26.92" transform="translate(21.1 -28.09) rotate(45)"/><rect class="e" x="9.43" y="31" width="4" height="26.92" transform="translate(34.79 4.94) rotate(45)"/></g></g></svg> diff --git a/public/assets/images/icons/blue/fullscreen-on3.svg b/public/assets/images/icons/blue/fullscreen-on3.svg new file mode 100644 index 00000000000..bdaad6d5942 --- /dev/null +++ b/public/assets/images/icons/blue/fullscreen-on3.svg @@ -0,0 +1 @@ +<?xml version="1.0" encoding="UTF-8"?><svg id="b" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 56 56"><defs><style>.e{fill:#28497c;}</style></defs><g id="c"><g id="d"><g><polygon class="e" points="35.99 12.02 44 12.02 44 20.03 47.95 23.98 48 24.01 48 8.02 32.01 8.02 32.05 8.07 35.99 12.02"/><polygon class="e" points="20.01 44.01 12.01 44.01 12.01 36.01 8.06 32.06 8.01 32.02 8.01 48.01 24 48.01 23.96 47.96 20.01 44.01"/><polygon class="e" points="44 36.01 44 44.01 35.99 44.01 32.05 47.96 32.01 48.01 48 48.01 48 32.02 47.95 32.06 44 36.01"/><polygon class="e" points="12.01 20.03 12.01 12.02 20.01 12.02 23.96 8.07 24 8.02 8.01 8.02 8.01 24.01 8.06 23.98 12.01 20.03"/></g><rect class="e" x="16" y="8.1" width="4" height="19.8" transform="translate(-7.46 18) rotate(-45)"/><rect class="e" x="36.5" y="27.89" width="4" height="21.21" transform="translate(-15.95 38.5) rotate(-45)"/><rect class="e" x="35.96" y="8.06" width="4" height="19.8" transform="translate(23.82 -21.58) rotate(45)"/><rect class="e" x="15.71" y="27.96" width="4" height="20.51" transform="translate(32.21 -1.33) rotate(45)"/><path class="e" d="M52,4V52H4V4H52M56,0H0V56H56V0h0Z"/></g></g></svg> diff --git a/public/assets/images/icons/blue/fullscreen-on4.svg b/public/assets/images/icons/blue/fullscreen-on4.svg new file mode 100644 index 00000000000..da6502ec76f --- /dev/null +++ b/public/assets/images/icons/blue/fullscreen-on4.svg @@ -0,0 +1 @@ +<?xml version="1.0" encoding="UTF-8"?><svg id="b" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 55.98 55.98"><defs><style>.e{fill:#28497c;}</style></defs><g id="c"><g id="d"><g><polygon class="e" points="35.99 0 35.99 4 51.98 4 51.98 19.99 55.98 19.99 55.98 0 35.99 0"/><polygon class="e" points="4 35.99 0 35.99 0 55.98 19.99 55.98 19.99 51.98 4 51.98 4 35.99"/></g><g><polygon class="e" points="55.98 35.99 51.98 35.99 51.98 51.98 35.99 51.98 35.99 55.98 55.98 55.98 55.98 35.99"/><polygon class="e" points="19.99 4 19.99 0 0 0 0 19.99 4 19.99 4 4 19.99 4"/></g><rect class="e" x="7.98" y="11.98" width="40" height="8"/><path class="e" d="M43.98,15.98v24H11.98V15.98H43.98m4-4H7.98V43.98H47.98V11.98h0Z"/></g></g></svg> diff --git a/public/assets/images/icons/blue/fullscreen-on5.svg b/public/assets/images/icons/blue/fullscreen-on5.svg new file mode 100644 index 00000000000..8566dece4ed --- /dev/null +++ b/public/assets/images/icons/blue/fullscreen-on5.svg @@ -0,0 +1 @@ +<?xml version="1.0" encoding="UTF-8"?><svg id="b" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 55.98 55.98"><defs><style>.e{fill:#28497c;}</style></defs><g id="c"><g id="d"><g><polygon class="e" points="35.99 0 35.99 4 51.98 4 51.98 49.99 55.98 49.99 55.98 0 35.99 0"/><polygon class="e" points="4 5.99 0 5.99 0 55.98 19.99 55.98 19.99 51.98 4 51.98 4 5.99"/></g><g><polygon class="e" points="55.98 35.99 51.98 35.99 51.98 51.98 5.99 51.98 5.99 55.98 55.98 55.98 55.98 35.99"/><polygon class="e" points="49.99 4 49.99 0 0 0 0 19.99 4 19.99 4 4 49.99 4"/></g><rect class="e" x="7.98" y="19.98" width="32" height="8"/><path class="e" d="M35.98,23.98v20H11.98V23.98h24m4-4H7.98v28H39.98V19.98h0Z"/></g></g></svg> diff --git a/public/assets/images/icons/blue/maximise.svg b/public/assets/images/icons/blue/maximise.svg new file mode 100644 index 00000000000..b096fc030bb --- /dev/null +++ b/public/assets/images/icons/blue/maximise.svg @@ -0,0 +1 @@ +<?xml version="1.0" encoding="UTF-8"?><svg id="b" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 56 56"><defs><style>.e{fill:#28497c;}</style></defs><g id="c"><g id="d"><path class="e" d="M52,0H14V22H0V56H32v-14h24V0h-4ZM28,52H4V26H14v16h14v10Zm24-14H18V10H52v28Z"/></g></g></svg> diff --git a/public/assets/images/icons/blue/sidebar-open.svg b/public/assets/images/icons/blue/sidebar-open.svg new file mode 100644 index 00000000000..3b7dff252a2 --- /dev/null +++ b/public/assets/images/icons/blue/sidebar-open.svg @@ -0,0 +1 @@ +<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><defs><style>.f{fill:none;}.g{fill:#28497c;}</style></defs><g id="a"/><g id="b"><g id="c"><rect class="f" width="64" height="64"/></g><g id="d"><g id="e"><g><path class="g" d="M20,12v21H52V12H20Zm28,17H24V16h24v13Z"/><path class="g" d="M20,52H52v-15H20v15Zm4-11h24v7H24v-7Z"/><polygon class="g" points="16 8 36 8 36 4 12 4 12 60 36 60 36 56 16 56 16 8"/></g></g></g></g></svg> diff --git a/public/assets/images/icons/blue/sidebar.svg b/public/assets/images/icons/blue/sidebar.svg new file mode 100644 index 00000000000..676661efe8b --- /dev/null +++ b/public/assets/images/icons/blue/sidebar.svg @@ -0,0 +1 @@ +<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><defs><style>.f{fill:none;}.g{fill:#28497c;}</style></defs><g id="a"/><g id="b"><g id="c"><rect class="f" width="64" height="64"/></g><g id="d"><g id="e"><g><path class="g" d="M12,12v21h32V12H12Zm4,4h24v13H16V16Z"/><path class="g" d="M44,37H12v15h32v-15Zm-4,11H16v-7h24v7Z"/><polygon class="g" points="48 8 28 8 28 4 52 4 52 60 28 60 28 56 48 56 48 8"/></g></g></g></g></svg> diff --git a/public/assets/images/icons/blue/sidebar3.svg b/public/assets/images/icons/blue/sidebar3.svg new file mode 100644 index 00000000000..ab4b9b39d6a --- /dev/null +++ b/public/assets/images/icons/blue/sidebar3.svg @@ -0,0 +1 @@ +<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><defs><style>.f{fill:none;}.g{fill:#28497c;}</style></defs><g id="a"/><g id="b"><g id="c"><rect class="f" width="64" height="64"/></g><g id="d"><g id="e"><path class="g" d="M56,8V56H8V8H56m4-4H4V60H60V4h0Z"/><polygon class="g" points="26 6 22 6 22 58 26 58 26 6 26 6"/><rect class="g" x="7" y="6" width="19" height="52"/></g></g></g></svg> diff --git a/public/assets/images/icons/blue/zoom-in.svg b/public/assets/images/icons/blue/zoom-in.svg index 36f054e2c40..bf307ed2aec 100644 --- a/public/assets/images/icons/blue/zoom-in.svg +++ b/public/assets/images/icons/blue/zoom-in.svg @@ -1 +1 @@ -<svg width="16" height="16" id="icons" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 54 54" fill="#28497c"><g id="zoom-in"><polygon points="30.44 2.99 30.44 6.42 47.57 6.42 47.57 23.55 51 23.55 51 2.99 30.44 2.99"/><polygon points="6.44 30.41 3.02 30.41 3.02 50.97 23.58 50.97 23.58 47.54 6.44 47.54 6.44 30.41"/></g></svg> +<?xml version="1.0" encoding="UTF-8"?><svg id="b" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 55.98 55.98"><defs><style>.e{fill:#28497c;}</style></defs><g id="c"><g id="d"><g><polygon class="e" points="31.99 0 31.99 4 51.98 4 51.98 23.99 55.98 23.99 55.98 0 31.99 0"/><polygon class="e" points="4 31.99 0 31.99 0 55.98 23.99 55.98 23.99 51.98 4 51.98 4 31.99"/></g></g></g></svg> diff --git a/public/assets/images/icons/blue/zoom-in2.svg b/public/assets/images/icons/blue/zoom-in2.svg index 3056bc405b2..65b590d240a 100644 --- a/public/assets/images/icons/blue/zoom-in2.svg +++ b/public/assets/images/icons/blue/zoom-in2.svg @@ -1 +1 @@ -<svg width="16" height="16" id="icons" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 54 54" fill="#28497c"><g id="zoom-in2"><path d="M39,15V39H15V15H39m3-3H12V42H42V12Z"/><polygon points="50.98 20.99 47.99 20.99 47.99 6 32.99 6 32.99 3 50.98 3 50.98 20.99"/><polygon points="21 50.98 3.01 50.98 3.01 32.99 6 32.99 6 47.98 21 47.98 21 50.98"/></g></svg> +<?xml version="1.0" encoding="UTF-8"?><svg id="b" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 55.98 55.98"><defs><style>.e{fill:#28497c;}</style></defs><g id="c"><g id="d"><g><path class="e" d="M10.98,44.98H44.98V10.98H10.98V44.98ZM14.98,14.98h26v26H14.98V14.98Z"/><polygon class="e" points="35.99 0 35.99 4 51.98 4 51.98 19.99 55.98 19.99 55.98 0 35.99 0"/><polygon class="e" points="4 35.99 0 35.99 0 55.98 19.99 55.98 19.99 51.98 4 51.98 4 35.99"/></g></g></g></svg> diff --git a/public/assets/images/icons/blue/zoom-out.svg b/public/assets/images/icons/blue/zoom-out.svg index ef38c356fba..3a0a4133bc3 100644 --- a/public/assets/images/icons/blue/zoom-out.svg +++ b/public/assets/images/icons/blue/zoom-out.svg @@ -1 +1 @@ -<svg width="16" height="16" id="icons" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 54 54" fill="#28497c"><g id="zoom-out"><polygon points="51 23.55 30.44 23.55 30.44 2.99 33.86 2.99 33.86 20.12 51 20.12 51 23.55"/><polygon points="23.58 50.97 20.15 50.97 20.15 33.83 3.02 33.83 3.02 30.41 23.58 30.41 23.58 50.97"/></g></svg> +<?xml version="1.0" encoding="UTF-8"?><svg id="b" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 55.98 55.98"><defs><style>.e{fill:#28497c;}</style></defs><g id="c"><g id="d"><polygon class="e" points="55.98 23.99 31.99 23.99 31.99 0 35.99 0 35.99 19.99 55.98 19.99 55.98 23.99"/><polygon class="e" points="23.99 55.98 19.99 55.98 19.99 35.99 0 35.99 0 31.99 23.99 31.99 23.99 55.98"/></g></g></svg> diff --git a/public/assets/images/icons/blue/zoom-out2.svg b/public/assets/images/icons/blue/zoom-out2.svg index 67e11b3da43..d660c602b5c 100644 --- a/public/assets/images/icons/blue/zoom-out2.svg +++ b/public/assets/images/icons/blue/zoom-out2.svg @@ -1 +1 @@ -<svg width="16" height="16" id="icons" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 54 54" fill="#28497c"><g id="zoom-out2"><path d="M51,18H42V12H36V3H33v9H12V33H3v3h9v6h6v9h3V42H42V21h9ZM39,15v3H36V15ZM15,39V36h3v3Zm24,0H21V33H15V15H33v6h6Z"/></g></svg> +<?xml version="1.0" encoding="UTF-8"?><svg id="b" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 55.98 55.98"><defs><style>.e{fill:#28497c;}</style></defs><g id="c"><g id="d"><path class="e" d="M55.98,15.99h-8V7.98h-7.99V0h-4V7.98H7.98v28.01H0v4H7.98v7.99H15.99v8h4v-8h27.99V19.99h8v-4Zm-12-4.01v4.01h-3.99v-4.01h3.99ZM11.98,43.98v-3.99h4.01v3.99h-4.01Zm32,0H19.99v-7.99H11.98V11.98h24.01v8.01h7.99v23.99Z"/></g></g></svg> diff --git a/public/assets/images/icons/green/focusmode-off.svg b/public/assets/images/icons/green/focusmode-off.svg new file mode 100644 index 00000000000..b5aa40664b3 --- /dev/null +++ b/public/assets/images/icons/green/focusmode-off.svg @@ -0,0 +1 @@ +<svg width="16" height="16" id="icons" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 54 54" fill="#00962d"><g id="fullscreen-off"><polygon points="51 20.13 33.87 20.13 33.87 2.99 37.29 2.99 37.29 16.7 51 16.7 51 20.13"/><polygon points="33.86 50.87 33.86 33.73 51 33.73 51 37.16 37.29 37.16 37.29 50.87 33.86 50.87"/><polygon points="20.16 50.97 16.73 50.97 16.73 37.26 3.02 37.26 3.02 33.84 20.16 33.84 20.16 50.97"/><polygon points="3.02 20.02 3.02 16.6 16.73 16.6 16.73 2.89 20.16 2.89 20.16 20.02 3.02 20.02"/></g></svg> diff --git a/public/assets/images/icons/grey/focusmode-off.svg b/public/assets/images/icons/grey/focusmode-off.svg new file mode 100644 index 00000000000..6509d7976b2 --- /dev/null +++ b/public/assets/images/icons/grey/focusmode-off.svg @@ -0,0 +1 @@ +<svg width="16" height="16" id="icons" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 54 54" fill="#6e6e6e"><g id="fullscreen-off"><polygon points="51 20.13 33.87 20.13 33.87 2.99 37.29 2.99 37.29 16.7 51 16.7 51 20.13"/><polygon points="33.86 50.87 33.86 33.73 51 33.73 51 37.16 37.29 37.16 37.29 50.87 33.86 50.87"/><polygon points="20.16 50.97 16.73 50.97 16.73 37.26 3.02 37.26 3.02 33.84 20.16 33.84 20.16 50.97"/><polygon points="3.02 20.02 3.02 16.6 16.73 16.6 16.73 2.89 20.16 2.89 20.16 20.02 3.02 20.02"/></g></svg> diff --git a/public/assets/images/icons/red/focusmode-off.svg b/public/assets/images/icons/red/focusmode-off.svg new file mode 100644 index 00000000000..9767595021a --- /dev/null +++ b/public/assets/images/icons/red/focusmode-off.svg @@ -0,0 +1 @@ +<svg width="16" height="16" id="icons" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 54 54" fill="#cb1800"><g id="fullscreen-off"><polygon points="51 20.13 33.87 20.13 33.87 2.99 37.29 2.99 37.29 16.7 51 16.7 51 20.13"/><polygon points="33.86 50.87 33.86 33.73 51 33.73 51 37.16 37.29 37.16 37.29 50.87 33.86 50.87"/><polygon points="20.16 50.97 16.73 50.97 16.73 37.26 3.02 37.26 3.02 33.84 20.16 33.84 20.16 50.97"/><polygon points="3.02 20.02 3.02 16.6 16.73 16.6 16.73 2.89 20.16 2.89 20.16 20.02 3.02 20.02"/></g></svg> diff --git a/public/assets/images/icons/white/focusmode-off.svg b/public/assets/images/icons/white/focusmode-off.svg new file mode 100644 index 00000000000..f852c7fb908 --- /dev/null +++ b/public/assets/images/icons/white/focusmode-off.svg @@ -0,0 +1 @@ +<?xml version="1.0" encoding="UTF-8"?><svg id="b" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 56 56"><defs><style>.e{fill:#fff;}</style></defs><g id="c"><g id="d"><polygon class="e" points="56 20 36 20 36 0 40 0 40 16 56 16 56 20"/><polygon class="e" points="20 56 16 56 16 40 0 40 0 36 20 36 20 56"/><polygon class="e" points="36 56 36 36 56 36 56 40 40 40 40 56 36 56"/><polygon class="e" points="0 20 0 16 16 16 16 0 20 0 20 20 0 20"/></g></g></svg> diff --git a/public/assets/images/icons/white/fullscreen-off.svg b/public/assets/images/icons/white/fullscreen-off.svg index 2709d176a1c..f852c7fb908 100644 --- a/public/assets/images/icons/white/fullscreen-off.svg +++ b/public/assets/images/icons/white/fullscreen-off.svg @@ -1 +1 @@ -<svg width="16" height="16" id="icons" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 54 54" fill="#ffffff"><g id="fullscreen-off"><polygon points="51 20.13 33.87 20.13 33.87 2.99 37.29 2.99 37.29 16.7 51 16.7 51 20.13"/><polygon points="33.86 50.87 33.86 33.73 51 33.73 51 37.16 37.29 37.16 37.29 50.87 33.86 50.87"/><polygon points="20.16 50.97 16.73 50.97 16.73 37.26 3.02 37.26 3.02 33.84 20.16 33.84 20.16 50.97"/><polygon points="3.02 20.02 3.02 16.6 16.73 16.6 16.73 2.89 20.16 2.89 20.16 20.02 3.02 20.02"/></g></svg> +<?xml version="1.0" encoding="UTF-8"?><svg id="b" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 56 56"><defs><style>.e{fill:#fff;}</style></defs><g id="c"><g id="d"><polygon class="e" points="56 20 36 20 36 0 40 0 40 16 56 16 56 20"/><polygon class="e" points="20 56 16 56 16 40 0 40 0 36 20 36 20 56"/><polygon class="e" points="36 56 36 36 56 36 56 40 40 40 40 56 36 56"/><polygon class="e" points="0 20 0 16 16 16 16 0 20 0 20 20 0 20"/></g></g></svg> diff --git a/public/assets/images/icons/white/fullscreen-off2.svg b/public/assets/images/icons/white/fullscreen-off2.svg new file mode 100644 index 00000000000..5c7b0c61b72 --- /dev/null +++ b/public/assets/images/icons/white/fullscreen-off2.svg @@ -0,0 +1 @@ +<?xml version="1.0" encoding="UTF-8"?><svg id="b" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 52.83 52.83"><defs><style>.e{fill:#fff;}</style></defs><g id="c"><g id="d"><polygon class="e" points="50.45 22.45 30.45 22.45 30.45 2.45 34.45 2.45 34.45 18.45 50.45 18.45 50.45 22.45"/><polygon class="e" points="22.45 50.45 18.45 50.45 18.45 34.45 2.45 34.45 2.45 30.45 22.45 30.45 22.45 50.45"/><polygon class="e" points="30.45 50.45 30.45 30.45 50.45 30.45 50.45 34.45 34.45 34.45 34.45 50.45 30.45 50.45"/><polygon class="e" points="2.45 22.45 2.45 18.45 18.45 18.45 18.45 2.45 22.45 2.45 22.45 22.45 2.45 22.45"/><rect class="e" x="8.95" y="-2.49" width="4" height="26.87" transform="translate(-4.54 10.95) rotate(-45)"/><rect class="e" x="39.66" y="28.53" width="4" height="26.26" transform="translate(-17.26 41.66) rotate(-45)"/><rect class="e" x="39.9" y="-2.53" width="4" height="26.92" transform="translate(20 -26.42) rotate(45)"/><rect class="e" x="8.93" y="28.44" width="4" height="26.92" transform="translate(32.83 4.54) rotate(45)"/></g></g></svg> diff --git a/public/assets/images/icons/white/fullscreen-off3.svg b/public/assets/images/icons/white/fullscreen-off3.svg new file mode 100644 index 00000000000..d51bf9d6dde --- /dev/null +++ b/public/assets/images/icons/white/fullscreen-off3.svg @@ -0,0 +1 @@ +<?xml version="1.0" encoding="UTF-8"?><svg id="b" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 56 56"><defs><style>.e{fill:#fff;}</style></defs><g id="c"><g id="d"><polygon class="e" points="44.01 20.01 36.01 20.01 36.01 12.01 32.06 8.06 32.01 8.02 32.01 24.01 48 24.01 47.96 23.96 44.01 20.01"/><rect class="e" x="13" y="5.1" width="4" height="19.8" transform="translate(-6.21 15) rotate(-45)"/><rect class="e" x="39.5" y="30.89" width="4" height="21.21" transform="translate(-17.19 41.5) rotate(-45)"/><rect class="e" x="38.96" y="5.06" width="4" height="19.8" transform="translate(22.58 -24.58) rotate(45)"/><rect class="e" x="12.71" y="30.96" width="4" height="20.51" transform="translate(33.45 1.67) rotate(45)"/><path class="e" d="M52,4V52H4V4H52M56,0H0V56H56V0h0Z"/><polygon class="e" points="11.99 36.02 20 36.02 20 44.03 23.95 47.98 24 48.01 24 32.02 8.01 32.02 8.05 32.07 11.99 36.02"/><polygon class="e" points="36.01 44.03 36.01 36.02 44.01 36.02 47.96 32.07 48 32.02 32.01 32.02 32.01 48.01 32.06 47.98 36.01 44.03"/><polygon class="e" points="20 12.01 20 20.01 11.99 20.01 8.05 23.96 8.01 24.01 24 24.01 24 8.02 23.95 8.06 20 12.01"/></g></g></svg> diff --git a/public/assets/images/icons/white/fullscreen-on.svg b/public/assets/images/icons/white/fullscreen-on.svg index a59f1926d5e..6c1066524e5 100644 --- a/public/assets/images/icons/white/fullscreen-on.svg +++ b/public/assets/images/icons/white/fullscreen-on.svg @@ -1 +1 @@ -<svg width="16" height="16" id="icons" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 54 54" fill="#ffffff"><g id="zoom-in-3"><polygon points="51 20.12 47.57 20.12 47.57 6.42 33.86 6.42 33.86 2.99 51 2.99 51 20.12"/><polygon points="20.15 50.97 3.02 50.97 3.02 33.83 6.44 33.83 6.44 47.54 20.15 47.54 20.15 50.97"/><polygon points="33.86 50.97 33.86 47.54 47.57 47.54 47.57 33.83 51 33.83 51 50.97 33.86 50.97"/><polygon points="3.02 20.12 3.02 2.99 20.15 2.99 20.15 6.42 6.44 6.42 6.44 20.12 3.02 20.12"/></g></svg> +<?xml version="1.0" encoding="UTF-8"?><svg id="b" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 55.98 55.98"><defs><style>.e{fill:#fff;}</style></defs><g id="c"><g id="d"><g><polygon class="e" points="35.99 0 35.99 4 51.98 4 51.98 19.99 55.98 19.99 55.98 0 35.99 0"/><polygon class="e" points="4 35.99 0 35.99 0 55.98 19.99 55.98 19.99 51.98 4 51.98 4 35.99"/></g><g><polygon class="e" points="55.98 35.99 51.98 35.99 51.98 51.98 35.99 51.98 35.99 55.98 55.98 55.98 55.98 35.99"/><polygon class="e" points="19.99 4 19.99 0 0 0 0 19.99 4 19.99 4 4 19.99 4"/></g></g></g></svg> diff --git a/public/assets/images/icons/white/fullscreen-on2.svg b/public/assets/images/icons/white/fullscreen-on2.svg new file mode 100644 index 00000000000..bc25ed8454d --- /dev/null +++ b/public/assets/images/icons/white/fullscreen-on2.svg @@ -0,0 +1 @@ +<?xml version="1.0" encoding="UTF-8"?><svg id="b" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 55.98 55.98"><defs><style>.e{fill:#fff;}</style></defs><g id="c"><g id="d"><g><polygon class="e" points="35.99 0 35.99 4 51.98 4 51.98 19.99 55.98 19.99 55.98 0 35.99 0"/><polygon class="e" points="4 35.99 0 35.99 0 55.98 19.99 55.98 19.99 51.98 4 51.98 4 35.99"/></g><g><polygon class="e" points="55.98 35.99 51.98 35.99 51.98 51.98 35.99 51.98 35.99 55.98 55.98 55.98 55.98 35.99"/><polygon class="e" points="19.99 4 19.99 0 0 0 0 19.99 4 19.99 4 4 19.99 4"/></g><rect class="e" x="9.44" y="-2.04" width="4" height="26.97" transform="translate(-4.74 11.44) rotate(-45)"/><rect class="e" x="42.48" y="31.04" width="4" height="26.87" transform="translate(-18.42 44.48) rotate(-45)"/><rect class="e" x="42.46" y="-2.03" width="4" height="26.92" transform="translate(21.1 -28.09) rotate(45)"/><rect class="e" x="9.43" y="31" width="4" height="26.92" transform="translate(34.79 4.94) rotate(45)"/></g></g></svg> diff --git a/public/assets/images/icons/white/fullscreen-on3.svg b/public/assets/images/icons/white/fullscreen-on3.svg new file mode 100644 index 00000000000..0492439d477 --- /dev/null +++ b/public/assets/images/icons/white/fullscreen-on3.svg @@ -0,0 +1 @@ +<?xml version="1.0" encoding="UTF-8"?><svg id="b" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 56 56"><defs><style>.e{fill:#fff;}</style></defs><g id="c"><g id="d"><g><polygon class="e" points="35.99 12.02 44 12.02 44 20.03 47.95 23.98 48 24.01 48 8.02 32.01 8.02 32.05 8.07 35.99 12.02"/><polygon class="e" points="20.01 44.01 12.01 44.01 12.01 36.01 8.06 32.06 8.01 32.02 8.01 48.01 24 48.01 23.96 47.96 20.01 44.01"/><polygon class="e" points="44 36.01 44 44.01 35.99 44.01 32.05 47.96 32.01 48.01 48 48.01 48 32.02 47.95 32.06 44 36.01"/><polygon class="e" points="12.01 20.03 12.01 12.02 20.01 12.02 23.96 8.07 24 8.02 8.01 8.02 8.01 24.01 8.06 23.98 12.01 20.03"/></g><rect class="e" x="16" y="8.1" width="4" height="19.8" transform="translate(-7.46 18) rotate(-45)"/><rect class="e" x="36.5" y="27.89" width="4" height="21.21" transform="translate(-15.95 38.5) rotate(-45)"/><rect class="e" x="35.96" y="8.06" width="4" height="19.8" transform="translate(23.82 -21.58) rotate(45)"/><rect class="e" x="15.71" y="27.96" width="4" height="20.51" transform="translate(32.21 -1.33) rotate(45)"/><path class="e" d="M52,4V52H4V4H52M56,0H0V56H56V0h0Z"/></g></g></svg> diff --git a/public/assets/images/icons/white/fullscreen-on4.svg b/public/assets/images/icons/white/fullscreen-on4.svg new file mode 100644 index 00000000000..0e406efc1a9 --- /dev/null +++ b/public/assets/images/icons/white/fullscreen-on4.svg @@ -0,0 +1 @@ +<?xml version="1.0" encoding="UTF-8"?><svg id="b" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 55.98 55.98"><defs><style>.e{fill:#fff;}</style></defs><g id="c"><g id="d"><g><polygon class="e" points="35.99 0 35.99 4 51.98 4 51.98 19.99 55.98 19.99 55.98 0 35.99 0"/><polygon class="e" points="4 35.99 0 35.99 0 55.98 19.99 55.98 19.99 51.98 4 51.98 4 35.99"/></g><g><polygon class="e" points="55.98 35.99 51.98 35.99 51.98 51.98 35.99 51.98 35.99 55.98 55.98 55.98 55.98 35.99"/><polygon class="e" points="19.99 4 19.99 0 0 0 0 19.99 4 19.99 4 4 19.99 4"/></g><rect class="e" x="7.98" y="11.98" width="40" height="8"/><path class="e" d="M43.98,15.98v24H11.98V15.98H43.98m4-4H7.98V43.98H47.98V11.98h0Z"/></g></g></svg> diff --git a/public/assets/images/icons/yellow/focusmode-off.svg b/public/assets/images/icons/yellow/focusmode-off.svg new file mode 100644 index 00000000000..a98b0e00c12 --- /dev/null +++ b/public/assets/images/icons/yellow/focusmode-off.svg @@ -0,0 +1 @@ +<svg width="16" height="16" id="icons" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 54 54" fill="#ffad00"><g id="fullscreen-off"><polygon points="51 20.13 33.87 20.13 33.87 2.99 37.29 2.99 37.29 16.7 51 16.7 51 20.13"/><polygon points="33.86 50.87 33.86 33.73 51 33.73 51 37.16 37.29 37.16 37.29 50.87 33.86 50.87"/><polygon points="20.16 50.97 16.73 50.97 16.73 37.26 3.02 37.26 3.02 33.84 20.16 33.84 20.16 50.97"/><polygon points="3.02 20.02 3.02 16.6 16.73 16.6 16.73 2.89 20.16 2.89 20.16 20.02 3.02 20.02"/></g></svg> diff --git a/resources/assets/javascripts/bootstrap/responsive-navigation.js b/resources/assets/javascripts/bootstrap/responsive-navigation.js new file mode 100644 index 00000000000..aa811078ea7 --- /dev/null +++ b/resources/assets/javascripts/bootstrap/responsive-navigation.js @@ -0,0 +1,10 @@ +import ResponsiveNavigation from '../../../vue/components/responsive/ResponsiveNavigation.vue'; + +STUDIP.ready(() => { + STUDIP.Vue.load().then(({ createApp }) => { + createApp({ + el: '#responsive-menu', + components: { ResponsiveNavigation } + }); + }); +}); diff --git a/resources/assets/javascripts/bootstrap/responsive.js b/resources/assets/javascripts/bootstrap/responsive.js index e7c1aed4f2f..8e216d08ba8 100644 --- a/resources/assets/javascripts/bootstrap/responsive.js +++ b/resources/assets/javascripts/bootstrap/responsive.js @@ -9,6 +9,11 @@ STUDIP.domReady(() => { } STUDIP.Responsive.engage(); + + if (STUDIP.Responsive.isFullscreen()) { + document.querySelector('html').classList.add('fullscreen-mode'); + } + }, true); // Trigger search in responsive display diff --git a/resources/assets/javascripts/entry-base.js b/resources/assets/javascripts/entry-base.js index 09779ddb164..b577ce49a2f 100644 --- a/resources/assets/javascripts/entry-base.js +++ b/resources/assets/javascripts/entry-base.js @@ -87,6 +87,7 @@ import "./bootstrap/cache-admin.js" import "./bootstrap/oer.js" import "./bootstrap/courseware.js" import "./bootstrap/beforeunload-event-listener.js" +import "./bootstrap/responsive-navigation.js" import "./mvv_course_wizard.js" import "./mvv.js" diff --git a/resources/assets/javascripts/lib/header_magic.js b/resources/assets/javascripts/lib/header_magic.js index 410b7543753..f465e7e95b8 100644 --- a/resources/assets/javascripts/lib/header_magic.js +++ b/resources/assets/javascripts/lib/header_magic.js @@ -10,27 +10,6 @@ const scroll = function(scrolltop) { if (is_below_the_fold !== was_below_the_fold) { $('body').toggleClass('fixed', is_below_the_fold); - menu = $('#navigation-level-1-items').remove(); - if (is_below_the_fold) { - menu.append( - $('.action-menu-list li', menu) - .remove() - .addClass('from-action-menu') - ); - menu.appendTo('#responsive-menu'); - } else { - $('.action-menu-list', menu).append( - $('.from-action-menu', menu) - .remove() - .removeClass('from-action-menu') - ); - menu.prependTo('#navigation-level-1'); - - NavigationShrinker(); - - $('#navigation-level-1-items-toggle').prop('checked', false); - } - was_below_the_fold = is_below_the_fold; } }; diff --git a/resources/assets/javascripts/lib/responsive.js b/resources/assets/javascripts/lib/responsive.js index 16e63541f49..a493e8ddb7b 100644 --- a/resources/assets/javascripts/lib/responsive.js +++ b/resources/assets/javascripts/lib/responsive.js @@ -4,127 +4,6 @@ import Sidebar from './sidebar.js'; const Responsive = { media_query: window.matchMedia('(max-width: 767px)'), - // Builds a dom element from a navigation object - buildMenu (navigation, id, activated) { - var list = $('<ul>'); - - if (id) { - list.attr('id', id); - } - - // TODO: Templating? - _.forEach(navigation, (nav, node) => { - nav.url = STUDIP.URLHelper.getURL(nav.url, {}, true); - let li = $('<li class="navigation-item">'); - let title = $('<div class="nav-title">').appendTo(li); - let link = $(`<a href="${nav.url}">`).text(nav.title).appendTo(title); - - if (nav.icon) { - if (!nav.icon.match(/^https?:\/\//)) { - nav.icon = STUDIP.ASSETS_URL + nav.icon; - } - $(link).prepend(`<img class="icon" src="${nav.icon}">`); - } - - if (nav.children) { - let active = activated.indexOf(node) !== -1; - $(`<input type="checkbox" id="resp/${node}">`) - .prop('checked', active) - .appendTo(li); - li.append( - `<label class="nav-label" for="resp/${node}"> </label>`, - Responsive.buildMenu(nav.children, false, activated) - ); - } - - list.append(li); - }); - - return list; - }, - - // Adds the responsive menu to the dom - addMenu () { - let wrapper = $('<div id="responsive-container">').append( - '<label for="responsive-toggle">', - '<input type="checkbox" id="responsive-toggle">', - Responsive.buildMenu( - STUDIP.Navigation.navigation, - 'responsive-navigation', - STUDIP.Navigation.activated - ), - '<label for="responsive-toggle">' - ); - - $('<li>', { html: wrapper }).prependTo('#header-links > ul'); - }, - - // Responsifies the layout. Builds the responsive menu from existing - // STUDIP.Navigation object - responsify () { - Responsive.media_query.removeListener(Responsive.responsify); - - $('html').addClass('responsified'); - - Responsive.addMenu(); - - if ($('#sidebar > section').length > 0) { - $('<li id="sidebar-menu">') - .on('click', () => Sidebar.open()) - .appendTo('#header-links > ul'); - - $('<label id="sidebar-shadow-toggle">') - .on('click', () => Sidebar.close()) - .prependTo('#sidebar'); - - $('#responsive-toggle').on('change', function() { - $('#sidebar').removeClass('visible-sidebar'); - $('#responsive-navigation').toggleClass('visible', this.checked); - }); - } else { - $('#responsive-toggle').on('change', function() { - $('#responsive-navigation').toggleClass('visible', this.checked); - }); - } - - $('#responsive-navigation :checkbox').on('change', function () { - let li = $(this).closest('li'); - if ($(this).is(':checked')) { - li.siblings().find(':checkbox:checked').prop('checked', false); - } - - // Force redraw of submenu (at least ios safari/chrome would - // not show it without a forced redraw) - $(this).siblings('ul').hide(0, function () { - $(this).show(); - }); - }).reverse().trigger('change'); - - var sidebar_avatar_menu = $('<div class="sidebar-widget sidebar-avatar-menu">'); - var avatar_menu = $('#avatar-menu'); - var title = $('.action-menu-title', avatar_menu).text(); - var list = $('<ul class="widget-list widget-links">'); - $('<div class="sidebar-widget-header">').text(title).appendTo(sidebar_avatar_menu); - - $('.action-menu-item', avatar_menu).each(function() { - var src = $('img', this).attr('src'); - var link = $('a', this).clone(); - - link.find('img').remove(); - - $('<li>').append(link).css({ - backgroundSize: '16px', - backgroundImage: `url(${src})` - }).appendTo(list); - }); - - $('<div class="sidebar-widget-content">') - .append(list) - .appendTo(sidebar_avatar_menu); - - $('#sidebar').prepend(sidebar_avatar_menu); - }, - setResponsiveDisplay (state = true) { $('html').toggleClass('responsive-display', state); @@ -136,16 +15,21 @@ const Responsive = { }, engage () { - if (Responsive.media_query.matches) { - Responsive.responsify(); - Responsive.setResponsiveDisplay(); - } else { - Responsive.media_query.addListener(Responsive.responsify); - } + Responsive.setResponsiveDisplay(Responsive.isResponsive()); - Responsive.media_query.addListener(() => { - Responsive.setResponsiveDisplay(Responsive.media_query.matches); + Responsive.media_query.addEventListener('change', () => { + Responsive.setResponsiveDisplay(Responsive.isResponsive()); }); + }, + + isResponsive() { + return Responsive.media_query.matches; + }, + + isFullscreen() { + const cache = STUDIP.Cache.getInstance('responsive.'); + + return cache.get('fullscreen-mode') ?? false; } }; diff --git a/resources/assets/stylesheets/highcontrast.scss b/resources/assets/stylesheets/highcontrast.scss index 27a4d01cd43..849f0d44097 100644 --- a/resources/assets/stylesheets/highcontrast.scss +++ b/resources/assets/stylesheets/highcontrast.scss @@ -305,8 +305,8 @@ button.button[disabled]:hover { } /* Black borders */ -.sidebar .sidebar-widget, -.sidebar .sidebar-widget-placeholder, +.sidebar-widget, +.sidebar-widget-placeholder, section.contentbox, article.studip, form.default, @@ -1421,12 +1421,12 @@ button.skiplink { // Benachrichtigungen: Schwarzer Text auf weißem Hintergrund, beim Hovern invertieren. #notification-container a.enable-desktop-notifications, -#notification_container a.mark-all-as-read { +#notification-container a.mark-all-as-read { background-color: $white; color: $contrast-blue; } -#notification_container .list .item { +#notification-container .list .item { background-color: $white; color: $black; diff --git a/resources/assets/stylesheets/less/breakpoints.less b/resources/assets/stylesheets/less/breakpoints.less index 638df23a5b7..c06be0b4021 100644 --- a/resources/assets/stylesheets/less/breakpoints.less +++ b/resources/assets/stylesheets/less/breakpoints.less @@ -2,4 +2,6 @@ @major-breakpoint-tiny: 0; @major-breakpoint-small: 576px; @major-breakpoint-medium: 768px; -@major-breakpoint-large: 1200px; +@major-breakpoint-large: 1024px; +@major-breakpoint-xlarge: 1280px; +@major-breakpoint-xxlarge: 1600px; diff --git a/resources/assets/stylesheets/less/responsive.less b/resources/assets/stylesheets/less/responsive.less deleted file mode 100644 index b9a51522a58..00000000000 --- a/resources/assets/stylesheets/less/responsive.less +++ /dev/null @@ -1,493 +0,0 @@ -@import (reference) "breakpoints.less"; -@import (reference) "visibility.less"; - -@header-bar-container-height:40px; - -@responsive-menu-width: 270px; -@responsive-menu-shadow-width: 6px; -@responsive-menu-shadow-color: rgba(0, 0, 0, 0.5); - -// Responsive main navigation (hamburger navigation to the left) -#responsive-container { - display: none; - user-select: none; - - input[type="checkbox"] { - display: none; - } - label[for="responsive-toggle"]:first-child { - .icon('before', 'hamburger-icon', 'info_alt', 20); - cursor: pointer; - } - - ul, li { - list-style-type: none; - margin: 0; - padding: 0; - } - li { - border-top: 1px solid @brand-color-lighter; - } - a { - color: white; - &:hover { - color: white; - } - } - - .nav-label { - .hide-text(); - .icon('before', 'arr_1right', 'info_alt', 24); - &:before { - transition: transform 300ms; - vertical-align: text-bottom; - } - - position: absolute; - left: 5px; - top: 5px; - - border-right: 1px solid @brand-color-lighter; - padding-right: 2px; - } - - // Create second, invisible toggle that closes the menu when clicked/touched - // outside of the menu - label[for="responsive-toggle"]:last-child { - display: none; - - position: fixed; - top: @header-bar-container-height; - right: 0; - bottom: 0; - left: @responsive-menu-width; - height: 100vh; - } -} - -#responsive-navigation { - #gradient > .horizontal(@brand-color-darker, @brand-color-light); - background-clip: content-box; - transition: left 300ms; - - - left: (-@responsive-menu-width - @responsive-menu-shadow-width); - &.visible { - left: 0; - - + label[for="responsive-toggle"] { - display: initial; - } - } - - position: fixed; - top: @header-bar-container-height; // + 1px white border - bottom: 0; - height: calc(100vh - @header-bar-container-height); - - box-sizing: border-box; - max-width: @responsive-menu-width; - width: @responsive-menu-width; - margin-bottom: @header-bar-container-height; - - border-right: @responsive-menu-shadow-width solid @responsive-menu-shadow-color; - overflow: auto; - - > li { - &:first-child { - border-top: none; - } - - .icon { - .square(26px); - display: inline-block; - padding-right: 8px; - vertical-align: text-bottom; - width: 26px; - } - - > .navigation-item { - font-size: 1.3em; - margin: 10px; - } - } - - .navigation-item { - position: relative; - } - - .nav-title { - display: block; - padding-left: 34px; - a { - display: block; - padding: 5px; - } - } - - ul { - transition: max-height 400ms ease; - - max-height: 0px; - overflow: hidden; - > li { - background-color: @brand-color-lighter; - > .navigation-item { - padding: 10px; - } - } - - .icon { - display: none; - } - } - input:checked + label::before { - transform: rotate(90deg); - } - - input:checked + label + ul { - max-height: 600px; - > li { - background-color: mix(rgba(0, 0, 0, 0.2), @brand-color-lighter); - } - } -} - -// Responsive sidebar menu (small hamburger menu to the right) -#barBottomright #sidebar-menu { - .icon('before', 'hamburger-icon-small', 'info_alt', 20); - cursor: pointer; - display: none; - margin: 0 2px; - text-align: right; - vertical-align: top; -} - - -/* @deprecated use .hidden-medium-up */ -.media-breakpoint-medium-up({ - .responsive-visible { - display: none; - } -}); - -.responsive-display { - // Hide sidebar from view until .responsified - &:not(.responsified) { - #sidebar { - display: none; - } - } - - .media-breakpoint-small-down({ - #header, #navigation-level-1, #site-title, #barTopMenu, - #barBottomLeft .current_page, #barBottommiddle, #barBottomLeft, #barBottomArrow, - #tabs, .sidebar-image, #sidebar-navigation:not(.show), #barTopFont, #main-footer-info, .sidebar-widget-header, - .tabs_wrapper .colorblock { - display: none !important; - } - - #current-page-structure { - #navigation-level-2 { - display: flex; - flex-direction: row; - flex-wrap: nowrap; - - background-color: @dark-gray-color-10; - border-bottom: 1px solid @dark-gray-color-40; - - .colorblock, - #context-title, - .context_icon, - .tabs_wrapper { - transition: unset; - } - - #context-title, - .tabs_wrapper { - background: transparent; - border-width: 0; - flex: 1; - } - - #context-title { - flex: 1; - - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - - + .tabs_wrapper { - flex: 0; - align-self: flex-end; - } - } - } - - .tabs_wrapper { - justify-content: flex-end; - .helpbar-container { - top: 0px; - right: 6px; - } - } - } - .responsive-hidden { - display: none; - } - #notification_marker { - display: inline-block; - margin-top: 0; - vertical-align: initial; - - width: 22px; - padding-left: 5px; - padding-right: 5px; - height: 20px; - line-height: 20px; - } - - #avatar-menu-container { - position: relative; - bottom: 0px; - right: 0px; - line-height: 20px !important; - - #avatar-menu { - display: none; - } - - &::after { - display: none !important; - } - } - - #top-bar { - box-sizing: border-box; - height: @header-bar-container-height; - position: fixed; - top: 0; - margin-left: 0px; - margin-right: 0px; - width: 100%; - } - - #header-links, #header-links ul { - box-sizing: border-box; - flex: 1; - } - - #barBottomright { - flex: 1 !important; - #sidebar-menu { - display: inline-block; - } - .list { - &::before, &::after { - display: none; - } - @width: 300px; - @arrow-height: 10px; - - margin-top: 2px; - width: @width; - max-width: @width; - - &.below { - left: (-@width + 90); - &:before { - left: (@width - 90); - } - } - - } - - > ul > li { - flex: 1 0 auto; - - &:first-child { - flex: 1 1 100%; - } - } - } - - #notification_container { - position: inherit !important; - /*top: 8px;*/ - width: 32px; - height: 20px; - } - - #responsive-container { - display: block; - } - - #current-page-structure { - margin-left: 0; - margin-right: 0; - } - - #current-page-structure, #top-bar, #navigation-level-1, #layout_content { - min-width: inherit !important; - } - - #layout_content { - margin: 0px 4px; - } - - .visible-sidebar { - right: 0 !important; - transition: right 300ms; - } - #sidebar.visible-sidebar #sidebar-shadow-toggle { - display: initial; - } - - #sidebar { - #gradient > .horizontal(@brand-color-light, @brand-color-darker); - background-clip: content-box; - transition: right 300ms; - - position: fixed; - top: @header-bar-container-height; - right: (-@responsive-menu-width - @responsive-menu-shadow-width); - left: auto; - bottom: 0; - - margin-right: 0px; - - width: @responsive-menu-width; - - overflow: hidden; - overflow-y: auto; - z-index: 10000; - - border-left: @responsive-menu-shadow-width solid @responsive-menu-shadow-color; - - box-sizing: border-box; - - &:before { - border: 0 !important; - } - - background: inherit; - border: 0; - - .widget-links li.active { - &:before, &:after { - display: none; - } - margin-right: 0; - } - - // Create second, invisible toggle that closes the menu when - // clicked/touched outside of the menu - #sidebar-shadow-toggle { - position: fixed; - top: @header-bar-container-height; - right: @responsive-menu-width; - bottom: 0; - left: 0; - height: 100vh; - - display: none; - } - } - #index, - #login, - #request_new_password, - #web_migrate { - div.index_container { - height: calc(100% - 74px); - position: static; - top: 0; - - div.messagebox, - div.index_main { - margin: 1em auto; - } - } - - #background-desktop, - #background-mobile { - position: fixed; - } - } - - #main-footer { - display: block; - min-width: 0; - width: 100vw; - } - }); - - .media-breakpoint-tiny-down({ - #index, - #login, - #request_new_password, - #web_migrate { - div.index_container { - div.messagebox, - div.index_main { - margin: 0 auto; - } - } - } - }); -} - -// Hide duplicated avatar menu in sidebar as default -.sidebar-avatar-menu { - display: none; - margin-top: 0 !important; -} - -.responsive-display { - .sidebar-avatar-menu { - display: block; - } - - #quicksearch_item { - padding: 0; - } - #search_sem_quick_search_frame { - display: flex; - flex-direction: row; - justify-content: flex-end; - - .quicksearchbox { - transition: all 300ms; - opacity: 0; - max-width: 0; - } - - &.open { - .quicksearchbox { - opacity: 1; - max-width: 1000px; - width: 100% !important; - } - } - } - - #barBottomright { - ul { - li:first-child { - flex: 1 0 auto; - } - li#quicksearch_item { - flex: 1 1 100%; - } - } - } - - table.default tfoot .button { - margin-top: 0.5em; - margin-bottom: 0.5em; - } - - .ui-dialog.ui-widget.ui-widget-content.studip-confirmation { - min-width: 20vw; - max-width: 100vw; - } -} diff --git a/resources/assets/stylesheets/less/visibility.less b/resources/assets/stylesheets/less/visibility.less index f1d713bcdc3..50dafee85c5 100644 --- a/resources/assets/stylesheets/less/visibility.less +++ b/resources/assets/stylesheets/less/visibility.less @@ -1,30 +1,46 @@ -.media-breakpoint-large-down(@rules) { +.media-breakpoint-xxlarge-down(@rules) { @rules(); } +.media-breakpoint-xlarge-down(@rules) { + @media (max-width: (@major-breakpoint-xxlarge - 1px)) { @rules(); } +} + +.media-breakpoint-large-down(@rules) { + @media (max-width: (@major-breakpoint-xlarge - 1px)) { @rules(); } +} + .media-breakpoint-medium-down(@rules) { - @media (max-width: (@major-breakpoint-large - 1px)) { @rules(); } + @media (max-width: (@major-breakpoint-large - 1px)) { @rules(); } } .media-breakpoint-small-down(@rules) { - @media (max-width: (@major-breakpoint-medium - 1px)) { @rules(); } + @media (max-width: (@major-breakpoint-medium - 1px)) { @rules(); } } .media-breakpoint-tiny-down(@rules) { - @media (max-width: (@major-breakpoint-small - 1px)) { @rules(); } + @media (max-width: (@major-breakpoint-small - 1px)) { @rules(); } } +.media-breakpoint-xxlarge-up(@rules) { + @media (min-width: (@major-breakpoint-xxlarge)) { @rules(); } +} + +.media-breakpoint-xlarge-up(@rules) { + @media (min-width: (@major-breakpoint-xlarge)) { @rules(); } +} + .media-breakpoint-large-up(@rules) { - @media (min-width: (@major-breakpoint-large)) { @rules(); } + @media (min-width: (@major-breakpoint-large)) { @rules(); } } .media-breakpoint-medium-up(@rules) { - @media (min-width: (@major-breakpoint-medium)) { @rules(); } + @media (min-width: (@major-breakpoint-medium)) { @rules(); } } .media-breakpoint-small-up(@rules) { - @media (min-width: (@major-breakpoint-small)) { @rules(); } + @media (min-width: (@major-breakpoint-small)) { @rules(); } } .media-breakpoint-tiny-up(@rules) { @@ -32,6 +48,20 @@ } +.hidden-xxlarge-down { + .media-breakpoint-xxlarge-down({ display: none !important; }) +} +.hidden-xxlarge-up { + .media-breakpoint-xxlarge-up({ display: none !important; }); +} + +.hidden-xlarge-down { + .media-breakpoint-xlarge-down({ display: none !important; }) +} +.hidden-xxlarge-up { + .media-breakpoint-large-up({ display: none !important; }); +} + .hidden-large-down { .media-breakpoint-large-down({ display: none !important; }) } diff --git a/resources/assets/stylesheets/scss/breakpoints.scss b/resources/assets/stylesheets/scss/breakpoints.scss index 18681fc3077..07bf9be5504 100644 --- a/resources/assets/stylesheets/scss/breakpoints.scss +++ b/resources/assets/stylesheets/scss/breakpoints.scss @@ -2,4 +2,9 @@ $major-breakpoint-tiny: 0; $major-breakpoint-small: 576px; $major-breakpoint-medium: 768px; -$major-breakpoint-large: 1200px; +$major-breakpoint-large: 1024px; +$major-breakpoint-xlarge: 1280px; +$major-breakpoint-xxlarge: 1600px; + +//** Breakpoint for sidebar display +$minor-breakpoint-sidebar-fullscreen: 450px; diff --git a/resources/assets/stylesheets/scss/buttons.scss b/resources/assets/stylesheets/scss/buttons.scss index 3ffb84ca067..88b208941a8 100644 --- a/resources/assets/stylesheets/scss/buttons.scss +++ b/resources/assets/stylesheets/scss/buttons.scss @@ -122,3 +122,8 @@ button.button { margin-right: 0; } } + +button.styleless { + background-color: unset; + border: 0; +} diff --git a/resources/assets/stylesheets/scss/contentbar.scss b/resources/assets/stylesheets/scss/contentbar.scss index ba8abc5c7f9..df2026f8c70 100644 --- a/resources/assets/stylesheets/scss/contentbar.scss +++ b/resources/assets/stylesheets/scss/contentbar.scss @@ -1,109 +1,151 @@ .contentbar { background-color: $dark-gray-color-5; - border: 1px solid $dark-gray-color-30; - gap: 10px; - margin-bottom: 15px; - min-height: 58px; - padding: 0px 9px 0px 9px; + border: solid thin $dark-gray-color-30; display: flex; + flex-wrap: wrap; + height: auto; justify-content: space-between; - flex-wrap: nowrap; + margin-bottom: 15px; + min-height: 30px; + padding: 1em; - .contentbar_title { - align-items: center; + .contentbar-wrapper-left { display: flex; - font-size: 1.3em; - max-width: 60%; - padding-left: 0.5em; - - ul.breadcrumb { - list-style: none; - padding-left: 10px; - width: 100%; - display: inline-block; - - div { - float: left; - max-width: 100%; - } + flex: auto; + /* Taken from courseware.scss line 323 */ + max-width: calc(100% - 106px); - li { - display: inline; - } + .contentbar-nav { + display: flex; + } - li+li:before { - padding: 5px; - color: $black; - content: "/"; + .contentbar-breadcrumb { + display: flex; + font-size: 1.25em; + line-height: 1.5em; + margin-right: 1em; + min-width: 0; + + .contentbar-icon { + flex: 0; + height: 24px; + margin-top: 2px; + width: 24px; } - } - } + ul { + display: flex; + list-style: none; + margin-left: 15px; + padding-left: 0; + + li+li:before { + padding: 0 0.25em; + content: '/'; + background-repeat: no-repeat; + background-position: center; + } - .textblock { - display: inline-block; - margin-left: 10px; - margin-right: 10px; + .contentbar-breadcrumb-item { + display: inline; + flex-shrink: 100000; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + + a { + color: $base-color; + text-decoration: none; + &:hover { + color: $active-color; + } + } + + &.contentbar-breadcrumb-item-current { + flex-shrink: 1; + } + } + } + } } - .contentbar_info { - align-items: center; - text-align: right; + .contentbar-wrapper-right { display: flex; - flex-wrap: nowrap; - gap: 5px; - margin: 5px; + justify-content: flex-end; + position: relative; - .contentbar-icons { - display: flex; + .contentbar-button-wrapper { + height: 24px; + margin: 0 7px; - label { - margin-left: 0.5em; - margin-right: 0.5em; + @-moz-document url-prefix() { + &.contentbar-action-menu-wrapper { + margin-top: 2px; + } } - .consuming_mode_trigger { - margin-left: 0.5em; - margin-right: 0.5em; - margin-top: -1px; + .contentbar-button, .cw-ribbon-button { + background-color: transparent; + background-position: center; + background-repeat: no-repeat; + background-size: 24px; + border: none; + cursor: pointer; + display: inline-block; + height: 24px; + width: 24px; - @media (max-width: 767px) { - display: none !important; + &.contentbar-button-menu, + &.cw-ribbon-button-menu { + @include background-icon(table-of-contents, clickable, 24); + } + + &.contentbar-button-zoom::before { + left: -5px; + position: relative; + top: -2px; } @-moz-document url-prefix() { - margin-top: -4px; + &.contentbar-button-zoom::before { + top: -3px; + } } - } - .consuming_mode_trigger:not(body.consuming_mode .consuming_mode_trigger) { - display: inline-block; - width: 24px; - height: 24px; - @include icon(before, fullscreen-on, clickable, 24px, 0px); } - nav { - margin-left: 2px; - margin-top: 2px; - } } } + } -body.consuming_mode { - #main-header, #main-footer { - display: none; +body:not(.consuming_mode) { + .consuming_mode_trigger { + @include icon(before, fullscreen-on, clickable, 24px, 0); + margin-top: -2px; + margin-left: 0 } +} - #sidebar { - margin-left: -280px; +body.consuming_mode { + #barBottomContainer, + #flex-header, + .secondary-navigation, + #barTopStudip, + #page_title_container { + max-height: 0px; opacity: 0; + overflow: hidden; + } + + #layout_wrapper { + padding-top: 0px; } - #content { - grid-column: 1/3; - grid-row: 1/3; + #layout-sidebar { + margin-left: -280px; + opacity: 0; } .contentbar { @@ -114,6 +156,8 @@ body.consuming_mode { } .consuming_mode_trigger { - @include icon(before, fullscreen-off, clickable, 25px, 5px); + @include icon(before, focusmode-off, clickable, 24px, 0); + margin-top: -2px; + margin-left: 0 } } diff --git a/resources/assets/stylesheets/scss/courseware.scss b/resources/assets/stylesheets/scss/courseware.scss index 2c6649f23ca..79476bae70e 100644 --- a/resources/assets/stylesheets/scss/courseware.scss +++ b/resources/assets/stylesheets/scss/courseware.scss @@ -404,7 +404,7 @@ $consum_ribbon_width: calc(100% - 58px); } &.cw-ribbon-button-zoom-out { - @include background-icon(fullscreen-off, clickable, 24); + @include background-icon(focusmode-off, clickable, 24); } &.cw-ribbon-button-prev { diff --git a/resources/assets/stylesheets/scss/header.scss b/resources/assets/stylesheets/scss/header.scss index 0c36b8989a3..1ddd3bdec56 100644 --- a/resources/assets/stylesheets/scss/header.scss +++ b/resources/assets/stylesheets/scss/header.scss @@ -1,5 +1,7 @@ +@import "./layouts.scss"; + /* --- header.css ----------------------------------------------------------- */ -body > header { +#main-header { box-sizing: border-box; padding-top: $bar-bottom-container-height; } @@ -23,13 +25,17 @@ body > header { z-index: 10000; } + #responsive-menu, #site-title { flex: 0 0 auto; - padding: 0 15px; z-index: 2; } +#site-title { + padding: 0 $page-margin; +} + // Fix header covering relevant other areas // $see https://gitlab.studip.de/studip/studip/-/issues/1019 html { @@ -188,22 +194,3 @@ html { height: $header-height; z-index: 3; } - -// Slide menu in header navigation -#responsive-menu { - box-sizing: border-box; - overflow-x: hidden; - padding: 0; - text-indent: -150px; - - @media not prefers-reduced-motion { - transition: all 500ms; - } -} -body.fixed { - #responsive-menu { - overflow-x: visible; - padding: 0 15px; - text-indent: 0; - } -} diff --git a/resources/assets/stylesheets/scss/helpbar.scss b/resources/assets/stylesheets/scss/helpbar.scss index 14370d6e051..da0f0ca2fa6 100644 --- a/resources/assets/stylesheets/scss/helpbar.scss +++ b/resources/assets/stylesheets/scss/helpbar.scss @@ -15,7 +15,7 @@ $border-width: 4px; position: relative; top: 1px; min-width: 32px; - right: 12px; + right: 17px; float: right; @@ -41,6 +41,7 @@ $border-width: 4px; > .helpbar-toggler { float: right; + margin-top: 2px; } } diff --git a/resources/assets/stylesheets/scss/layouts.scss b/resources/assets/stylesheets/scss/layouts.scss index 2f0a94f1943..f38518b5373 100644 --- a/resources/assets/stylesheets/scss/layouts.scss +++ b/resources/assets/stylesheets/scss/layouts.scss @@ -1,5 +1,7 @@ // TODO: SCSSify +@import "./variables.scss"; + $page-margin: 15px; $content-width: 400px; @@ -253,7 +255,7 @@ body { flex-grow: 1; - min-width: $page-width; + min-width: $site-width; .tabs_wrapper { display: flex; diff --git a/resources/assets/stylesheets/scss/navigation.scss b/resources/assets/stylesheets/scss/navigation.scss index 50e47a21604..def2e913598 100644 --- a/resources/assets/stylesheets/scss/navigation.scss +++ b/resources/assets/stylesheets/scss/navigation.scss @@ -214,49 +214,11 @@ body:not(.fixed) #navigation-level-1-items { } } -// Toggle mechanism for touch/hover -#barTopMenu-toggle { - display: none; -} -label[for="barTopMenu-toggle"] { - @include background-icon(hamburger, info_alt, 16); - background-position: 0 center; - background-repeat: no-repeat; - - color: $white; - line-height: $bar-bottom-container-height; - overflow: hidden; - padding-left: (5px + 16px + 5px); // padding + icon + next padding - white-space: nowrap; - - height: 0; - max-height: 0; - opacity: 0; - @media not prefers-reduced-motion { - transition: all 300ms; - } - - // 1/4 of the screen's width, creates a bigger hover area - width: 25vw; - -} -html.no-touch { - #barTopMenu-toggle, - label[for="barTopMenu-toggle"] { - pointer-events: none; - } -} - body.fixed { #navigation-level-1 { height: $header-height; } - label[for="barTopMenu-toggle"] { - opacity: 1; - max-height: $bar-bottom-container-height; - height: auto; - } #navigation-level-1-items { background-color: $base-color; @@ -318,8 +280,7 @@ body.fixed { } } - #responsive-menu:hover #navigation-level-1-items, - #barTopMenu-toggle:checked ~ #navigation-level-1-items { + #responsive-menu:hover #navigation-level-1-items { display: block; } } diff --git a/resources/assets/stylesheets/scss/responsive.scss b/resources/assets/stylesheets/scss/responsive.scss new file mode 100644 index 00000000000..95b97f3a0d6 --- /dev/null +++ b/resources/assets/stylesheets/scss/responsive.scss @@ -0,0 +1,796 @@ +@use "../mixins/colors.scss"; +@import "breakpoints"; +@import "buttons"; +@import "sidebar"; + +$header-bar-container-height: 40px; + +$responsive-menu-width: 270px; + +$sidebarIn: -15px; +$sidebarOut: -330px; + +#responsive-toggle-desktop, +#responsive-toggle-fullscreen { + display: none; +} + +#responsive-toggle-fullscreen { + position: relative; + top: 4px; + + img { + cursor: pointer; + } +} + +#non-responsive-toggle-fullscreen { + margin-left: auto; + margin-right: 15px; + position: relative; + top: 2px; + + img { + cursor: pointer; + } +} + +#responsive-menu { + font-size: $font-size-base; + padding: 0 5px; + z-index: 1002; + + .responsive-navigation-header { + display: flex; + margin-top: 2px; + padding: 2px 0 5px; + + .menu-closed { + cursor: pointer; + transform: rotate(0deg); + + @media not prefers-reduced-motion { + transition: 0.5s ease-in-out; + } + } + + .menu-open { + cursor: pointer; + transform: rotate(90deg); + + @media not prefers-reduced-motion { + transition: 0.5s ease-in-out; + } + } + } + +} + +#responsive-navigation-button { + cursor: pointer; + left: 10px; + position: relative; +} + +#responsive-navigation-items { + background-color: $base-color; + left: 0; + max-width: $responsive-menu-width; + overflow-y: auto; + padding-bottom: 5px; + position: fixed; + top: 40px; + + /* Safari only */ + @media not all and (min-resolution: .001dpcm) { + @supports (-webkit-appearance: none) { + top: 43px; + } + } + + width: $responsive-menu-width; + + @media not prefers-reduced-motion { + transition: all 0.5s ease-in-out; + } + + header { + background-image: url("#{$image-path}/sidebar/noicon-sidebar.png"); + background-size: cover; + display: flex; + flex-wrap: wrap; + max-height: 250px; + overflow-y: auto; + padding: 10px; + + .profile-info { + flex: auto; + font-size: $font-size-small; + padding: 20px; + position: relative; + + .profile-pic { + width: 100%; + + img, + svg { + cursor: pointer; + height: 50px; + width: 50px; + } + } + } + + > div { + display: flex; + width: 100%; + + .avatar-navigation { + flex: auto; + padding-left: 10px; + + .navigation-item { + background-color: transparent; + + &:hover { + background-color: $base-color-80; + } + } + } + } + + .open-avatarmenu, + .close-avatarmenu { + button { + cursor: pointer; + position: relative; + top: calc(50% - 12px); + } + } + } + + .main-navigation { + margin: 0 5px; + padding: 0 5px; + } + + .navigation-item { + background-color: $base-color; + display: flex; + flex-wrap: wrap; + list-style-type: none; + margin: 0px; + + &:not(:last-child) { + border-bottom: 1px solid $white; + } + + &.navigation-up, + &.navigation-current { + border-bottom: 2px solid $white; + + .navigation-icon { + img { + padding-top: 0; + } + } + + .navigation-title { + padding: 10px 10px 10px 0; + } + + &:hover { + background-color: $base-color-80; + } + + } + + &.navigation-current { + background-color: $base-color-60; + } + + a { + color: $white; + cursor: pointer; + } + + button { + color: $white; + cursor: pointer; + display: flex; + flex: 0; + text-align: center; + width: 100%; + + img, + svg { + padding-top: 12px; + } + + &.navigation-in { + border-left: 1px solid $base-color-60; + } + } + + &:not(.navigation-current):not(.navigation-up) { + button:hover { + background-color: $base-color-80; + } + } + + } + + .navigation-title { + color: $white; + flex: 1; + + > a { + display: inline-block; + padding: 10px 10px 10px 5px; + text-align: left; + width: calc(100% - 15px); + + .navigation-icon { + flex: 0; + width: 35px; + } + + .navigation-text { + flex: 1; + padding-top: 2px; + } + + } + + img, + svg { + vertical-align: text-bottom; + margin-right: 10px; + } + + } + + a { + flex: 0; + + &:hover { + background-color: $base-color-80; + } + + } + + img, svg { + vertical-align: text-bottom; + } +} + +.responsive-display, +.fullscreen-mode body:not(.consuming_mode) { + + body { + display: inherit; + } + + #responsive-menu { + flex: 0; + } + + #site-title, + #quicksearch_item, + #avatar-menu-container, + #current-page-structure { + display: none; + } + + #header-links { + > ul { + margin-right: 10px; + + li { + padding: 0; + + &.helpbar-container { + float: unset; + margin-top: 5px; + right: 5px; + } + } + } + + #notification-container, + .header_avatar_container, + #sidebar-menu { + display: none; + } + } + + #navigation-level-1 { + display: none; + } + + #sidebar { + background-color: $white; + position: absolute; + transform: translateX($sidebarOut); + z-index: 100; + + &.responsive-hide { + @media not prefers-reduced-motion { + animation: slide-out 0.5s forwards; + } + } + + &.responsive-show { + @media not prefers-reduced-motion { + animation: slide-in 0.5s forwards; + } + + left: 15px; + position: relative; + } + + .sidebar-image { + display: none; + } + + > .sidebar-widget { + margin-top: 0; + } + + > .sidebar-widget ~ .sidebar-widget { + margin-top: 15px; + } + + @keyframes slide-in { + 100% { + transform: translateX($sidebarIn); + } + } + + + @keyframes slide-out { + 0% { + transform: translateX($sidebarIn); + } + 100% { + transform: translateX($sidebarOut); + } + } + } + + #sidebar-navigation { + display: none !important; + } + + #current-page-structure { + #navigation-level-2 { + display: none !important; + } + } + + #responsive-contentbar { + margin-bottom: 15px; + padding-bottom: 0.5em; + padding-left: 5px; + + .contentbar-nav, + .cw-ribbon-nav { + width: auto; + margin-left: 5px; + + .contentbar-button { + + &.contentbar-button-sidebar { + cursor: pointer; + margin-right: 10px; + + img { + transform: rotate(0deg); + } + + &.contentbar-button-sidebar-open { + img { + transform: rotate(180deg); + } + } + } + } + + } + + .contentbar-wrapper-left { + flex: 1; + max-width: unset; + + & > .contentbar-icon { + margin-right: 15px; + } + + .contentbar-breadcrumb { + font-size: $font-size-large; + width: calc(100% - 75px); + + > img { + margin-left: 15px; + width: 24px; + } + } + } + + > .contentbar-wrapper-right { + position: relative; + left: 5px; + + .contentbar-button, + nav { + position: relative; + } + } + + &.cw-ribbon { + margin-top: 4px; + + .cw-ribbon-tools { + right: 0; + top: 96px; + } + } + } + + #toc { + position: absolute; + right: -8px; + top: 82px; + } + + #toc_header { + height: 47px; + } + + #main-footer { + display: none; + } +} + +.responsive-display:not(.fullscreen-mode) { + #responsive-menu { + width: calc(100% - 56px); + } + + #responsive-navigation-items { + max-width: unset; + width: 100%; + } + + #sidebar { + height: 100%; + position: fixed; + transform: translateX($sidebarOut); + -webkit-transform: translateX($sidebarOut); + top: 80px; + z-index: 100; + + &.responsive-show { + width: 100%; + + .sidebar-widget { + width: calc(100% - 30px); + } + } + } +} + +/* Settings especially for fullscreen mode */ +.fullscreen-mode:not(.responsive-display) { + body:not(.consuming_mode) { + display: flex; + flex-direction: row; + flex-wrap: wrap; + + #top-bar { + max-height: unset; + opacity: 1; + overflow: unset; + } + + #responsive-menu { + max-width: calc(100% - 100px); + min-width: calc(100% - 100px); + width: calc(100% - 100px); + } + + #main-header { + flex-basis: 100%; + } + + #responsive-toggle-fullscreen { + display: block; + margin-right: 5px; + } + + .contentbar:not(#responsive-contentbar) { + display: none; + } + + #sidebar { + flex: 0; + + &.responsive-hide { + top: 110px; + } + } + + #content-wrapper { + flex: 1; + height: 100%; + } + } +} + +.consuming_mode { + display: unset; + + #responsive-contentbar { + display: none; + } +} + +html:not(.responsive-display):not(.fullscreen-mode) { + #responsive-navigation { + display: none; + } + + body.fixed { + #responsive-navigation { + display: block; + } + + #responsive-navigation-items { + margin-top: -5px; + width: 100%; + } + } + +} + +/* content from old responsive.less */ +.responsive-display { + @include media-breakpoint-small-down() { + #navigation-level-1, + #site-title, + #navigation-level1-items, + .current_page, + #tabs, + #site-title, + #footer, + .tabs_wrapper .colorblock { + display: none !important; + } + + #layout_wrapper #current-page-structure { + #navigation-level-2 { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + + background-color: $dark-gray-color-10; + border-bottom: 1px solid $dark-gray-color-40; + + .colorblock, + #context-title, + .context_icon, + .tabs_wrapper { + transition: unset; + } + + #context-title, + .tabs_wrapper { + background: transparent; + border-width: 0; + flex: 1; + } + + #context-title { + flex: 1; + + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + + + .tabs_wrapper { + flex: 0; + align-self: flex-end; + } + } + } + } + #layout_wrapper #current-page-structure .tabs_wrapper { + justify-content: flex-end; + .helpbar-container { + top: 0px; + right: 6px; + } + } + .responsive-hidden { + display: none; + } + #notification_marker { + display: inline-block; + margin-top: 0; + vertical-align: initial; + + width: 22px; + padding-left: 5px; + padding-right: 5px; + height: 20px; + line-height: 20px; + } + + #avatar-menu-container { + position: relative; + bottom: 0px; + right: 0px; + line-height: 20px !important; + + #avatar-menu { + display: none; + } + + &::after { + display: none !important; + } + } + + #top-bar { + box-sizing: border-box; + height: $header-bar-container-height; + position: fixed; + top: 0; + margin-left: 0px; + margin-right: 0px; + width: 100%; + } + + #header-links, #header-links ul { + box-sizing: border-box; + flex: 1; + } + + #header-links { + flex: 1 !important; + .list { + &::before, + &::after { + display: none; + } + $width: 300px; + $arrow-height: 10px; + + margin-top: 2px; + width: $width; + max-width: $width; + + &.below { + left: (-$width + 90px); + &:before { + left: ($width - 90px); + } + } + + } + + > ul > li { + flex: 1 0 auto; + + &:first-child { + flex: 1 1 100%; + } + } + } + + #notification-container { + position: inherit !important; + /*top: 8px;*/ + width: 32px; + height: 20px; + } + + #responsive-container { + display: block; + } + + #current-page-structure { + margin-left: 0; + margin-right: 0; + } + + #current-page-structure, #top-bar, #navigation-level-1, #layout_content { + min-width: inherit !important; + } + + #layout_content { + margin: 0px 4px; + } + + #index, + #login, + #request_new_password, + #web_migrate { + div.index_container { + height: calc(100% - 74px); + position: static; + top: 0; + + div.messagebox, + div.index_main { + margin: 1em auto; + } + } + + #background-desktop, + #background-mobile { + position: fixed; + } + } + } + + @include media-breakpoint-tiny-down() { + #index, + #login, + #request_new_password, + #web_migrate { + div.index_container { + div.messagebox, + div.index_main { + margin: 0 auto; + } + } + } + } +} + +.responsive-display { + #quicksearch_item { + padding: 0; + } + #search_sem_quick_search_frame { + display: flex; + flex-direction: row; + justify-content: flex-end; + + .quicksearchbox { + transition: all 300ms; + opacity: 0; + max-width: 0; + } + + &.open { + .quicksearchbox { + opacity: 1; + max-width: 1000px; + width: 100% !important; + } + } + } + + #header-links { + ul { + li:first-child { + flex: 1 0 auto; + } + li#quicksearch_item { + flex: 1 1 100%; + } + } + } + + table.default tfoot .button { + margin-top: 0.5em; + margin-bottom: 0.5em; + } + + .ui-dialog.ui-widget.ui-widget-content.studip-confirmation { + min-width: 20vw; + max-width: 100vw; + } +} diff --git a/resources/assets/stylesheets/scss/table_of_contents.scss b/resources/assets/stylesheets/scss/table_of_contents.scss index 4298e3dbc8f..adf8df9d171 100644 --- a/resources/assets/stylesheets/scss/table_of_contents.scss +++ b/resources/assets/stylesheets/scss/table_of_contents.scss @@ -31,11 +31,9 @@ ul.numberedchapters { width: 0%; z-index: 100; position: absolute; - right: 2px; - top: -10px; + right: -22px; + top: -25px; background-color: $white; - min-height: 10%; - max-height: 100%; border: 1px solid #d0d7e3; margin-bottom: 10px; -webkit-box-shadow: 2px 2px #ccc; @@ -59,7 +57,7 @@ ul.numberedchapters { height: 58px; overflow: hidden; background-color: $white; - color: $black !important; + color: $black; margin-bottom: -0.5em; border-bottom: thin solid #d0d7e3; display: flex; @@ -79,8 +77,11 @@ ul.numberedchapters { margin-left: 10px; margin-bottom: unset; } -.toc_transform { - transition: all 0.8s ease!important; + +@media not prefers-reduced-motion { + .toc_transform { + transition: all 0.8s ease; + } } #main_content { @@ -201,7 +202,7 @@ section > .toc { } #toc { - max-width: 94%!important; + max-width: 94%; } ul.breadcrumb { @@ -211,17 +212,13 @@ section > .toc { width: 70%; } - #toc_header { - width: 90%; - } - .consuming_mode .toc_overview { - top: 51px!important; + top: 51px; } } .wiki { - border: unset!important; + border: unset; } .action-menu { diff --git a/resources/assets/stylesheets/scss/visibility.scss b/resources/assets/stylesheets/scss/visibility.scss index 02004d21dc4..df56df15602 100644 --- a/resources/assets/stylesheets/scss/visibility.scss +++ b/resources/assets/stylesheets/scss/visibility.scss @@ -1,6 +1,16 @@ -@mixin media-breakpoint-large-down() { +@mixin media-breakpoint-xxlarge-down() { @content; } +@mixin media-breakpoint-xlarge-down() { + @media (max-width: ($major-breakpoint-xxlarge - 1px)) { + @content; + } +} +@mixin media-breakpoint-large-down() { + @media (max-width: ($major-breakpoint-xlarge - 1px)) { + @content; + } +} @mixin media-breakpoint-medium-down() { @media (max-width: ($major-breakpoint-large - 1px)) { @content; @@ -17,6 +27,16 @@ } } +@mixin media-breakpoint-xxlarge-up() { + @media (min-width: ($major-breakpoint-xxlarge)) { + @content; + } +} +@mixin media-breakpoint-xlarge-up() { + @media (min-width: ($major-breakpoint-xlarge)) { + @content; + } +} @mixin media-breakpoint-large-up() { @media (min-width: ($major-breakpoint-large)) { @content; @@ -36,6 +56,33 @@ @content; } +@mixin sidebar-breakpoint-down() { + @media (max-width: ($minor-breakpoint-sidebar-fullscreen)) { + @content; + } +} + +@mixin hidden-xxlarge-down { + @include media-breakpoint-xxlarge-down() { + display: none !important; + } +} +@mixin hidden-xxlarge-up { + @include media-breakpoint-xxlarge-up() { + display: none !important; + } +} + +@mixin hidden-xlarge-down { + @include media-breakpoint-xlarge-down() { + display: none !important; + } +} +@mixin hidden-xlarge-up { + @include media-breakpoint-xlarge-up() { + display: none !important; + } +} @mixin hidden-large-down { @include media-breakpoint-large-down() { diff --git a/resources/assets/stylesheets/studip.less b/resources/assets/stylesheets/studip.less index ed40030871c..0b24c86ed22 100644 --- a/resources/assets/stylesheets/studip.less +++ b/resources/assets/stylesheets/studip.less @@ -8,7 +8,6 @@ @import "less/variables.less"; @import "less/breakpoints.less"; @import "less/visibility.less"; -@import "less/responsive.less"; @import "less/tables.less"; @import "less/buttons.less"; @@ -478,8 +477,6 @@ a.new-member { brightness(100% - hsvvalue(@base-color) + hsvvalue(#28497c)); } -#navigation-level-1-items li a img, -#navigation-level-1-items li a canvas, #sidebar .sidebar-image > img { .recolor; } diff --git a/resources/assets/stylesheets/studip.scss b/resources/assets/stylesheets/studip.scss index 1b0d2e0cb34..0cf922df9bc 100644 --- a/resources/assets/stylesheets/studip.scss +++ b/resources/assets/stylesheets/studip.scss @@ -77,6 +77,7 @@ @import "scss/quicksearch"; @import "scss/raumzeit"; @import "scss/report"; +@import "scss/responsive"; @import "scss/resources"; @import "scss/sidebar"; @import "scss/select"; diff --git a/resources/vue/components/BlubberThread.vue b/resources/vue/components/BlubberThread.vue index 3dbe8dd9818..dab56cfea65 100644 --- a/resources/vue/components/BlubberThread.vue +++ b/resources/vue/components/BlubberThread.vue @@ -3,7 +3,7 @@ :id="'blubberthread_' + threadData.thread_posting.thread_id" @dragover.prevent="dragover" @dragleave.prevent="dragleave" @drop.prevent="upload"> - <div class="responsive-visible context_info" v-if="threadData.notifications"> + <div class="hidden-medium-up context_info" v-if="threadData.notifications"> <a href="#" @click.prevent="toggleFollow()" class="followunfollow" diff --git a/resources/vue/components/courseware/CoursewareRibbon.vue b/resources/vue/components/courseware/CoursewareRibbon.vue index 8c200141b31..119448c7f06 100644 --- a/resources/vue/components/courseware/CoursewareRibbon.vue +++ b/resources/vue/components/courseware/CoursewareRibbon.vue @@ -1,5 +1,5 @@ <template> - <div :class="{ 'cw-ribbon-wrapper-consume': consumeMode }"> + <div :class="{ 'cw-ribbon-wrapper-consume': consumeMode }" :id="isContentBar ? 'contentbar' : null" > <div v-if="stickyRibbon" class="cw-ribbon-sticky-top"></div> <header class="cw-ribbon" :class="{ 'cw-ribbon-sticky': stickyRibbon, 'cw-ribbon-consume': consumeMode }"> <div class="cw-ribbon-wrapper-left"> @@ -67,6 +67,10 @@ export default { type: Boolean }, buttonsClass: String, + isContentBar: { + type: Boolean, + default: false + } }, data() { return { @@ -101,6 +105,7 @@ export default { }, methods: { toggleConsumeMode() { + STUDIP.Vue.emit('toggle-focus-mode', !this.consumeMode); if (!this.consumeMode) { this.$store.dispatch('coursewareConsumeMode', true); this.$store.dispatch('coursewareSelectedToolbarItem', 'contents'); @@ -125,6 +130,9 @@ export default { }, mounted() { window.addEventListener('scroll', this.handleScroll); + if (this.isContentBar) { + STUDIP.Vue.emit('courseware-contentbar-mounted', this); + } }, watch: { toolsActive(newState, oldState) { diff --git a/resources/vue/components/courseware/CoursewareStructuralElement.vue b/resources/vue/components/courseware/CoursewareStructuralElement.vue index 8c12b889762..d029e65e5d3 100644 --- a/resources/vue/components/courseware/CoursewareStructuralElement.vue +++ b/resources/vue/components/courseware/CoursewareStructuralElement.vue @@ -7,7 +7,7 @@ v-if="validContext" > <div class="cw-structural-element-content" v-if="structuralElement"> - <courseware-ribbon :canEdit="canEdit && canAddElements"> + <courseware-ribbon :canEdit="canEdit && canAddElements" :isContentBar="true"> <template #buttons> <router-link v-if="prevElement" :to="'/structural_element/' + prevElement.id"> <div class="cw-ribbon-button cw-ribbon-button-prev" :title="textRibbon.perv" /> diff --git a/resources/vue/components/responsive/NavigationItem.vue b/resources/vue/components/responsive/NavigationItem.vue new file mode 100644 index 00000000000..0bfd6d2bd23 --- /dev/null +++ b/resources/vue/components/responsive/NavigationItem.vue @@ -0,0 +1,83 @@ +<template> + <li v-if="item.visible" class="navigation-item" :class="{'navigation-item-active': active}"> + <template v-if="hasChildren()"> + <div class="navigation-title"> + <a :href="item.url" :title="navigateToText(item.title)" tabindex="0"> + <span class="navigation-icon"> + <studip-icon v-if="isCourse" shape="seminar" role="info_alt" size="24" alt=""></studip-icon> + <img v-if="item.icon" :src="iconUrl" width="24" alt=""> + </span> + <span class="navigation-text"> + {{ item.title }} + </span> + </a> + </div> + <button class="styleless navigation-in" tabindex="0" + :title="openNavigationText(item.title)" + @click="moveTo(item.path)" @keydown.prevent.enter="moveTo(item.path)" @keydown.prevent.space="moveTo(item.path)"> + <studip-icon shape="arr_1right" role="info_alt" size="20" alt=""></studip-icon> + </button> + </template> + <div v-else class="navigation-title"> + <a :href="item.url" tabindex="0" :title="navigateToText(item.title)"> + <studip-icon v-if="isCourse" shape="seminar" role="info_alt" size="24" alt=""></studip-icon> + <img v-if="item.icon" :src="iconUrl" width="24" alt=""> + {{ item.title }} + </a> + </div> + </li> +</template> + +<script> +import StudipIcon from '../StudipIcon.vue'; + +export default { + name: 'NavigationItem', + components: { StudipIcon }, + props: { + item: { + type: Object, + required: true + }, + active: { + type: Boolean, + default: false + }, + isCourse: { + type: Boolean, + default: false + } + }, + computed: { + iconUrl() { + if (this.item.icon && !this.item.icon.match(/^https?:\/\//)) { + return STUDIP.ASSETS_URL + this.item.icon; + } + return this.item.icon; + } + }, + methods: { + getUrl(url) { + return STUDIP.URLHelper.getURL(url); + }, + moveTo(path) { + STUDIP.Vue.emit('responsive-navigation-move-to', path); + }, + hasChildren() { + return this.item.children && Object.keys(this.item.children).length > 0; + }, + navigateToText(itemTitle) { + return this.$gettextInterpolate( + this.$gettext('Navigiere zu %{ title }'), + { title: itemTitle } + ); + }, + openNavigationText(itemTitle) { + return this.$gettextInterpolate( + this.$gettext('Öffne Navigation %{ title }'), + { title: itemTitle } + ); + } + } +} +</script> diff --git a/resources/vue/components/responsive/ResponsiveContentBar.vue b/resources/vue/components/responsive/ResponsiveContentBar.vue new file mode 100644 index 00000000000..9e499b3fbf1 --- /dev/null +++ b/resources/vue/components/responsive/ResponsiveContentBar.vue @@ -0,0 +1,206 @@ +<template> + <div v-if="realContentbarSource === ''"> + <MountingPortal mount-to="#responsive-contentbar-container" append> + <portal-target name="layout-page"></portal-target> + </MountingPortal> + <portal to="layout-page"> + <div id="responsive-contentbar" class="contentbar" ref="contentbar"> + <div v-if="hasSidebar" class="contentbar-nav" ref="leftNav"> + <button :class="sidebarIconClasses" @click.prevent="toggleSidebar" id="toggle-sidebar" + :title="$gettext('Sidebar öffnen')"> + <studip-icon shape="sidebar3" size="24" ref="sidebarIcon" + alt=""></studip-icon> + </button> + </div> + <div class="contentbar-wrapper-left"> + <studip-icon :shape="icon" size="24" role="info" class="text-bottom contentbar-icon"></studip-icon> + <nav class="contentbar-breadcrumb" ref="breadcrumbs">{{ title }}</nav> + </div> + <div class="contentbar-wrapper-right" ref="wrapperRight"></div> + </div> + </portal> + </div> +</template> + +<script> +import StudipIcon from '../StudipIcon.vue'; + +export default { + name: 'ResponsiveContentBar', + components: { StudipIcon }, + props: { + icon: { + type: String, + default: 'seminar' + }, + title: { + type: String, + default: '' + }, + hasSidebar: { + type: Boolean, + default: true + } + }, + data() { + return { + realContentbar: null, + realContentbarSource: null, + realContentbarIconContainer: null, + realContentbarType: null, + sidebarOpen: false + } + }, + computed: { + sidebarIconClasses() { + let classes = ['styleless', 'contentbar-button', 'contentbar-button-sidebar']; + if (this.sidebarOpen) { + classes.push('contentbar-button-sidebar-open'); + } + return classes; + } + }, + methods: { + toggleSidebar() { + + const sidebar = document.getElementById('sidebar'); + const content = document.getElementById('content-wrapper'); + const pageTitle = document.getElementById('page-title-container'); + const html = document.querySelector('html'); + if (this.sidebarOpen) { + sidebar.classList.add('responsive-hide'); + sidebar.classList.remove('responsive-show'); + + if (html.classList.contains('responsive-display') && !html.classList.contains('fullscreen-mode')) { + content.style.display = null; + pageTitle.style.display = null; + } + + this.sidebarOpen = false; + } else { + sidebar.classList.add('responsive-show'); + sidebar.classList.remove('responsive-hide'); + if (html.classList.contains('responsive-display') && !html.classList.contains('fullscreen-mode')) { + content.style.display = 'none'; + pageTitle.style.display = 'none'; + } + this.sidebarOpen = true; + } + + // Adjust toggle sidebar button title + document.getElementById('toggle-sidebar').title = this.sidebarOpen + ? this.$gettext('Sidebar schließen') + : this.$gettext('Sidebar öffnen'); + }, + adjustExistingContentbar(responsiveMode) { + if (this.realContentbar) { + if (responsiveMode) { + this.realContentbar.id = 'responsive-contentbar'; + this.realContentbar.classList.add('contentbar'); + if (!this.realContentbar.querySelector('#toggle-sidebar-button')) { + this.realContentbar.querySelector(this.realContentbarIconContainer) + .prepend(this.createSidebarIcon()); + } + if (this.realContentbarType === 'courseware') { + this.realContentbar.querySelector('.cw-ribbon-wrapper-left') + .classList.add('contentbar-wrapper-left'); + this.realContentbar.querySelector('.cw-ribbon-wrapper-right') + .classList.add('contentbar-wrapper-right'); + } + + document.getElementById('responsive-contentbar-container').prepend(this.realContentbar); + } else { + this.realContentbar.id = 'contentbar'; + document.getElementById('toggle-sidebar-button').remove(); + + if (this.realContentbarType === 'courseware') { + this.realContentbar.classList.remove('contentbar'); + this.realContentbar.querySelector('.cw-ribbon-wrapper-left') + .classList.remove('contentbar-wrapper-left'); + this.realContentbar.querySelector('.cw-ribbon-wrapper-right') + .classList.remove('contentbar-wrapper-right'); + } + + document.querySelector(this.realContentbarSource).prepend(this.realContentbar); + } + } + }, + createSidebarIcon() { + const button = document.createElement('button'); + + this.sidebarIconClasses.map(className => { + button.classList.add(className); + }); + button.id = 'toggle-sidebar-button'; + button.title = this.$gettext('Sidebar einblenden'); + button.addEventListener('click', (event) => { + button.classList.toggle('contentbar-button-sidebar-open'); + event.preventDefault(); + this.toggleSidebar(); + }) + const sidebarIcon = document.createElement('img'); + sidebarIcon.src = STUDIP.ASSETS_URL + '/images/icons/blue/sidebar3.svg'; + sidebarIcon.height = 24; + sidebarIcon.width = 24; + button.appendChild(sidebarIcon); + + return button; + } + }, + mounted() { + // There's already a PHP contentbar on this page, use it. + this.$nextTick(() => { + const realContentbar = document.querySelector('.contentbar:not(#responsive-contentbar)'); + if (realContentbar) { + this.realContentbar = realContentbar; + this.realContentbarSource = '#layout_content'; + this.realContentbarIconContainer = '.contentbar-nav'; + this.realContentbarType = 'wiki'; + this.adjustExistingContentbar(true); + } else { + this.realContentbarSource = ''; + + const cwContentbar = document.querySelector('#contentbar header'); + if (cwContentbar) { + this.realContentbar = cwContentbar; + this.realContentbarSource = '.cw-structural-element-content > div'; + this.realContentbarIconContainer = '.cw-ribbon-nav'; + this.realContentbarType = 'courseware'; + this.adjustExistingContentbar(true); + } + } + + // Add click listener to sidebar so that it can be hidden on clicking an item + document.querySelectorAll('.sidebar-widget a').forEach(item => { + item.addEventListener('click', () => this.toggleSidebar()); + }); + }) + + // Use courseware contentbar instead of this Vue component. + STUDIP.Vue.on('courseware-contentbar-mounted', element => { + this.realContentbar = element.$el.querySelector('header'); + this.realContentbarSource = '.cw-structural-element-content > div'; + this.realContentbarIconContainer = '.cw-ribbon-nav'; + this.realContentbarType = 'courseware'; + this.adjustExistingContentbar(true); + + document.querySelectorAll('.sidebar-widget button span').forEach(item => { + item.addEventListener('click', () => this.toggleSidebar()); + }); + }) + + STUDIP.Vue.on('toggle-focus-mode', (state) => { + const html = document.querySelector('html'); + if (html.classList.contains('responsive-display') || html.classList.contains('fullscreen-mode')) { + this.adjustExistingContentbar(!state); + } + }) + + }, + beforeDestroy() { + if (this.realContentbar) { + this.adjustExistingContentbar(false); + } + } +} +</script> diff --git a/resources/vue/components/responsive/ResponsiveNavigation.vue b/resources/vue/components/responsive/ResponsiveNavigation.vue new file mode 100644 index 00000000000..d71152027c7 --- /dev/null +++ b/resources/vue/components/responsive/ResponsiveNavigation.vue @@ -0,0 +1,525 @@ +<template> + <div role="navigation"> + <div class="responsive-navigation-header"> + <button v-if="menuNeeded" + id="responsive-navigation-button" class="styleless" + :title="showMenu ? $gettext('Navigation schließen') : $gettext('Navigation öffnen')" + aria-owns="responsive-navigation-items" + @click.prevent="toggleMenu" + @keydown.prevent.space="toggleMenu" + @keydown.prevent.enter="toggleMenu"> + <studip-icon shape="hamburger" role="info_alt" + :alt="showMenu ? $gettext('Navigation schließen') : $gettext('Navigation öffnen')" + :size="iconSize" :class="showMenu ? 'menu-open' : 'menu-closed'"> + </studip-icon> + </button> + <toggle-fullscreen v-if="!isResponsive && !isFocusMode && me.username != 'nobody'" + :is-fullscreen="isFullscreen"></toggle-fullscreen> + </div> + <transition name="appear" appear> + <nav v-if="showMenu" id="responsive-navigation-items" class="responsive" ref="navigation" + :aria-expanded="showMenu"> + <header v-if="me.username !== 'nobody'"> + <template v-if="!avatarMenuOpen"> + <section class="profile-info"> + <div class="profile-pic"> + <img :src="me.avatar" + @click.prevent="toggleAvatarMenu" + :title="$gettext('Profilnavigation öffnen')"> + </div> + <div class="profile-data"> + <div>{{ me.fullname }}</div> + <div>{{ me.email }} ({{ me.perm }})</div> + </div> + </section> + <section class="open-avatarmenu"> + <button class="styleless" tabindex="0" ref="openAvatarmenu" + @keydown.prevent.enter="toggleAvatarMenu" + @keydown.prevent.space="toggleAvatarMenu" + @click.prevent="toggleAvatarMenu" + :title="$gettext('Profilnavigation öffnen')"> + <studip-icon shape="arr_1right" role="info_alt" :size="iconSize" alt=""></studip-icon> + </button> + </section> + </template> + <template v-else> + <focus-trap active="true" :return-focus-on-deactivate="true" + :click-outside-deactivates="true"> + <div> + <div class="close-avatarmenu"> + <button class="styleless" ref="closeAvatarmenu" tabindex="0" + @keydown.prevent.enter="toggleAvatarMenu" + @keydown.prevent.space="toggleAvatarMenu" + @click="toggleAvatarMenu"> + <studip-icon shape="arr_1left" role="info_alt" :size="iconSize"></studip-icon> + </button> + </div> + <ul class="avatar-navigation"> + <navigation-item v-for="(item, index) in avatarNavigation.children" :key="index" + :item="item"></navigation-item> + </ul> + </div> + </focus-trap> + </template> + </header> + <ul class="main-navigation"> + <li v-if="currentParent != null" class="navigation-item navigation-up"> + <div class="navigation-title" :title="$gettext('Zum Start')" @click="moveTo('/')"> + <button class="styleless" @click="moveTo('/')" @keydown.prevent.enter="moveTo('/')" + @keydown.prevent.space="moveTo('/')" tabindex="0"> + <div class="navigation-icon"> + <studip-icon shape="arr_2up" role="info_alt" :size="iconSize - 4"></studip-icon> + </div> + <div class="navigation-text"> + {{ $gettext('Start') }} + </div> + </button> + </div> + </li> + <li v-if="currentParent != null" class="navigation-item navigation-current"> + <div class="navigation-title"> + <button class="styleless" tabindex="0" + @click="moveTo(currentParent.path)" + @keydown.prevent.enter="moveTo(currentParent.path)" + @keydown.prevent.space="moveTo(currentParent.path)" + :title="$gettext('Eine Ebene höher')"> + <div class="navigation-icon"> + <studip-icon shape="arr_1left" role="info_alt" :size="iconSize - 4"></studip-icon> + </div> + <div class="navigation-text"> + {{ $gettext('Eine Ebene höher') }} + </div> + </button> + </div> + </li> + <navigation-item v-for="(item, index) in currentNavigation.children" :key="index" + :item="item"></navigation-item> + </ul> + </nav> + </transition> + <responsive-content-bar v-if="(isResponsive || isFullscreen) && !isFocusMode" + :has-sidebar="hasSidebar" :title="initialTitle" + ref="contentbar"></responsive-content-bar> + <responsive-skip-links v-if="isFullscreen && hasSkiplinks" :links="skipLinks"></responsive-skip-links> + </div> +</template> + +<script> +import NavigationItem from './NavigationItem.vue'; +import StudipIcon from '../StudipIcon.vue'; +import ResponsiveContentBar from './ResponsiveContentBar.vue'; +import ToggleFullscreen from './ToggleFullscreen.vue'; +import ResponsiveSkipLinks from './ResponsiveSkipLinks.vue'; +import { FocusTrap } from 'focus-trap-vue'; + +export default { + name: 'ResponsiveNavigation', + components: { ResponsiveContentBar, StudipIcon, NavigationItem, ToggleFullscreen, ResponsiveSkipLinks, FocusTrap }, + props: { + me: { + type: Object, + required: true + }, + context: { + type: String, + default: '' + }, + hasSidebar: { + type: Boolean, + default: true + } + }, + data() { + return { + isResponsive: false, + isFullscreen: false, + isFocusMode: false, + headerMagic: false, + iconSize: 28, + showMenu: false, + activeItem: STUDIP.Navigation.activated.at(-1) ?? 'start', + currentNavigation: this.findItem(STUDIP.Navigation.activated.at(0) ?? 'start'), + initialNavigation: {}, + initialTitle: '', + isAdmin: ['root','admin'].includes(this.me.perm), + courses: [], + avatarNavigation: STUDIP.Navigation.navigation.avatar, + avatarMenuOpen: false, + observer: null, + hasSkiplinks: document.querySelector('#skiplink_list') !== null + } + }, + computed: { + // Current navigation title, supplemented by context title if available + currentTitle() { + return this.context != '' && this.currentNavigation.path.indexOf('my_courses/') != -1 ? + this.context : ''; + }, + // The parent element of the current navigation item + currentParent() { + return this.currentNavigation.parent != null + ? this.findItem(this.currentNavigation.parent) + : null; + }, + /* + * The parent element of the current navigation item parent + * which is used to provide a link up + */ + currentGrandparent() { + return this.currentParent != null && this.currentParent.parent != null + ? this.findItem(this.currentParent.parent) + : null; + }, + /* + * Is the responsive navigation menu needed (because we are in responsive or fullscreen mode)? + */ + menuNeeded() { + return !this.isFocusMode + && (this.isResponsive || this.isFullscreen || this.headerMagic); + }, + + skipLinks() { + let links = [ + { url: '#responsive-navigation-button', label: this.$gettext('Hauptnavigation') } + ]; + + if (this.isFullscreen) { + links.push( + { url: '#toggle-fullscreen', label: this.$gettext('Vollbildmodus verlassen') }, + ); + + if (this.hasSidebar) { + let name = ''; + if (document.getElementById('sidebar').classList.contains('responsive-show')) { + name = this.$gettext('Sidebar ausblenden'); + } else { + name = this.$gettext('Sidebar anzeigen'); + } + links.push({ url: '#toggle-sidebar', label: name }); + } + } + + return links; + } + }, + methods: { + /** + * Find a navigation item specified by given path in the given navigation structure + * @param path + * @param navigation + * @returns {{parent: null, path: string, visible: boolean, children, icon: null, active: boolean, title, url}|null} + */ + findItem(path, navigation) { + // No navigation given, use full Stud.IP navigation hierarchy. + if (!navigation) { + + const nav = STUDIP.Navigation.navigation; + + // Some "pseudo" navigation directly at root level. + if (path === '/' || path === 'start') { + return { + active: true, + children: nav, + icon: null, + parent: null, + path: '/', + title: nav.start.title, + url: nav.start.url, + visible: true + }; + // Direct hit in sub navigation items. + } else if (nav[path]) { + return nav[path]; + // Recurse through sub navigation items. + } else { + + let found = null; + for (const sub in nav) { + found = this.findItem(path, nav[sub]); + if (found) { + break; + } + } + return found; + + } + + } else { + + // Found requested item at current level. + if (navigation[path]) { + return navigation[path]; + } else { + + if (navigation.children) { + // Found requested item as child of current one. + if (navigation.children[path]) { + return navigation.children[path]; + // Recurse deeper. + } else { + let found = null; + for (const sub in navigation.children) { + found = this.findItem(path, navigation.children[sub]); + if (found) { + break; + } + } + return found; + + } + // No children left to search through, we are doomed. + } else { + return null; + } + + } + } + + }, + /** + * Open or close the navigation menu + * @param event + */ + toggleMenu() { + + this.showMenu = !this.showMenu; + + this.$nextTick(() => { + if (this.showMenu && !this.headerMagic) { + this.currentNavigation = this.initialNavigation; + + if (this.isResponsive) { + this.$refs.navigation.style.height = (document.documentElement.clientHeight - 42) + 'px'; + } + document.getElementById('header-links').style.display = 'none'; + } else { + document.getElementById('header-links').style.display = null; + } + }) + }, + /** + * Turn fullscreen mode on or off + * @param event + */ + setFullscreen(state) { + const html = document.querySelector('html'); + const sidebar = document.getElementById('sidebar'); + const cache = STUDIP.Cache.getInstance('responsive.'); + + if (state) { + html.classList.add('fullscreen-mode'); + cache.set('fullscreen-mode', true); + } else { + html.classList.remove('fullscreen-mode'); + sidebar.classList.remove('responsive-show', 'fullscreen-mode'); + this.showMenu = false; + cache.remove('fullscreen-mode'); + } + + this.isFullscreen = state; + + if (!this.isResponsive) { + this.moveHelpbar(); + } + }, + getUrl(url) { + return STUDIP.URLHelper.getURL(url, {}, true); + }, + /** + * Move to another item in navigation structure, specified by path + * @param string path + */ + moveTo(path) { + this.avatarMenuOpen = false; + this.currentNavigation = this.findItem(path ? path : '/'); + this.$nextTick(() => { + const current = document.querySelector('.navigation-current') ?? document.querySelector('.navigation-item'); + if (current) { + current.focus(); + } + }) + }, + /** + * Relocate the helpbar icon to another DOM location + * as it is part of top bar in responsive view. + */ + moveHelpbar() { + let tag = 'div'; + let target = '.tabs_wrapper'; + if (this.isFullscreen || this.isResponsive) { + tag = 'li'; + target = '#header-links ul'; + } + + let helpBar = document.createElement(tag); + const realHelpBar = document.querySelector('.helpbar-container'); + + const helpbarIcon = document.querySelector('#helpbar_icon'); + + if (helpbarIcon) { + const realIcon = helpbarIcon.querySelector('img.icon-shape-question-circle'); + realIcon.src = (this.isFullscreen || this.isResponsive) + ? realIcon.src.replace('blue', 'white') + : realIcon.src.replace('white', 'blue'); + + helpBar.appendChild(helpbarIcon); + helpBar.appendChild(document.querySelector('div.helpbar')); + helpBar.classList.add('helpbar-container'); + document.querySelector(target).appendChild(helpBar); + realHelpBar.remove(); + } + }, + /** + * Show or hide avatar navigation menu. + */ + toggleAvatarMenu() { + this.avatarMenuOpen = !this.avatarMenuOpen; + }, + onChangeViewMode(tagName, classes) { + const classList = classes.split(' '); + + switch (tagName) { + // watch for "consuming_mode" or "fixed" class changes + case 'BODY': + if (classList.includes('consuming_mode')) { + this.isFocusMode = true; + STUDIP.Vue.emit('consuming-mode-enabled'); + } else { + this.isFocusMode = false; + STUDIP.Vue.emit('consuming-mode-disabled'); + } + if (classList.includes('fixed')) { + this.headerMagic = true; + STUDIP.Vue.emit('header-magic-enabled'); + } else { + this.headerMagic = false; + STUDIP.Vue.emit('header-magic-disabled'); + } + break; + // Watch for "responsive-display" and "fullscreen-mode" class changes + case 'HTML': + if (classList.includes('responsive-display')) { + this.isResponsive = true; + + if (classList.includes('fullscreen-mode')) { + this.setFullscreen(false); + } + + STUDIP.Vue.emit('responsive-display-enabled'); + this.$nextTick(() => { + this.moveHelpbar(); + }) + } else { + this.isResponsive = false; + STUDIP.Vue.emit('responsive-display-disabled'); + this.$nextTick(() => { + this.moveHelpbar(); + }) + } + + if (classList.includes('fullscreen-mode')) { + this.isFullscreen = true; + + STUDIP.Vue.emit('fullscreen-enabled'); + } else { + this.isFullscreen = false; + STUDIP.Vue.emit('fullscreen-disabled'); + } + break; + case 'HEADER': + if (classList.includes('cw-ribbon-consume')) { + this.isFocusMode = true; + } else { + this.isFocusMode = false; + } + } + } + }, + mounted() { + const cache = STUDIP.Cache.getInstance('responsive.'); + const html = document.querySelector('html'); + const body = document.querySelector('body'); + const fullscreen = cache.get('fullscreen-mode') ?? false; + const fullscreenDocument = html.classList.contains('fullscreen-mode'); + + this.isFullscreen = fullscreenDocument || fullscreen; + if (this.isFullscreen && !fullscreenDocument) { + html.classList.add('fullscreen-mode'); + } + + if (html.classList.contains('responsive-display')) { + this.isResponsive = true; + } + + // Re-structure some DOM elements + this.$nextTick(() => { + if (this.isResponsive || (this.isFullscreen && !this.isFocusMode)) { + this.moveHelpbar(); + } + }) + + this.initialNavigation = this.currentNavigation; + this.initialTitle = this.initialNavigation.title; + + STUDIP.Vue.on('responsive-navigation-move-to', path => { + this.moveTo(path); + }) + + // Listen to changes in fullscreen setting + STUDIP.Vue.on('toggle-fullscreen', value => { + this.setFullscreen(value); + }) + + /* + * Use an observer for html and body in order to check + * whether we move into consuming mode or leave responsive mode. + */ + this.observer = new MutationObserver(mutations => { + for (const m of mutations) { + const newValue = m.target.getAttribute(m.attributeName); + this.onChangeViewMode(m.target.tagName, newValue); + } + }) + + // Observe <html> for class changes. + this.observer.observe(html, { + attributes: true, + attributeOldValue : false, + attributeFilter: ['class'] + }) + + // Observe <body> for class changes. + this.observer.observe(body, { + attributes: true, + attributeOldValue : false, + attributeFilter: ['class'] + }) + + // Observe courseware contentbar for consuming mode. + STUDIP.Vue.on('courseware-ribbon-mounted', element => { + this.observer.observe(element.$el.querySelector('header.cw-ribbon'), { + attributes: true, + attributeOldValue : false, + attributeFilter: ['class'] + }) + }) + + }, + beforeDestroy() { + this.observer.disconnect(); + document.getElementById('header-links').style.display = null; + } +} +</script> + +<style lang="scss"> +.appear-enter-active, +.appear-leave-active { + transition: opacity .3s ease; +} + +.appear-enter, +.appear-leave-to { + opacity: 0; +} +</style> diff --git a/resources/vue/components/responsive/ResponsiveSkipLinks.vue b/resources/vue/components/responsive/ResponsiveSkipLinks.vue new file mode 100644 index 00000000000..52f0bfcfdbb --- /dev/null +++ b/resources/vue/components/responsive/ResponsiveSkipLinks.vue @@ -0,0 +1,49 @@ +<template> + <div> + <MountingPortal mount-to="#skiplink_list" append> + <portal-target name="additional-skiplinks"></portal-target> + </MountingPortal> + <portal to="additional-skiplinks"> + <li v-for="(link) in links" :key="link.url"> + <button class="skiplink" role="link" @click.prevent="goto(link.url)"> + {{ link.label }} + </button> + </li> + </portal> + </div> +</template> + +<script> +export default { + name: 'ResponsiveSkipLinks', + props: { + links: { + type: Array, + default: () => [] + } + }, + methods: { + goto(url) { + window.location = url; + } + }, + created() { + const allButtons = document.querySelectorAll('button.skiplink'); + const buttons = document.querySelectorAll('button.skiplink:not([data-in-fullscreen="1"])'); + buttons.forEach(button => { + button.style.display = 'none'; + }); + this.$nextTick(() => { + allButtons.forEach(button => { + document.getElementById('skiplink_list').appendChild(button.parentNode); + }); + }) + }, + beforeDestroy() { + const buttons = document.querySelectorAll('button.skiplink:not([data-in-fullscreen="1"])'); + buttons.forEach(button => { + button.style.display = null; + }); + } +} +</script> diff --git a/resources/vue/components/responsive/ToggleFullscreen.vue b/resources/vue/components/responsive/ToggleFullscreen.vue new file mode 100644 index 00000000000..77ce8fbdbc6 --- /dev/null +++ b/resources/vue/components/responsive/ToggleFullscreen.vue @@ -0,0 +1,51 @@ +<template> + <div> + <MountingPortal mount-to="#responsive-toggle-fullscreen" append> + <portal-target name="toggle-fullscreen-off"></portal-target> + </MountingPortal> + <MountingPortal mount-to="#non-responsive-toggle-fullscreen" append> + <portal-target name="toggle-fullscreen-on"></portal-target> + </MountingPortal> + <portal :to="isFullscreen ? 'toggle-fullscreen-off' : 'toggle-fullscreen-on'"> + <button class="styleless" id="toggle-fullscreen" + :title="isFullscreen ? $gettext('Vollbildmodus verlassen') : $gettext('Vollbildmodus aktivieren')" + @click.prevent="toggleFullscreen" + @keydown.prevent.enter="toggleFullscreen" + @keydown.prevent.space="toggleFullscreen"> + <studip-icon :shape="isFullscreen ? 'fullscreen-off' : 'fullscreen-on4'" + :role="isFullscreen ? 'info_alt' : 'clickable'" :size="iconSize" alt=""></studip-icon> + </button> + </portal> + </div> +</template> + +<script> +import StudipIcon from '../StudipIcon.vue'; + +export default { + name: 'ToggleFullscreen', + components: { StudipIcon }, + props: { + isFullscreen: { + type: Boolean, + default: false + }, + iconSize: { + type: Number, + default: 24 + } + }, + methods: { + toggleFullscreen() { + STUDIP.Vue.emit('toggle-fullscreen', !this.isFullscreen); + } + }, + created() { + window.addEventListener('keydown', e => { + if (e.key === 'Escape' && this.isFullscreen) { + this.toggleFullscreen(); + } + }); + } +} +</script> diff --git a/resources/vue/mixins/MyCoursesMixin.js b/resources/vue/mixins/MyCoursesMixin.js index 81d85ab5e7b..203340b5ebb 100644 --- a/resources/vue/mixins/MyCoursesMixin.js +++ b/resources/vue/mixins/MyCoursesMixin.js @@ -164,9 +164,9 @@ export default { }, created () { - this.responsiveDisplay = Responsive.media_query.matches; + this.responsiveDisplay = Responsive.isResponsive(); Responsive.media_query.addListener(() => { - this.responsiveDisplay = Responsive.media_query.matches; + this.responsiveDisplay = Responsive.isResponsive(); }) } } diff --git a/templates/contentbar/contentbar.php b/templates/contentbar/contentbar.php index 4433aabdc5b..851c77e3988 100644 --- a/templates/contentbar/contentbar.php +++ b/templates/contentbar/contentbar.php @@ -1,36 +1,37 @@ -<div class="contentbar"> - <div class="contentbar_title"> - <? if (!$toc->isActive()) : ?> - <a href="<?= $toc->getUrl() ?>" title="<?= htmlReady($toc->getTitle()) ?>"> - <? endif ?> - <?= $icon->asImg(24, ['class' => 'text-bottom']) ?> - <? if (!$toc->isActive()) : ?> - </a> - <? endif ?> - <ul class="breadcrumb"><?= $breadcrumbs->render() ?></ul> - </div> - - <div class="contentbar_info"> - <div class="textblock"><?= $info ?></div> - - <div class="contentbar-icons"> +<header> + <header class="contentbar"> + <nav class="contentbar-nav"></nav> + <div class="contentbar-wrapper-left"> + <nav class="contentbar-breadcrumb"> + <? if (!$toc->isActive()) : ?> + <a href="<?= $toc->getUrl() ?>" title="<?= htmlReady($toc->getTitle()) ?>" class="contentbar-icon"> + <? endif ?> + <?= $icon->asImg(24, ['class' => 'text-bottom']) ?> + <? if (!$toc->isActive()) : ?> + </a> + <? endif ?> + <?= $breadcrumbs->render() ?> + </nav> + </div> + <div class="contentbar-wrapper-right"> <? if ($toc->hasChildren()) : ?> - <input type="checkbox" id="cb-toc"> - <label for="cb-toc" class="check-box enter-accessible" title="<?= _('Inhaltsverzeichnis') ?>" tabindex="0"> - <?= Icon::create('table-of-contents')->asImg(24) ?> - </label> - <?= $ttpl->render() ?> + <div class="contentbar-button-wrapper contentbar-toc-wrapper"> + <input type="checkbox" id="cb-toc"> + <label for="cb-toc" class="contentbar-button contentbar-button-menu check-box enter-accessible" title="<?= _('Inhaltsverzeichnis') ?>" tabindex="0"> + </label> + <?= $ttpl->render() ?> + </div> <? endif ?> - <a class="consuming_mode_trigger" - href="#" - title="<?= _("Konsummodus ein-/ausschalten") ?>"> - </a> + <div class="contentbar-button-wrapper contentbar-consuming-mode-wrapper"> + <button class="contentbar-button contentbar-button-zoom consuming_mode_trigger"></button> + </div> <? if ($actionMenu) : ?> - <?= $actionMenu->render() ?> + <div class="contentbar-button-wrapper contentbar-action-menu-wrapper"> + <?= $actionMenu->render() ?> + </div> <? endif ?> </div> - </div> - + </header> </div> diff --git a/templates/footer.php b/templates/footer.php index 5f4f0504bab..57fe54cf050 100644 --- a/templates/footer.php +++ b/templates/footer.php @@ -1,5 +1,5 @@ <!-- Beginn Footer --> -<?= SkipLinks::addIndex(_('Fußzeile'), 'main-footer',900) ?> +<?= SkipLinks::addIndex(_('Fußzeile'), 'main-footer', 900, false) ?> <footer id="main-footer" aria-label="<?= _('Fußzeile') ?>"> <? if (is_object($GLOBALS['user']) && $GLOBALS['user']->id != 'nobody') : ?> <div id="main-footer-info"> @@ -54,5 +54,4 @@ <? endif; ?> </footer> <?= $this->render_partial('debug/db-log.php') ?> -<?= $this->render_partial('responsive-navigation.php') ?> <!-- Ende Footer --> diff --git a/templates/header.php b/templates/header.php index a234358627c..b3bf385be84 100644 --- a/templates/header.php +++ b/templates/header.php @@ -59,11 +59,27 @@ if ($navigation) { <!-- Top bar with site title, quick search and avatar menu --> <div id="top-bar" role="banner"> <div id="responsive-menu"> - <input type="checkbox" id="barTopMenu-toggle"> - <label for="barTopMenu-toggle"> - <?= _('Menü') ?> - </label> - <? // The main menu will be placed here when scrolled, see navigation.less ?> + <?= $this->render_partial('responsive-navigation.php') ?> + <? + $user = User::findCurrent(); + if ($user) { + $me = [ + 'avatar' => Avatar::getAvatar($user->id)->getURL(Avatar::MEDIUM), + 'email' => $user->email, + 'fullname' => $user->getFullName(), + 'username' => $user->username, + 'perm' => $GLOBALS['perm']->get_perm() + ]; + + $hasSidebar = Sidebar::get()->countWidgets(NavigationWidget::class) > 0; + ?> + <? } else { + $me = ['username' => 'nobody']; + $hasSidebar = false; + } ?> + <responsive-navigation :me='<?= json_encode($me) ?>' context="<?= htmlReady(Context::get() ? + Context::get()->getFullname() : '') ?>" :has-sidebar="<?= $hasSidebar ? 'true' : 'false' ?>"> + </responsive-navigation> </div> <div id="site-title"> <?= htmlReady(Config::get()->UNI_NAME_CLEAN) ?> @@ -97,7 +113,7 @@ if ($navigation) { <? if (PageLayout::hasCustomQuicksearch()): ?> <?= PageLayout::getCustomQuicksearch() ?> <? else: ?> - <? SkipLinks::addIndex(_('Suche'), 'globalsearch-input', 910) ?> + <? SkipLinks::addIndex(_('Suche'), 'globalsearch-input', 910, false) ?> <li id="quicksearch_item"> <script> var selectSem = function (seminar_id, name) { @@ -179,7 +195,7 @@ if ($navigation) { $subnav->getImage() ); } - SkipLinks::addIndex(_('Profilmenü'), "header_avatar_image_link", 1); + SkipLinks::addIndex(_('Profilmenü'), 'header_avatar_image_link', 1, false); ?> <?= $action_menu->render(); ?> </div> @@ -187,6 +203,8 @@ if ($navigation) { </li> <? endif; ?> + <li id="responsive-toggle-desktop"></li> + <li id="responsive-toggle-fullscreen"></li> </ul> </div> </div> @@ -194,7 +212,7 @@ if ($navigation) { <!-- Main navigation and right-hand logo --> <nav id="navigation-level-1" aria-current="page" aria-label="<?= _('Hauptnavigation') ?>"> - <? SkipLinks::addIndex(_('Hauptnavigation'), 'navigation-level-1', 2); ?> + <? SkipLinks::addIndex(_('Hauptnavigation'), 'navigation-level-1', 2, false); ?> <ul id="navigation-level-1-items" <? if (count($header_nav['hidden']) > 0) echo 'class="overflown"'; ?>> <? foreach ($header_nav['visible'] as $path => $nav): ?> <?= $this->render_partial( @@ -309,5 +327,7 @@ if ($navigation) { </div> </div> + <div id="responsive-contentbar-container"></div> + <!-- End main site header --> </header> diff --git a/templates/layouts/base.php b/templates/layouts/base.php index 1345dcdf854..e3eb6c39ec1 100644 --- a/templates/layouts/base.php +++ b/templates/layouts/base.php @@ -26,12 +26,6 @@ $lang_attr = str_replace('_', '-', $_SESSION['_language']); String.locale = "<?= htmlReady(strtr($_SESSION['_language'], '_', '-')) ?>"; document.querySelector('html').classList.replace('no-js', 'js'); - setTimeout(() => { - // This needs to be put in a timeout since otherwise it will not match - if (window.matchMedia('(max-width: 767px)').matches) { - document.querySelector('html').classList.add('responsive-display'); - } - }, 0); window.STUDIP = { ABSOLUTE_URI_STUDIP: "<?= $GLOBALS['ABSOLUTE_URI_STUDIP'] ?>", @@ -66,7 +60,14 @@ $lang_attr = str_replace('_', '-', $_SESSION['_language']); <script> window.STUDIP.editor_enabled = <?= json_encode((bool) Studip\Markup::editorEnabled()) ?>; - </script> + + setTimeout(() => { + // This needs to be put in a timeout since otherwise it will not match + if (STUDIP.Responsive.isResponsive()) { + document.querySelector('html').classList.add('responsive-display'); + } + }, 0); +</script> </head> <body id="<?= PageLayout::getBodyElementId() ?>" <? if (SkipLinks::isEnabled()) echo 'class="enable-skiplinks"'; ?>> @@ -79,6 +80,7 @@ $lang_attr = str_replace('_', '-', $_SESSION['_language']); <!-- Start main page content --> <main id="content-wrapper"> <div id="content"> + <h1 class="sr-only"><?= htmlReady(PageLayout::getTitle()) ?></h1> <? if (PageLayout::isFullscreenModeAllowed()): ?> <button hidden class="fullscreen-toggle unfullscreen" aria-label="<?= _('Vollbildmodus verlassen') ?>" title="<?= _('Vollbildmodus verlassen') ?>"> <?= Icon::create('zoom-out2')->asImg(24) ?> diff --git a/templates/skiplinks.php b/templates/skiplinks.php index 3854c4c6a96..da222107894 100644 --- a/templates/skiplinks.php +++ b/templates/skiplinks.php @@ -3,15 +3,22 @@ ?> <? if ($navigation instanceof Navigation && iterator_count($navigation) > 0) : ?> <ul role="navigation" id="skiplink_list"> - <? foreach ($navigation as $nav) : ?> + <? foreach ($navigation as $index => $nav) : ?> <li> <? if (mb_substr($url = $nav->getURL(), 0, 1) == '#') : ?> - <button class="skiplink" role="link" onclick="STUDIP.SkipLinks.setActiveTarget('<?= $url ?>');"><?= htmlReady($nav->getTitle()) ?></button> + <button class="skiplink" role="link" onclick="STUDIP.SkipLinks.setActiveTarget('<?= htmlReady($url) ?>');" + data-in-fullscreen="<?= $fullscreen[$index] ?>"> + <?= htmlReady($nav->getTitle()) ?> + </button> <? else : ?> <? if (is_internal_url($url)) : ?> - <a href="<?= URLHelper::getLink($url) ?>"><?= htmlReady($nav->getTitle()) ?></a> + <a href="<?= URLHelper::getLink($url) ?>" data-in-fullscreen="<?= $fullscreen[$index] ?>"> + <?= htmlReady($nav->getTitle()) ?> + </a> <? else : ?> - <a href="<?= htmlReady($url) ?>"><?= htmlReady($nav->getTitle()) ?></a> + <a href="<?= htmlReady($url) ?>" data-in-fullscreen="<?= $fullscreen[$index] ?>"> + <?= htmlReady($nav->getTitle()) ?> + </a> <? endif ?> <? endif ?> </li> diff --git a/templates/tabs.php b/templates/tabs.php index 1b505e7c33d..cfdd550cecc 100644 --- a/templates/tabs.php +++ b/templates/tabs.php @@ -8,7 +8,7 @@ foreach (Navigation::getItem("/")->getSubNavigation() as $path => $nav) { $ebene3 = []; ?> <div class="tabs_wrapper"> - <? SkipLinks::addIndex(_('Zweite Navigationsebene'), 'navigation-level-2', 10); ?> + <? SkipLinks::addIndex(_('Zweite Navigationsebene'), 'navigation-level-2', 10, false); ?> <ul id="tabs" role="navigation"> <? if (!empty($navigation)): ?> <? foreach ($navigation as $path => $nav) : ?> @@ -60,13 +60,7 @@ $ebene3 = []; <? endforeach ?> <? endif; ?> </ul> - <? if (PageLayout::isFullscreenModeAllowed()): ?> - <div class="fullscreen-container"> - <button class="fullscreen-toggle" aria-label="<?= _('Vollbildmodus') ?>" title="<?= _('Vollbildmodus') ?>"> - <?= Icon::create('zoom-in2')->asImg(24) ?> - </button> - </div> - <? endif ?> + <div id="non-responsive-toggle-fullscreen"></div> <? if (is_object($GLOBALS['perm']) && $GLOBALS['perm']->have_perm('autor')) : ?> <?= Helpbar::get()->render() ?> <? endif; ?> -- GitLab