From e9806e73f7d91f0810a298bc6c9139e6da138575 Mon Sep 17 00:00:00 2001 From: Rasmus Fuhse <fuhse@data-quest.de> Date: Tue, 28 May 2024 12:58:57 +0000 Subject: [PATCH] Resolve "Barrierefreiheit: Globale Suche nicht barrierefrei nutzbar" Closes #4072 Merge request studip/studip!2918 --- .../globalsearch/GlobalSearchCourses.php | 4 +- .../globalsearch/GlobalSearchCourseware.php | 2 +- .../globalsearch/GlobalSearchMessages.php | 6 +-- .../globalsearch/GlobalSearchMyCourses.php | 4 +- .../globalsearch/GlobalSearchUsers.php | 2 +- .../javascripts/bootstrap/global_search.js | 45 +++++++++++++++++++ .../assets/javascripts/lib/global_search.js | 14 +++--- .../assets/stylesheets/scss/globalsearch.scss | 4 +- templates/globalsearch/searchbar.php | 19 ++++++-- 9 files changed, 77 insertions(+), 23 deletions(-) diff --git a/lib/classes/globalsearch/GlobalSearchCourses.php b/lib/classes/globalsearch/GlobalSearchCourses.php index bf29ca04ecb..9de5535dba2 100644 --- a/lib/classes/globalsearch/GlobalSearchCourses.php +++ b/lib/classes/globalsearch/GlobalSearchCourses.php @@ -225,9 +225,9 @@ class GlobalSearchCourses extends GlobalSearchModule implements GlobalSearchFull array_map( function ($lecturer, $index) use ($search, $course) { if ($index < 3) { - return '<a href="' . URLHelper::getURL('dispatch.php/profile', ['username' => $lecturer->username]) . '">' . self::mark($lecturer->getUserFullname(), $search) . '</a>'; + return self::mark($lecturer->getUserFullname(), $search); } else if ($index == 3) { - return '<a href="' . URLHelper::getURL('dispatch.php/course/details/index/' . $course->id) . '">... (' . _('mehr') . ') </a>'; + return '... (' . _('mehr') . ')'; } }, $lecturers, diff --git a/lib/classes/globalsearch/GlobalSearchCourseware.php b/lib/classes/globalsearch/GlobalSearchCourseware.php index d4c53b168d1..de069fe887e 100644 --- a/lib/classes/globalsearch/GlobalSearchCourseware.php +++ b/lib/classes/globalsearch/GlobalSearchCourseware.php @@ -142,7 +142,7 @@ class GlobalSearchCourseware extends GlobalSearchModule implements GlobalSearchF 'description' => $description, 'url' => $pageData['url'], 'img' => $structural_element->image ? $structural_element->getImageUrl() : Icon::create('courseware')->asImagePath(), - 'additional' => '<a href="' . htmlReady($pageData['originUrl']) . '" title="' . htmlReady($pageData['originName']) . '">' . htmlReady($pageData['originName']) . '</a>', + 'additional' => htmlReady($pageData['originName']), 'date' => $date->format('d.m.Y H:i'), 'structural-element-id' => $structural_element->id, 'expand' => null diff --git a/lib/classes/globalsearch/GlobalSearchMessages.php b/lib/classes/globalsearch/GlobalSearchMessages.php index d1a91a52f13..5f64b40413b 100644 --- a/lib/classes/globalsearch/GlobalSearchMessages.php +++ b/lib/classes/globalsearch/GlobalSearchMessages.php @@ -79,11 +79,7 @@ class GlobalSearchMessages extends GlobalSearchModule if ($user) { $username = $user->getFullName(); - $additional = sprintf( - '<a href="%s">%s</a>', - URLHelper::getLink('dispatch.php/profile', ['username' => $user->username]), - self::mark($user->getFullName(), $search) - ); + $additional = self::mark($user->getFullName(), $search); } } diff --git a/lib/classes/globalsearch/GlobalSearchMyCourses.php b/lib/classes/globalsearch/GlobalSearchMyCourses.php index f8f2f11ec69..6558f787ba5 100644 --- a/lib/classes/globalsearch/GlobalSearchMyCourses.php +++ b/lib/classes/globalsearch/GlobalSearchMyCourses.php @@ -162,9 +162,9 @@ class GlobalSearchMyCourses extends GlobalSearchModule array_map( function ($lecturer, $index) use ($search, $course) { if ($index < 3) { - return '<a href="' . URLHelper::getURL('dispatch.php/profile', ['username' => $lecturer->username]) . '">' . self::mark($lecturer->getUserFullname(), $search) . '</a>'; + return self::mark($lecturer->getUserFullname(), $search); } else if ($index == 3) { - return '<a href="' . URLHelper::getURL('dispatch.php/course/details/index/' . $course->id) . '">... (' . _('mehr') . ') </a>'; + return '... (' . _('mehr') . ')'; } }, $lecturers, diff --git a/lib/classes/globalsearch/GlobalSearchUsers.php b/lib/classes/globalsearch/GlobalSearchUsers.php index fb8677ae626..458f0982101 100644 --- a/lib/classes/globalsearch/GlobalSearchUsers.php +++ b/lib/classes/globalsearch/GlobalSearchUsers.php @@ -86,7 +86,7 @@ class GlobalSearchUsers extends GlobalSearchModule implements GlobalSearchFullte ['username' => $user->username], true ), - 'additional' => '<a href="' . URLHelper::getLink('dispatch.php/profile', ['username' => $user->username]) . '">' . self::mark($user->username, $search) . '</a>', + 'additional' => self::mark($user->username, $search), 'expand' => self::getSearchURL($search), 'img' => Avatar::getAvatar($user->id)->getUrl(Avatar::MEDIUM), ]; diff --git a/resources/assets/javascripts/bootstrap/global_search.js b/resources/assets/javascripts/bootstrap/global_search.js index 4d0738ed62d..a85f2f0d831 100644 --- a/resources/assets/javascripts/bootstrap/global_search.js +++ b/resources/assets/javascripts/bootstrap/global_search.js @@ -36,6 +36,51 @@ STUDIP.domReady(() => { return false; } }); + $('#globalsearch-input').on('keypress', function(e) { + if (e.which === 13) { + STUDIP.GlobalSearch.doSearch(); + return false; + } + }); + $('#globalsearch-searchbar').on('keydown', function(e) { + if (e.originalEvent.code === 'ArrowDown') { + if ($('#globalsearch-list [role=listitem]:focus').length === 0) { + $('#globalsearch-list [role=listitem]:visible').first().focus(); + } else { + let n = $('#globalsearch-list [role=listitem]:focus').next(); + if (n.length > 0 && n.is('[role=listitem]:visible')) { + n.focus(); + } else { + n = $('#globalsearch-list [role=listitem]:focus').parent().next().find('[role=listitem]:visible').first(); + if (n.length > 0) { + n.focus(); + } else { + $('#globalsearch-list [role=listitem]:visible').first().focus(); + } + } + } + return false; + } + if (e.originalEvent.code === 'ArrowUp') { + if ($('#globalsearch-list [role=listitem]:focus').length === 0) { + $('#globalsearch-list [role=listitem]:visible').last().focus(); + } else { + let n = $('#globalsearch-list [role=listitem]:focus').prev(); + if (n.length > 0 && n.is('[role=listitem]:visible')) { + n.focus(); + } else { + n = $('#globalsearch-list [role=listitem]:focus').parent().prev().find('[role=listitem]:visible').last(); + if (n.length > 0) { + n.focus(); + } else { + $('#globalsearch-list [role=listitem]:visible').last().focus(); + } + } + } + return false; + } + }); + // Close search on click on page. $('#navigation-level-1, #current-page-structure, #main-footer').on('click', function() { diff --git a/resources/assets/javascripts/lib/global_search.js b/resources/assets/javascripts/lib/global_search.js index 05a53aa3200..394b7e30a33 100644 --- a/resources/assets/javascripts/lib/global_search.js +++ b/resources/assets/javascripts/lib/global_search.js @@ -9,6 +9,7 @@ const GlobalSearch = { */ toggleSearchBar: function(visible, cleanup) { $('#globalsearch-searchbar').toggleClass('is-visible', visible); + $('#globalsearch-input').attr('aria-expanded', visible ? 'true' : 'false'); $('#globalsearch-input').toggleClass('hidden-small-down', !visible); $('#globalsearch-icon').toggleClass('hidden-small-down', visible); $('#globalsearch-clear').toggleClass('hidden-small-down', !visible); @@ -70,7 +71,7 @@ const GlobalSearch = { // Iterate over each result category. $.each(json, function(name, value) { // Create an <article> for category. - var category = $(`<article id="globalsearch-${name}">`), + var category = $(`<article id="globalsearch-${name}" role="list">`), header = $('<header>').appendTo(category), counter = 0; @@ -96,7 +97,7 @@ const GlobalSearch = { // Process results and create corresponding entries. $.each(value.content, function(index, result) { // Create single result entry. - var single = $('<section>'), + var single = $(`<a href="${result.url}" role="listitem" ${dataDialog}>`), data = $('<div class="globalsearch-result-data">'), details = $('<div class="globalsearch-result-details">'); @@ -107,17 +108,17 @@ const GlobalSearch = { // Which result types should be opened via dialog? const openInDialog = ['GlobalSearchFiles', 'GlobalSearchMessages']; var dataDialog = (openInDialog.indexOf(name) >= 0 ? dataDialog = 'data-dialog' : dataDialog = ''); - var link = $(`<a href="${result.url}" ${dataDialog}>`).appendTo(single); + //var link = $(`<a href="${result.url}" ${dataDialog}>`).appendTo(single); // Optional image... if (result.img !== null) { $(`<img src="${result.img}" alt="">`) .wrap('<div class="globalsearch-result-img">') .parent() // Element is now the wrapper - .appendTo(link); + .appendTo(single); } - link.append(data); + single.append(data); // Name/title $('<div class="globalsearch-result-title">') @@ -144,7 +145,7 @@ const GlobalSearch = { if (result.date !== null) { $('<div class="globalsearch-result-time">') .html(result.date) - .appendTo(link); + .appendTo(single); } // "Expand" attribute for further, result-related search @@ -178,6 +179,7 @@ const GlobalSearch = { GlobalSearch.lastSearch = null; $('#globalsearch-searchbar').removeClass('is-visible has-value'); + $('#globalsearch-input').attr('aria-expanded', 'false'); $('#globalsearch-input').val(''); $('#globalsearch-results').html(''); $('#globalsearch-input').focus(); diff --git a/resources/assets/stylesheets/scss/globalsearch.scss b/resources/assets/stylesheets/scss/globalsearch.scss index 4faee5feabd..ef7c6e652f8 100644 --- a/resources/assets/stylesheets/scss/globalsearch.scss +++ b/resources/assets/stylesheets/scss/globalsearch.scss @@ -171,7 +171,7 @@ } } - section { + a[role=listitem] { display: flex; flex-direction: row; flex-wrap: nowrap; @@ -189,7 +189,7 @@ display: none; } - & > a { + & > span.detail { display: flex; flex-direction: row; flex-wrap: nowrap; diff --git a/templates/globalsearch/searchbar.php b/templates/globalsearch/searchbar.php index 93a8ab51224..837931cf3f1 100644 --- a/templates/globalsearch/searchbar.php +++ b/templates/globalsearch/searchbar.php @@ -1,6 +1,16 @@ -<div id="globalsearch-searchbar" role="search" aria-label="<?= _('Globale Suche') ?>"> - <input class="hidden-small-down" type="text" name="globalsearchterm" id="globalsearch-input" - placeholder="<?= _('Was suchen Sie?') ?>" role="searchbox"> +<div id="globalsearch-searchbar" + role="search" + aria-label="<?= _('Globale Suche') ?>"> + <input class="hidden-small-down" + type="text" + name="globalsearchterm" + id="globalsearch-input" + placeholder="<?= _('Was suchen Sie?') ?>" + role="combobox" + aria-haspopup="listbox" + aria-expanded="false" + aria-controls="globalsearch-list" + aria-label="Suche nach Objekten und Personen in Stud.IP"> <?= Icon::create('decline', Icon::ROLE_INACTIVE)->asInput([ 'id' => 'globalsearch-clear', 'class' => 'hidden-small-down', @@ -10,7 +20,8 @@ 'id' => 'globalsearch-icon', 'alt' => _('Suche starten') ]) ?> - <div id="globalsearch-list"> + <div id="globalsearch-list" + role="listbox"> <a href="#" id="globalsearch-togglehints" data-toggle-text="<?= _('Tipps ausblenden') ?>"> <?= _('Tipps einblenden') ?> </a> -- GitLab