diff options
| author | Murtaza Sultani <sultani@data-quest.de> | 2026-01-21 11:33:56 +0100 |
|---|---|---|
| committer | Murtaza Sultani <sultani@data-quest.de> | 2026-01-21 11:33:56 +0100 |
| commit | b5fdb4af0fe8c2dafcb62e1e072eac9e142805fa (patch) | |
| tree | 481911be648d696381b424b5f131b7d36b784c67 | |
| parent | bb1f39e8aa5fb6e03d4351af0f1024ac730ee96f (diff) | |
Resolve "Forum3: A11y"
Closes #6181
Merge request studip/studip!4682
28 files changed, 588 insertions, 306 deletions
diff --git a/resources/vue/apps/forum/categories/Index.vue b/resources/vue/apps/forum/categories/Index.vue index 62a3397..62dbe4d 100644 --- a/resources/vue/apps/forum/categories/Index.vue +++ b/resources/vue/apps/forum/categories/Index.vue @@ -11,6 +11,8 @@ import StudipIcon from '@/vue/components/StudipIcon.vue'; import {deserializeJSONAPIResponse} from '@/assets/javascripts/lib/jsonapiUtils'; import StudipPagination from '@/vue/components/StudipPagination.vue'; import {useSortable} from '@/vue/composables/useSortable'; +import ShowCategory from "@/vue/components/forum/categories/ShowCategory.vue"; +import StudipDialog from "@/vue/components/StudipDialog.vue"; const forumConfig = useForumConfig(); const currentCategory = ref(null); @@ -201,6 +203,7 @@ const showCategoryDialog = category => currentCategory.value = category; <thead> <tr class="sortable"> <th + scope="col" :class="getSortClass('name')" :aria-sort="getAriaSortString('name')" :aria-label="getAriaSortLabel('name', $gettext('Name'))" @@ -214,6 +217,7 @@ const showCategoryDialog = category => currentCategory.value = category; </button> </th> <th + scope="col" :class="getSortClass('meta.discussions_count')" :aria-sort="getAriaSortString('meta.discussions_count')" :aria-label="getAriaSortLabel('meta.discussions_count', $gettext('Anzahl der Diskussionen'))" @@ -227,6 +231,7 @@ const showCategoryDialog = category => currentCategory.value = category; </button> </th> <th + scope="col" :class="getSortClass('meta.users_count')" :aria-sort="getAriaSortString('meta.users_count')" :aria-label="getAriaSortLabel('meta.users_count', $gettext('Anzahl der Teilnehmenden'))" @@ -240,6 +245,7 @@ const showCategoryDialog = category => currentCategory.value = category; </button> </th> <th + scope="col" :class="getSortClass('meta.postings_count')" :aria-sort="getAriaSortString('meta.postings_count')" :aria-label="getAriaSortLabel('meta.postings_count', $gettext('Anzahl der Beiträge'))" @@ -253,6 +259,7 @@ const showCategoryDialog = category => currentCategory.value = category; </button> </th> <th + scope="col" :class="getSortClass('meta.recent_activity')" :aria-sort="getAriaSortString('meta.recent_activity')" :aria-label="getAriaSortLabel('meta.recent_activity', $gettext('Letzte Aktivität'))" @@ -265,7 +272,9 @@ const showCategoryDialog = category => currentCategory.value = category; {{ $gettext('Letzte Aktivität') }} </button> </th> - <th></th> + <th scope="col"> + <span class="sr-only">{{ $gettext('Aktionen') }}</span> + </th> </tr> </thead> <Draggable @@ -313,5 +322,20 @@ const showCategoryDialog = category => currentCategory.value = category; :itemsPerPage="pagination.limit" @pageUpdated="fetchCategories" /> </div> + + <StudipDialog + v-if="currentCategory?.id" + :title="$gettext('Detaillierte Information')" + :closeText="$gettext('Schließen')" + height="700" + width="600" + @close="currentCategory = null" + > + <template #dialogContent> + <div class="forum"> + <ShowCategory :category="currentCategory" /> + </div> + </template> + </StudipDialog> </ForumApp> </template> diff --git a/resources/vue/apps/forum/categories/Show.vue b/resources/vue/apps/forum/categories/Show.vue index 2d5d5b2..40609f0 100644 --- a/resources/vue/apps/forum/categories/Show.vue +++ b/resources/vue/apps/forum/categories/Show.vue @@ -72,19 +72,33 @@ onMounted(async () => { {{ category.name }} </h2> <div class="mt-10 inline-flex gap-20 items-center"> - <span class="inline-flex gap-5 items-center" :title="$gettext('Anzahl der Teilnehmenden an der Diskussion')" role="group"> + <span + class="inline-flex gap-5 items-center" + role="group" + :title="$gettext('Anzahl der Teilnehmenden an der Diskussion')" + :aria-label="$gettext('Anzahl der Teilnehmenden an der Diskussion')" + > <StudipIcon shape="community2" :size="15" role="info" aria-hidden="true" /> <span class="sr-only">{{ $gettext('Anzahl der Teilnehmenden an der Diskussion') }}:</span> <small>{{ metadata.users_count }}</small> </span> - <span class="inline-flex gap-5 items-center" :title="$gettext('Anzahl der Beiträge')" role="group"> + <span + role="group" + class="inline-flex gap-5 items-center" + :title="$gettext('Anzahl der Beiträge')" + :aria-label="$gettext('Anzahl der Beiträge')" + > <StudipIcon shape="reply" :size="15" role="info" aria-hidden="true" /> <span class="sr-only">{{ $gettext('Anzahl der Beiträge') }}:</span> <small>{{ metadata.postings_count }}</small> </span> - <span class="inline-flex gap-5 items-center" :title="$gettext('Letzte Aktivität')" role="group"> + <span + role="group" + class="inline-flex gap-5 items-center" + :title="$gettext('Letzte Aktivität')" + :aria-label="$gettext('Letzte Aktivität')" + > <StudipIcon shape="activity" :size="15" role="info" aria-hidden="true" /> - <span class="sr-only">{{ $gettext('Letzte Aktivität') }}:</span> <StudipDateTime v-if="metadata.recent_activity" :iso="metadata.recent_activity" :relative="true" /> <template v-else>{{ $gettext('Keine Aktivität') }}</template> </span> @@ -94,20 +108,24 @@ onMounted(async () => { <div class="actions"> <CreateTopic :category_id="category.category_id" /> <button + type="button" + class="button button--icon-only" v-if="forumConfig.tileLayout" @click="forumConfig.toggleForumLayout()" - type="button" :title="$gettext('Tabellarische Ansicht')" - class="button button--icon-only"> - <StudipIcon shape="view-list" :size="20" /> + :aria-label="$gettext('Tabellarische Ansicht')" + > + <StudipIcon shape="view-list" :size="20" aria-hidden="true" /> </button> <button v-else - @click="forumConfig.toggleForumLayout()" type="button" + class="button button--icon-only" + @click="forumConfig.toggleForumLayout()" :title="$gettext('Kachelansicht')" - class="button button--icon-only"> - <StudipIcon shape="view-wall" :size="20" /> + :aria-label="$gettext('Kachelansicht')" + > + <StudipIcon shape="view-wall" :size="20" aria-hidden="true" /> </button> <div aria-live="polite" class="sr-only" role="status">{{ toggleLayoutMessage }}</div> </div> diff --git a/resources/vue/apps/forum/discussions/Edit.vue b/resources/vue/apps/forum/discussions/Edit.vue index df63764..ecdfbe1 100644 --- a/resources/vue/apps/forum/discussions/Edit.vue +++ b/resources/vue/apps/forum/discussions/Edit.vue @@ -95,24 +95,41 @@ onMounted(() => { <div class="discussion-badges-container"> <div v-if="discussionForm.topic" class="badge"> - <span :style="{ backgroundColor: discussionForm.topic.color ?? '#EDEDED', height: '12px', width: '12px'}"></span> + <span :style="{ backgroundColor: discussionForm.topic.color ?? '#EDEDED', height: '12px', width: '12px'}" aria-hidden="true"></span> <span>{{ discussionForm.topic.name }}</span> - <button @click="discussionForm.topic = null" class="action"> - <StudipIcon shape="decline" :size="15" /> + <button + type="button" + class="action button-base" + @click="discussionForm.topics = discussionForm.topics.filter(t => t.topic_id !== topic.topic_id)" + :title="$gettext('Entfernen')" + :aria-label="$gettext('Ausgewähltes Thema entfernen')" + > + <StudipIcon shape="decline" :size="15" aria-hidden="true" /> </button> </div> <div v-if="discussionForm.type" class="badge"> - <StudipIcon :shape="discussionForm.type.icon" :size="15" /> + <StudipIcon :shape="discussionForm.type.icon" :size="15" aria-hidden="true" /> <span>{{ discussionForm.type.name }}</span> - <button @click="discussionForm.type = null" class="action"> - <StudipIcon shape="decline" :size="15" /> + <button + class="action button-base" + @click="discussionForm.type = null" + :title="$gettext('Entfernen')" + :aria-label="$gettext('Ausgewählten Diskussionstyp entfernen')" + > + <StudipIcon shape="decline" :size="15" aria-hidden="true" /> </button> </div> <template v-for="tag in discussionForm.tags" :key="tag"> <div class="badge"> <span>{{ '#'+tag.name }}</span> - <button @click="discussionForm.tags = discussionForm.tags.filter(t => t.name !== tag.name)" class="action"> - <StudipIcon shape="decline" :size="15" /> + <button + type="button" + class="action button-base" + @click="discussionForm.tags = discussionForm.tags.filter(t => t.name !== tag.name)" + :title="$gettext('Entfernen')" + :aria-label="$gettext('Ausgewähltes Schlagwort entfernen')" + > + <StudipIcon shape="decline" :size="15" aria-hidden="true" /> </button> </div> </template> diff --git a/resources/vue/apps/forum/discussions/Index.vue b/resources/vue/apps/forum/discussions/Index.vue index 7315a2a..97f5c4e 100644 --- a/resources/vue/apps/forum/discussions/Index.vue +++ b/resources/vue/apps/forum/discussions/Index.vue @@ -60,19 +60,35 @@ onMounted(async () => { </h2> <div class="mt-10 inline-flex gap-20 items-center"> - <span class="inline-flex gap-5 items-center" :title="$gettext('Anzahl der Teilnehmenden')" role="group"> + <span + role="group" + class="inline-flex gap-5 items-center" + :title="$gettext('Anzahl der Teilnehmenden')" + :aria-label="$gettext('Anzahl der Teilnehmenden')" + > <StudipIcon shape="community2" role="info" :size="15" aria-hidden="true" /> <span class="sr-only">{{ $gettext('Anzahl der Teilnehmenden') }}:</span> <small>{{ metadata.users_count }}</small> </span> - <span class="inline-flex gap-5 items-center" :title="$gettext('Anzahl der Beiträge')" role="group"> - <StudipIcon shape="reply" role="info" :size="15" aria-hidden="true"/> + + <span + role="group" + class="inline-flex gap-5 items-center" + :title="$gettext('Anzahl der Beiträge')" + :aria-label="$gettext('Anzahl der Beiträge')" + > + <StudipIcon shape="reply" role="info" :size="15" aria-hidden="true" /> <span class="sr-only">{{ $gettext('Anzahl der Beiträge') }}:</span> <small>{{ metadata.postings_count }}</small> </span> - <span class="inline-flex gap-5 items-center" :title="$gettext('Letzte Aktivität')" role="group"> + + <span + role="group" + class="inline-flex gap-5 items-center" + :title="$gettext('Letzte Aktivität')" + :aria-label="$gettext('Letzte Aktivität')" + > <StudipIcon shape="activity" role="info" :size="15" aria-hidden="true" /> - <span class="sr-only">{{ $gettext('Letzte Aktivität') }}:</span> <StudipDateTime v-if="metadata.recent_activity" :iso="metadata.recent_activity" :relative="true" /> <template v-else>{{ $gettext('Keine Aktivität') }}</template> </span> diff --git a/resources/vue/apps/forum/discussions/Show.vue b/resources/vue/apps/forum/discussions/Show.vue index 35c81f2..9b97197 100644 --- a/resources/vue/apps/forum/discussions/Show.vue +++ b/resources/vue/apps/forum/discussions/Show.vue @@ -157,22 +157,31 @@ onMounted(async () => { <template> <ForumApp id="discussion_start"> <header class="header"> - <div v-if="category.color" class="flag" :style="{ backgroundColor: category.color}"></div> + <div v-if="category.color" class="flag" :style="{ backgroundColor: category.color}" aria-hidden="true"></div> <div class="header__content header__content--with-actions items-start"> <div class="flex items-start gap-10"> - <a :href="goBackURL" :title="$gettext('Zum Thema')" class="go-back-link"> - <StudipIcon shape="arr_1left" :size="20" /> + <a + class="go-back-link" + :href="goBackURL" + :title="$gettext('Zum Thema')" + :aria-label="$gettext('Zum Thema')" + > + <StudipIcon shape="arr_1left" :size="20" aria-hidden="true" /> </a> <div> <ul class="breadcrumb"> <li> - <a :href="getTopicURL(discussion.topic_id)" :title="$gettext('Zum Thema')"> + <a + :href="getTopicURL(discussion.topic_id)" + :title="$gettext('Zum Thema')" + :aria-label="$gettext('Zum Thema: %{name}', { name: discussion.topic.name})" + > {{ discussion.topic.name }} </a> </li> <li> <div class="inline-flex items-start gap-5"> - <StudipIcon class="mt-1" v-if="discussion.sticky" role="info" shape="pin" :size="20" /> + <StudipIcon class="mt-1" v-if="discussion.sticky" role="info" shape="pin" :size="20" aria-hidden="true" /> {{ discussion.title }} </div> </li> @@ -180,7 +189,7 @@ onMounted(async () => { <ul class="mt-10 tags-container"> <li v-if="discussion.type.name" class="tags-container__tag"> - <StudipIcon role="info" :shape="discussion.type.icon" :size="15" :title="discussion.type.name"/> + <StudipIcon role="info" :shape="discussion.type.icon" :size="15" :title="discussion.type.name" aria-hidden="true" /> </li> <template v-for="tag in discussion.tags" :key="tag.id"> <li class="tags-container__tag"> @@ -193,6 +202,7 @@ onMounted(async () => { <div class="actions"> <div + role="status" v-if="discussion.closed_at" :title="$gettext('Diskussion ist geschlossen')" class="discussion-closed"> @@ -200,11 +210,18 @@ onMounted(async () => { {{ $gettext('Geschlossen:') }} <StudipDateTime :iso="discussion.closed_at" :relative="true" /> </em> - <StudipIcon shape="lock-locked2" :size="20" role="inactive" /> + <StudipIcon shape="lock-locked2" :size="20" role="inactive" aria-hidden="true" /> </div> <template v-if="!forumConfig.allowGuestAccess"> - <button v-if="canEditDiscussion" @click="editDiscussion(discussion.discussion_id)" type="button" :title="$gettext('Diskussion bearbeiten')" class="button button--icon-only"> - <StudipIcon shape="edit" :size="20" /> + <button + type="button" + class="button button--icon-only" + v-if="canEditDiscussion" + @click="editDiscussion(discussion.discussion_id)" + :title="$gettext('Bearbeiten bearbeiten')" + :aria-label="$gettext('Diskussion %{title} bearbeiten', { title: discussion.title })" + > + <StudipIcon shape="edit" :size="20" aria-hidden="true" /> </button> <SubscriptionDropdown v-if="!discussion.closed_at" diff --git a/resources/vue/apps/forum/discussions_types/Index.vue b/resources/vue/apps/forum/discussions_types/Index.vue index a5db3fc..d4db9bd 100644 --- a/resources/vue/apps/forum/discussions_types/Index.vue +++ b/resources/vue/apps/forum/discussions_types/Index.vue @@ -100,6 +100,7 @@ onMounted(() => { class="button button--icon-only" @click="addType" :title="$gettext('Neuen Diskussionstyp anlegen')" + :aria-label="$gettext('Neuen Diskussionstyp anlegen')" > <StudipIcon shape="add" aria-hidden="true" /> </button> @@ -114,8 +115,9 @@ onMounted(() => { <thead> <tr class="sortable"> - <th>{{ $gettext('Icon') }}</th> + <th scope="col">{{ $gettext('Icon') }}</th> <th + scope="col" :class="getSortClass('name')" :aria-sort="getAriaSortString('name')" :aria-label="getAriaSortLabel('name', $gettext('Name'))" @@ -124,12 +126,14 @@ onMounted(() => { type="button" class="as-link" @click="sortBy('name')" - :title="$gettext('Nach Name sortieren')"> + :title="$gettext('Nach Name sortieren')" + :aria-label="$gettext('Nach Name sortieren')" + > {{ $gettext('Name') }} </button> </th> - <th>{{ $gettext('Aktionen') }}</th> + <th scope="col">{{ $gettext('Aktionen') }}</th> </tr> </thead> <tbody> @@ -143,6 +147,7 @@ onMounted(() => { class="as-link" @click="editType(type.id)" :title="$gettext('Diskussionstyp bearbeiten')" + :aria-label="$gettext('Diskussionstyp bearbeiten')" > {{ type.name }} </button> diff --git a/resources/vue/apps/forum/search/Index.vue b/resources/vue/apps/forum/search/Index.vue index 42f9ce1..2dfb11a 100644 --- a/resources/vue/apps/forum/search/Index.vue +++ b/resources/vue/apps/forum/search/Index.vue @@ -192,58 +192,103 @@ onMounted(async () => { <h1>{{ $gettext('Suche') }}</h1> <div class="search-controls"> <div class="search-input-container"> - <input name="q" type="text" v-model="searchForm.keyword" :placeholder="$gettext('Diskussionen oder Beiträge')"/> + <label for="search-field" class="sr-only"> + {{ $gettext('Suchfeld') }} + </label> + <input id="search-field" name="q" type="text" v-model="searchForm.keyword" :placeholder="$gettext('Diskussionen oder Beiträge')"/> </div> <button type="submit" class="button button--icon-label" :title="$gettext('Suchen')" + :aria-label="$gettext('Suchen')" > <StudipIcon shape="search" :size="20" aria-hidden="true" /> {{ $gettext('Suchen') }} </button> - <button @click="resetSearchForm" type="button" class="button button--icon-only" :title="$gettext('Zurücksetzen')"> - <StudipIcon shape="decline" :size="20" /> + <button + type="button" + class="button button--icon-only" + @click="resetSearchForm" + :title="$gettext('Zurücksetzen')" + :aria-label="$gettext('Zurücksetzen')" + > + <StudipIcon shape="decline" :size="20" aria-hidden="true" /> </button> </div> <div class="filter-summary-container"> <template v-for="topic in searchForm.topics" :key="topic.topic_id"> <div class="badge"> - <a :href="getTopicURL(topic.topic_id)" :title="$gettext('Zum Thema')" target="_blank" class="flex gap-5 items-center"> - <span :style="{ backgroundColor: topic.color ?? '#EDEDED', height: '14px', width: '14px'}"></span> + <a + target="_blank" + class="flex gap-5 items-center" + :href="getTopicURL(topic.topic_id)" + :title="$gettext('Zum Thema')" + :aria-label="$gettext('Zum Thema: %{name}', { name: topic.name })" + > + <span :style="{ backgroundColor: topic.color ?? '#EDEDED', height: '14px', width: '14px'}" aria-hidden="true"></span> {{ topic.name }} </a> - <button @click="searchForm.topics = searchForm.topics.filter(t => t.topic_id !== topic.topic_id)" class="action"> - <StudipIcon shape="decline" :size="15" /> + <button + type="button" + class="action button-base" + @click="searchForm.topics = searchForm.topics.filter(t => t.topic_id !== topic.topic_id)" + :title="$gettext('Entfernen')" + :aria-label="$gettext('Ausgewähltes Thema entfernen')" + > + <StudipIcon shape="decline" :size="15" aria-hidden="true" /> </button> </div> </template> <template v-for="type in searchForm.types" :key="type.type_id"> <div class="badge" :title="type.name"> - <StudipIcon :shape="type.icon" :size="15" /> + <StudipIcon :shape="type.icon" :size="15" aria-hidden="true" /> <span>{{ type.name }}</span> - <button @click="searchForm.types = searchForm.types.filter(t => t.type_id !== type.type_id)" class="action"> - <StudipIcon shape="decline" :size="15" /> + <button + class="action button-base" + @click="searchForm.types = searchForm.types.filter(t => t.type_id !== type.type_id)" + :title="$gettext('Entfernen')" + :aria-label="$gettext('Ausgewählten Diskussionstyp entfernen')" + > + <StudipIcon shape="decline" :size="15" aria-hidden="true" /> </button> </div> </template> <template v-for="tag in searchForm.tags" :key="tag"> <div class="badge" :title="tag.name"> <span>{{ '#'+tag.name }}</span> - <button @click="searchForm.tags = searchForm.tags.filter(t => t.name !== tag.name)" class="action"> - <StudipIcon shape="decline" :size="15" /> + <button + type="button" + class="action button-base" + @click="searchForm.tags = searchForm.tags.filter(t => t.name !== tag.name)" + :title="$gettext('Entfernen')" + :aria-label="$gettext('Ausgewähltes Schlagwort entfernen')" + > + <StudipIcon shape="decline" :size="15" aria-hidden="true" /> </button> </div> </template> <template v-for="user in searchForm.authors" :key="user.user_id"> <div class="badge"> - <a :href="user.profile_url" target="_blank" :title="$gettext('Zum Nutzer Profile')" class="flex gap-5 items-center"> + <a + target="_blank" + class="flex gap-5 items-center" + :href="user.profile_url" + :title="$gettext('Zum Nutzer Profile')" + :aria-label="$gettext('Zum Nutzer Profile von %{name}', { name: user.name })" + > <img width="15px" height="15px" :src="user.avatar_url" :alt="user.name" /> {{ user.name }} </a> - <button @click="searchForm.authors = searchForm.authors.filter(u => u.name !== user.name)" class="action"> + <button + type="button" + class="action button-base" + @click="searchForm.authors = searchForm.authors.filter(u => u.name !== user.name)" + :title="$gettext('Entfernen')" + :aria-label="$gettext('Ausgewählte Autor/-in entfernen')" + > <StudipIcon shape="decline" :size="15" /> </button> </div> @@ -254,15 +299,16 @@ onMounted(async () => { <div> <button - @click="isFilterVisible = !isFilterVisible" type="button" class="toggle-filter-button button-base" + @click="isFilterVisible = !isFilterVisible" :title="isFilterVisible ? $gettext('Erweiterte Filter zuklappen') : $gettext('Erweiterte Filter aufklappen')" + :aria-label="isFilterVisible ? $gettext('Erweiterte Filter zuklappen') : $gettext('Erweiterte Filter aufklappen')" :aria-expanded="isFilterVisible.toString()" > {{ $gettext('Erweiterte Filter') }} - <StudipIcon v-if="isFilterVisible" shape="arr_1up" :size="20" /> - <StudipIcon v-else shape="arr_1down" :size="20" /> + <StudipIcon v-if="isFilterVisible" shape="arr_1up" :size="20" aria-hidden="true" /> + <StudipIcon v-else shape="arr_1down" :size="20" aria-hidden="true" /> </button> <div v-if="isFilterVisible" class="filter-controls"> <div> diff --git a/resources/vue/apps/forum/subscriptions/Index.vue b/resources/vue/apps/forum/subscriptions/Index.vue index 98798a3..bb22d01 100644 --- a/resources/vue/apps/forum/subscriptions/Index.vue +++ b/resources/vue/apps/forum/subscriptions/Index.vue @@ -104,61 +104,75 @@ onMounted(async () => { </colgroup> <thead> <tr class="sortable"> - <th - :class="getSortClass('subject.name')" - :aria-sort="getAriaSortString('subject.name')" - :aria-label="getAriaSortLabel('subject.name', $gettext('Thema Name'))" - > - <button - type="button" - class="as-link" - @click="sortBy('subject.name')" - :title="$gettext('Nach Thema Name sortieren')"> - {{ $gettext('Thema') }} - </button> - </th> - <th></th> - <th - :class="getSortClass('subject.type')" - :aria-sort="getAriaSortString('subject.type')" - :aria-label="getAriaSortLabel('subject.type', $gettext('Typ'))" - > - <button - type="button" - class="as-link" - @click="sortBy('subject.type')" - :title="$gettext('Nach Typ sortieren')"> - {{ $gettext('Typ') }} - </button> - </th> - <th - :class="getSortClass('mkdate')" - :aria-sort="getAriaSortString('mkdate')" - :aria-label="getAriaSortLabel('mkdate', $gettext('Abonniert datum'))" - > - <button - type="button" - class="as-link" - @click="sortBy('mkdate')" - :title="$gettext('Nach Abonniert am sortieren')"> - {{ $gettext('Abonniert am') }} - </button> - </th> - <th - class="actions" - :class="getSortClass('notification_type')" - :aria-sort="getAriaSortString('notification_type')" - :aria-label="getAriaSortLabel('notification_type', $gettext('Typ des Abonnements'))" - > - <button - type="button" - class="as-link" - @click="sortBy('notification_type')" - :title="$gettext('Nach Typ des Abonnements sortieren')"> - {{ $gettext('Typ des Abonnements') }} - </button> - </th> - </tr> + <th + scope="col" + :class="getSortClass('subject.name')" + :aria-sort="getAriaSortString('subject.name')" + :aria-label="getAriaSortLabel('subject.name', $gettext('Thema Name'))" + > + <button + type="button" + class="as-link" + @click="sortBy('subject.name')" + :title="$gettext('Nach Name des Thema sortieren')" + :aria-label="$gettext('Nach Name des Thema sortieren')" + > + {{ $gettext('Thema') }} + </button> + </th> + <th scope="col"> + <span class="sr-only">{{ $gettext('Status der Diskussion') }}</span> + </th> + <th + scope="col" + :class="getSortClass('subject.type')" + :aria-sort="getAriaSortString('subject.type')" + :aria-label="getAriaSortLabel('subject.type', $gettext('Typ'))" + > + <button + type="button" + class="as-link" + @click="sortBy('subject.type')" + :title="$gettext('Nach Typ sortieren')" + :aria-label="$gettext('Nach Typ sortieren')" + > + {{ $gettext('Typ') }} + </button> + </th> + <th + scope="col" + :class="getSortClass('mkdate')" + :aria-sort="getAriaSortString('mkdate')" + :aria-label="getAriaSortLabel('mkdate', $gettext('Abonniert datum'))" + > + <button + type="button" + class="as-link" + @click="sortBy('mkdate')" + :title="$gettext('Nach Abonniert am sortieren')" + :aria-label="$gettext('Nach Abonniert am sortieren')" + > + {{ $gettext('Abonniert am') }} + </button> + </th> + <th + scope="col" + class="actions" + :class="getSortClass('notification_type')" + :aria-sort="getAriaSortString('notification_type')" + :aria-label="getAriaSortLabel('notification_type', $gettext('Typ des Abonnements'))" + > + <button + type="button" + class="as-link" + @click="sortBy('notification_type')" + :title="$gettext('Nach Typ des Abonnements sortieren')" + :aria-label="$gettext('Nach Typ des Abonnements sortieren')" + > + {{ $gettext('Typ des Abonnements') }} + </button> + </th> + </tr> </thead> <tbody> <tr v-if="isLoading" > @@ -167,56 +181,63 @@ onMounted(async () => { </td> </tr> <tr v-else v-for="subscription in sortedData" :key="subscription.id"> - <td> - <div class="table-row-overview"> - <div class="title-with-actions"> - <div class="title-with-actions__content"> - <a v-if="subscription.subject.type === 'forum-topics'" :href="getTopicURL(subscription.subject.id)" :title="$gettext('Zum Thema')"> - <span class="subscription-title as-link line-clamp-2">{{ subscription.subject.name }}</span> - </a> - <a v-else-if="subscription.subject.type === 'forum-discussions'" :href="getDiscussionURL(subscription.subject.id)" :title="$gettext('Zur Diskussion')"> - <span class="subscription-title as-link line-clamp-2">{{ subscription.subject.title }}</span> - </a> - </div> + <td> + <div class="table-row-overview"> + <div class="title-with-actions"> + <div class="title-with-actions__content"> + <a v-if="subscription.subject.type === 'forum-topics'" :href="getTopicURL(subscription.subject.id)" :title="$gettext('Zum Thema')"> + <span class="subscription-title as-link line-clamp-2">{{ subscription.subject.name }}</span> + </a> + <a v-else-if="subscription.subject.type === 'forum-discussions'" :href="getDiscussionURL(subscription.subject.id)" :title="$gettext('Zur Diskussion')"> + <span class="subscription-title as-link line-clamp-2">{{ subscription.subject.title }}</span> + </a> + </div> - <div class="title-with-actions__actions-xs"> - <SubscriptionDropdown - :type="getSubscriptionDropdownTitle(subscription.subject.type)" - :subject="subscription.subject" - :subject_id="subscription.subject_id" - :userSubscription="subscription" - @deleted="removeSubscription(subscription.id)" - /> + <div class="title-with-actions__actions-xs"> + <SubscriptionDropdown + :type="getSubscriptionDropdownTitle(subscription.subject.type)" + :subject="subscription.subject" + :subject_id="subscription.subject_id" + :userSubscription="subscription" + @deleted="removeSubscription(subscription.id)" + /> + </div> </div> </div> - </div> - </td> - <td> - <StudipIcon - v-if="subscription.subject.type === 'forum-discussions' && subscription.subject.closed_at" - :title="$gettext('Diskussion ist geschlossen')" - shape="lock-locked2" - :size="20" - role="inactive" - /> - </td> - <td> - {{ getSubjectLabel(subscription.subject.type) }} - </td> - <td> - <StudipDateTime :iso="subscription.mkdate" :relative="true" /> - </td> - <td class="actions"> - <div class="inline-flex"> - <SubscriptionDropdown - :type="getSubscriptionDropdownTitle(subscription.subject.type)" - :subject="subscription.subject" - :userSubscription="subscription" - @deleted="removeSubscription(subscription.id)" - /> - </div> - </td> - </tr> + </td> + <td> + <template v-if="subscription.subject.type === 'forum-discussions'"> + + <StudipIcon + v-if="subscription.subject.closed_at" + :title="$gettext('Diskussion ist geschlossen')" + shape="lock-locked2" + :size="20" + role="inactive" + aria-hidden="true" + /> + <span role="status" class="sr-only"> + {{ subscription.subject.closed_at ? $gettext('Diskussion ist geschlossen') : $gettext('Diskussion ist offen') }} + </span> + </template> + </td> + <td> + {{ getSubjectLabel(subscription.subject.type) }} + </td> + <td> + <StudipDateTime :iso="subscription.mkdate" :relative="true" /> + </td> + <td class="actions"> + <div class="inline-flex"> + <SubscriptionDropdown + :type="getSubscriptionDropdownTitle(subscription.subject.type)" + :subject="subscription.subject" + :userSubscription="subscription" + @deleted="removeSubscription(subscription.id)" + /> + </div> + </td> + </tr> </tbody> <tfoot v-if="pagination.total > pagination.limit"> <tr> diff --git a/resources/vue/apps/forum/topics/Edit.vue b/resources/vue/apps/forum/topics/Edit.vue index ac96e93..e7b84cd 100644 --- a/resources/vue/apps/forum/topics/Edit.vue +++ b/resources/vue/apps/forum/topics/Edit.vue @@ -76,11 +76,11 @@ onMounted(() => { <section> <input type="hidden" name="category" :value="JSON.stringify(topicForm.category)"> - <label for="category_input"> + <label for="category-input"> {{ $gettext('Kategorie') }} </label> <StudipSelect - id="category_input" + id="category-input" label="name" :options="categories" v-model="topicForm.category" @@ -95,13 +95,13 @@ onMounted(() => { > <template #selected-option="{name, color}"> <div class="flex items-center"> - <span v-if="color" :style="{ backgroundColor: color, height: '14px', width: '14px', marginRight: '8px'}"></span> + <span v-if="color" :style="{ backgroundColor: color, height: '14px', width: '14px', marginRight: '8px'}" aria-hidden="true"></span> <span class="line-clamp-1 flex-1">{{name}}</span> </div> </template> <template #option="{name, color}"> <div :style="{ display: 'flex', alignItems: 'center' }"> - <span v-if="color" :style="{ backgroundColor: color, height: '14px', width: '14px', marginRight: '8px'}"></span> + <span v-if="color" :style="{ backgroundColor: color, height: '14px', width: '14px', marginRight: '8px'}" aria-hidden="true"></span> <span :style="{ flex: '1'}" class="line-clamp-1">{{name}}</span> </div> </template> diff --git a/resources/vue/apps/forum/topics/Index.vue b/resources/vue/apps/forum/topics/Index.vue index 1fc4ee9..faa804b 100644 --- a/resources/vue/apps/forum/topics/Index.vue +++ b/resources/vue/apps/forum/topics/Index.vue @@ -65,20 +65,24 @@ onMounted(async () => { <div class="actions"> <CreateTopic /> <button + type="button" + class="button button--icon-only" v-if="forumConfig.tileLayout" @click="forumConfig.toggleForumLayout();" :title="$gettext('Tabellarische Ansicht')" - type="button" - class="button button--icon-only"> - <StudipIcon shape="view-list" :size="20" /> + :aria-label="$gettext('Tabellarische Ansicht')" + > + <StudipIcon shape="view-list" :size="20" aria-hidden="true" /> </button> <button v-else + type="button" + class="button button--icon-only" @click="forumConfig.toggleForumLayout();" :title="$gettext('Kachelansicht')" - type="button" - class="button button--icon-only"> - <StudipIcon shape="view-wall" :size="20" /> + :aria-label="$gettext('Kachelansicht')" + > + <StudipIcon shape="view-wall" :size="20" aria-hidden="true" /> </button> <div aria-live="polite" class="sr-only" role="status">{{ toggleLayoutMessage }}</div> </div> diff --git a/resources/vue/apps/forum/topics/Show.vue b/resources/vue/apps/forum/topics/Show.vue index 37952c5..0fcbd9c 100644 --- a/resources/vue/apps/forum/topics/Show.vue +++ b/resources/vue/apps/forum/topics/Show.vue @@ -81,19 +81,34 @@ onMounted(async () => { </ul> <div class="mt-10 inline-flex gap-20 items-center"> - <span class="inline-flex gap-5 items-center" :title="$gettext('Anzahl der Teilnehmenden am Thema')" role="group"> + <span + role="group" + class="inline-flex gap-5 items-center" + :title="$gettext('Anzahl der Teilnehmenden am Thema')" + :aria-label="$gettext('Anzahl der Teilnehmenden am Thema')" + > <StudipIcon shape="community2" role="info" :size="15" aria-hidden="true" /> <span class="sr-only">{{ $gettext('Anzahl der Teilnehmenden am Thema') }}:</span> <small>{{ metadata.users_count }}</small> </span> - <span class="inline-flex gap-5 items-center" :title="$gettext('Anzahl der Beiträge')" role="group"> - <StudipIcon shape="reply" role="info" :size="15" aria-hidden="true"/> + + <span + role="group" + class="inline-flex gap-5 items-center" + :title="$gettext('Anzahl der Beiträge')" + :aria-label="$gettext('Anzahl der Beiträge')" + > + <StudipIcon shape="reply" role="info" :size="15" aria-hidden="true" /> <span class="sr-only">{{ $gettext('Anzahl der Beiträge') }}:</span> <small>{{ metadata.postings_count }}</small> </span> - <span class="inline-flex gap-5 items-center" :title="$gettext('Letzte Aktivität')" role="group"> + <span + role="group" + class="inline-flex gap-5 items-center" + :title="$gettext('Letzte Aktivität')" + :aria-label="$gettext('Letzte Aktivität')" + > <StudipIcon shape="activity" role="info" :size="15" aria-hidden="true" /> - <span class="sr-only">{{ $gettext('Letzte Aktivität') }}:</span> <StudipDateTime v-if="metadata.recent_activity" :iso="metadata.recent_activity" :relative="true" /> <template v-else>{{ $gettext('Keine Aktivität') }}</template> </span> diff --git a/resources/vue/components/forum/EmptyForum.vue b/resources/vue/components/forum/EmptyForum.vue index 12f0709..3a09aa6 100644 --- a/resources/vue/components/forum/EmptyForum.vue +++ b/resources/vue/components/forum/EmptyForum.vue @@ -39,7 +39,6 @@ const addDiscussion = () => { type="button" class="button button--icon-label" @click="openTour('ea68d2f9d7b81d01d2d3ea38a105c734')" - :title="$gettext('Tour ansehen')" > <StudipIcon shape="lightbulb" :size="20" aria-hidden="true" /> {{ $gettext('Tour ansehen') }} @@ -48,7 +47,6 @@ const addDiscussion = () => { type="button" class="button button--icon-label" @click="addDiscussion" - :title="$gettext('Eine Diskussion starten')" > <StudipIcon shape="add" :size="20" aria-hidden="true" /> {{ $gettext('Eine Diskussion starten') }} diff --git a/resources/vue/components/forum/SelectTagsInput.vue b/resources/vue/components/forum/SelectTagsInput.vue index 255b0b5..e6f3ecc 100644 --- a/resources/vue/components/forum/SelectTagsInput.vue +++ b/resources/vue/components/forum/SelectTagsInput.vue @@ -16,7 +16,7 @@ import StudipSelect from '@/vue/components/StudipSelect.vue'; return { name: tag }; }" :closeOnSelect="false" - v-bind="{...$props, ...$attrs}" + v-bind="$attrs" > <template #selected-option="{name}"> <span>{{ name }}</span> diff --git a/resources/vue/components/forum/SelectUserInput.vue b/resources/vue/components/forum/SelectUserInput.vue index d996b84..d5fe742 100644 --- a/resources/vue/components/forum/SelectUserInput.vue +++ b/resources/vue/components/forum/SelectUserInput.vue @@ -7,7 +7,7 @@ import StudipSelect from '@/vue/components/StudipSelect.vue'; <StudipSelect label="name" :placeholder="$gettext('Autor/-in')" - v-bind="{...$props, ...$attrs}" + v-bind="$attrs" > <template #selected-option="{name, avatar_url}"> <div class="flex items-center"> diff --git a/resources/vue/components/forum/categories/CategoryItem.vue b/resources/vue/components/forum/categories/CategoryItem.vue index e2d524e..e20a249 100644 --- a/resources/vue/components/forum/categories/CategoryItem.vue +++ b/resources/vue/components/forum/categories/CategoryItem.vue @@ -75,7 +75,7 @@ const swapCategory = event => { class="drag-link styleless" @keydown="swapCategory" :title="$gettext('Sortierelement für Element %{name}. Drücken Sie die Tasten Pfeil-nach-oben oder Pfeil-nach-unten, um dieses Element in der Liste zu verschieben.', {name: category.name})" - + :aria-label="$gettext('Sortierelement für Element %{name}. Drücken Sie die Tasten Pfeil-nach-oben oder Pfeil-nach-unten, um dieses Element in der Liste zu verschieben.', {name: category.name})" > <span class="drag-handle"></span> </button> @@ -88,18 +88,20 @@ const swapCategory = event => { <a class="title-with-actions__link" :href="getCategoryURL(category.id)" - :title="$gettext('Zur Kategorie')"> + :title="$gettext('Zur Kategorie')" + :aria-label="$gettext('Zur Kategorie: %{name}', {name: category.name})" + > <span class="category-title line-clamp-2">{{ category.name }}</span> <span v-if="!forumConfig.allowGuestAccess && category.meta.unread_postings_count" class="unread-items-badge" role="status" aria-live="polite" - :aria-label="$gettext('Sie haben %{count} ungelesene Beiträge', {count: category.meta.unread_postings_count})" :title="$gettext('Sie haben %{count} ungelesene Beiträge', {count: category.meta.unread_postings_count})" + :aria-label="$gettext('Sie haben %{count} ungelesene Beiträge', {count: category.meta.unread_postings_count})" > - {{ category.meta.unread_postings_count }} - </span> + {{ category.meta.unread_postings_count }} + </span> </a> </div> @@ -125,7 +127,7 @@ const swapCategory = event => { <dl> <dt>{{ $gettext('Anzahl der Teilnehmenden in der Kategorie') }}</dt> <dd class="inline-flex gap-5 items-center"> - <StudipIcon shape="community2" role="info" :size="15" /> + <StudipIcon shape="community2" role="info" :size="15" aria-hidden="true" /> {{ category.meta.users_count }} </dd> </dl> @@ -133,19 +135,19 @@ const swapCategory = event => { <dl> <dt>{{ $gettext('Anzahl der Diskussionen') }}</dt> <dd class="inline-flex gap-5 items-center"> - <StudipIcon shape="forum" role="info" :size="15" /> + <StudipIcon shape="forum" role="info" :size="15" aria-hidden="true" /> {{ category.meta.discussions_count }} </dd> <dt>{{ $gettext('Anzahl der Beiträge') }}</dt> <dd class="inline-flex gap-5 items-center"> - <StudipIcon shape="reply" role="info" :size="15" /> + <StudipIcon shape="reply" role="info" :size="15" aria-hidden="true" /> {{ category.meta.postings_count }} </dd> <dt>{{ $gettext('Letzte Aktivität') }}</dt> <dd class="inline-flex gap-5 items-center"> - <StudipIcon shape="activity" role="info" :size="15" /> + <StudipIcon shape="activity" role="info" :size="15" aria-hidden="true" /> <StudipDateTime v-if="category.meta.recent_activity" :iso="category.meta.recent_activity" :relative="true" /> <template v-else>{{ $gettext('Keine Aktivität') }}</template> </dd> @@ -157,23 +159,38 @@ const swapCategory = event => { {{ category.meta.discussions_count }} {{ $gettext('Diskussion') }} </td> <td> - <span class="inline-flex gap-10 items-center" :title="$gettext('Anzahl der Teilnehmenden in der Kategorie')" :aria-label="$gettext('Anzahl der Teilnehmenden in der Kategorie')" role="group"> - <StudipIcon shape="community2" role="info" :size="20" aria-hidden="true" /> - <span>{{ category.meta.users_count }}</span> - </span> + <span + role="group" + class="inline-flex gap-10 items-center" + :title="$gettext('Anzahl der Teilnehmenden in der Kategorie')" + :aria-label="$gettext('Anzahl der Teilnehmenden in der Kategorie')" + > + <StudipIcon shape="community2" role="info" :size="20" aria-hidden="true" /> + <span>{{ category.meta.users_count }}</span> + </span> </td> <td> - <span class="inline-flex gap-10 items-center" :title="$gettext('Anzahl der Beiträge')" :aria-label="$gettext('Anzahl der Beiträge')" role="group"> - <StudipIcon shape="reply" role="info" :size="20" aria-hidden="true" /> - <span>{{ category.meta.postings_count }}</span> - </span> + <span + role="group" + class="inline-flex gap-10 items-center" + :title="$gettext('Anzahl der Beiträge')" + :aria-label="$gettext('Anzahl der Beiträge')" + > + <StudipIcon shape="reply" role="info" :size="20" aria-hidden="true" /> + <span>{{ category.meta.postings_count }}</span> + </span> </td> <td> - <span class="inline-flex gap-10 items-center nowrap" :title="$gettext('Letzte Aktivität')" :aria-label="$gettext('Letzte Aktivität')" role="group"> - <StudipIcon shape="activity" role="info" :size="20" aria-hidden="true"/> - <StudipDateTime v-if="category.meta.recent_activity" :iso="category.meta.recent_activity" :relative="true" /> - <template v-else>{{ $gettext('Keine Aktivität') }}</template> - </span> + <span + role="group" + class="inline-flex gap-10 items-center nowrap" + :title="$gettext('Letzte Aktivität')" + :aria-label="$gettext('Letzte Aktivität')" + > + <StudipIcon shape="activity" role="info" :size="20" aria-hidden="true"/> + <StudipDateTime v-if="category.meta.recent_activity" :iso="category.meta.recent_activity" :relative="true" /> + <template v-else>{{ $gettext('Keine Aktivität') }}</template> + </span> </td> <td class="actions"> <StudipActionMenu @@ -212,15 +229,15 @@ const swapCategory = event => { </span> <span - v-if="!forumConfig.allowGuestAccess && category.meta.unread_postings_count" - class="unread-items-badge" role="status" aria-live="polite" - :aria-label="$gettext('Sie haben %{count} ungelesene Beiträge', {count: category.meta.unread_postings_count})" + class="unread-items-badge" + v-if="!forumConfig.allowGuestAccess && category.meta.unread_postings_count" :title="$gettext('Sie haben %{count} ungelesene Beiträge', {count: category.meta.unread_postings_count})" + :aria-label="$gettext('Sie haben %{count} ungelesene Beiträge', {count: category.meta.unread_postings_count})" > - {{ category.meta.unread_postings_count }} - </span> + {{ category.meta.unread_postings_count }} + </span> </div> <div class="actions"> <StudipActionMenu @@ -240,30 +257,44 @@ const swapCategory = event => { <div v-if="forumConfig.isModerator" class="drag-area"> <button type="button" - :id="`sort-handle-${category.id}`" class="drag-link styleless" + :id="`sort-handle-${category.id}`" @keydown="swapCategory" :title="$gettext('Sortierelement für Element %{name}. Drücken Sie die Tasten Pfeil-nach-oben oder Pfeil-nach-unten, um dieses Element in der Liste zu verschieben.', {name: category.name})" + :aria-label="$gettext('Sortierelement für Element %{name}. Drücken Sie die Tasten Pfeil-nach-oben oder Pfeil-nach-unten, um dieses Element in der Liste zu verschieben.', {name: category.name})" > <span class="drag-handle"></span> </button> </div> <div class="topic-card__footer"> - <span class="inline-flex gap-10 items-center" :title="$gettext('Anzahl der Teilnehmenden in der Kategorie')" :aria-label="$gettext('Anzahl der Teilnehmenden in der Kategorie')" role="group"> - <StudipIcon shape="community2" role="info" :size="15" aria-hidden="true"/> - <small>{{ category.meta.users_count }}</small> - </span> - <span class="inline-flex gap-10 items-center" :title="$gettext('Anzahl der Beiträge')" :aria-label="$gettext('Anzahl der Beiträge')" role="group"> - <StudipIcon shape="reply" role="info" :size="15" aria-hidden="true"/> - <small>{{ category.meta.postings_count }}</small> - </span> - <span class="inline-flex gap-10 items-center" :title="$gettext('Letzte Aktivität')" :aria-label="$gettext('Letzte Aktivität')" role="group"> - <StudipIcon shape="activity" role="info" :size="15" aria-hidden="true"/> - <small v-if="category.meta.recent_activity"> - <StudipDateTime :iso="category.meta.recent_activity" :relative="true" /> - </small> - <small v-else>{{ $gettext('Keine Aktivität') }}</small> - </span> + <span + class="inline-flex gap-10 items-center" + :title="$gettext('Anzahl der Teilnehmenden in der Kategorie')" + :aria-label="$gettext('Anzahl der Teilnehmenden in der Kategorie')" + > + <StudipIcon shape="community2" role="info" :size="15" aria-hidden="true" /> + <small>{{ category.meta.users_count }}</small> + </span> + <span + class="inline-flex gap-10 items-center" + :title="$gettext('Anzahl der Beiträge')" + :aria-label="$gettext('Anzahl der Beiträge')" + > + <StudipIcon shape="reply" role="info" :size="15" aria-hidden="true" /> + <small>{{ category.meta.postings_count }}</small> + </span> + <span + role="group" + class="inline-flex gap-10 items-center" + :title="$gettext('Letzte Aktivität')" + :aria-label="$gettext('Letzte Aktivität')" + > + <StudipIcon shape="activity" role="info" :size="15" aria-hidden="true" /> + <small v-if="category.meta.recent_activity"> + <StudipDateTime :iso="category.meta.recent_activity" :relative="true" /> + </small> + <small v-else>{{ $gettext('Keine Aktivität') }}</small> + </span> </div> </div> </div> diff --git a/resources/vue/components/forum/categories/Create.vue b/resources/vue/components/forum/categories/Create.vue index 98693ba..755ef27 100644 --- a/resources/vue/components/forum/categories/Create.vue +++ b/resources/vue/components/forum/categories/Create.vue @@ -26,11 +26,12 @@ const addCategory = () => { <template> <button - v-if="forumConfig.isModerator" type="button" class="button" + v-if="forumConfig.isModerator" @click="addCategory" :title="$gettext('Neue Kategorie anlegen')" + :aria-label="$gettext('Neue Kategorie anlegen')" :class="label ? 'button--icon-label' : 'button--icon-only'" > <StudipIcon shape="add" :size="20" aria-hidden="true" /> diff --git a/resources/vue/components/forum/discussions/Create.vue b/resources/vue/components/forum/discussions/Create.vue index 568041b..2a41029 100644 --- a/resources/vue/components/forum/discussions/Create.vue +++ b/resources/vue/components/forum/discussions/Create.vue @@ -29,11 +29,12 @@ const addDiscussion = () => { <template> <button - v-if="!forumConfig.allowGuestAccess" type="button" + class="button button--icon-only" + v-if="!forumConfig.allowGuestAccess" @click="addDiscussion" :title="$gettext('Neue Diskussion starten')" - class="button button--icon-only" + :aria-label="$gettext('Neue Diskussion starten')" > <StudipIcon shape="add" :size="20" aria-hidden="true" /> </button> diff --git a/resources/vue/components/forum/discussions/DiscussionIndex.vue b/resources/vue/components/forum/discussions/DiscussionIndex.vue index 446565c..bee9983 100644 --- a/resources/vue/components/forum/discussions/DiscussionIndex.vue +++ b/resources/vue/components/forum/discussions/DiscussionIndex.vue @@ -90,6 +90,7 @@ onMounted(() => { <thead> <tr class="sortable"> <th + scope="col" :class="getSortClass('title')" :aria-sort="getAriaSortString('title')" :aria-label="getAriaSortLabel('title', $gettext('Diskussionstitel'))" @@ -103,6 +104,7 @@ onMounted(() => { </button> </th> <th + scope="col" :class="getSortClass('members')" :aria-sort="getAriaSortString('members')" :aria-label="getAriaSortLabel('members', $gettext('Anzahl der Teilnehmenden'))" @@ -116,6 +118,7 @@ onMounted(() => { </button> </th> <th + scope="col" :class="getSortClass('meta.postings_count')" :aria-sort="getAriaSortString('meta.postings_count')" :aria-label="getAriaSortLabel('meta.postings_count', $gettext('Anzahl der Beiträge'))" @@ -129,6 +132,7 @@ onMounted(() => { </button> </th> <th + scope="col" :class="getSortClass('view_count')" :aria-sort="getAriaSortString('view_count')" :aria-label="getAriaSortLabel('view_count', $gettext('Anzahl der Aufrufe'))" @@ -142,6 +146,7 @@ onMounted(() => { </button> </th> <th + scope="col" :class="getSortClass('meta.recent_activity')" :aria-sort="getAriaSortString('meta.recent_activity')" :aria-label="getAriaSortLabel('meta.recent_activity', $gettext('Letzte Aktivität'))" @@ -154,8 +159,12 @@ onMounted(() => { {{ $gettext('Letzte Aktivität') }} </button> </th> - <th></th> - <th v-if="withActions"></th> + <th scope="col"> + <span class="sr-only">{{ $gettext('Status') }}</span> + </th> + <th scope="col" v-if="withActions"> + <span class="sr-only">{{ $gettext('Aktionen') }}</span> + </th> </tr> </thead> <tbody> @@ -236,32 +245,37 @@ onMounted(() => { <dl> <dt>{{ $gettext('Aufrufe') }}</dt> <dd class="inline-flex gap-5 items-center"> - <StudipIcon shape="block-eyecatcher" :size="15" role="info" /> + <StudipIcon shape="block-eyecatcher" :size="15" role="info" aria-hidden="true" /> {{ numberFormatter(discussion.view_count, 1) }} </dd> <dt>{{ $gettext('Anzahl der Beiträge') }}</dt> <dd class="inline-flex gap-5 items-center"> - <StudipIcon shape="reply" :size="15" role="info" /> + <StudipIcon shape="reply" :size="15" role="info" aria-hidden="true" /> {{ discussion.meta.postings_count }} </dd> <dt>{{ $gettext('Letzte Aktivität') }}</dt> <dd class="inline-flex gap-5 items-center"> - <StudipIcon shape="activity" :size="15" role="info" /> + <StudipIcon shape="activity" :size="15" role="info" aria-hidden="true" /> <StudipDateTime :iso="discussion.meta.recent_activity" :relative="true" /> </dd> <dt>{{ $gettext('Ist geschlossen') }}</dt> <dd - v-if="discussion.closed_at" class="inline-flex gap-5 items-center" > <StudipIcon + v-if="discussion.closed_at" :title="$gettext('Diskussion ist geschlossen')" shape="lock-locked2" :size="15" - role="inactive" /> + role="inactive" + aria-hidden="true" + /> + <span class="sr-only"> + {{ discussion.closed_at ? $gettext('Diskussion ist geschlossen') : $gettext('Diskussion ist offen') }} + </span> </dd> </dl> @@ -293,7 +307,12 @@ onMounted(() => { :title="$gettext('Diskussion ist geschlossen')" shape="lock-locked2" :size="20" + aria-hidden="true" role="inactive" /> + + <span role="status" class="sr-only"> + {{ discussion.closed_at ? $gettext('Diskussion ist geschlossen') : $gettext('Diskussion ist offen') }} + </span> </td> <td v-if="withActions" class="actions"> <StudipActionMenu diff --git a/resources/vue/components/forum/discussions/SelectDiscussionType.vue b/resources/vue/components/forum/discussions/SelectDiscussionType.vue index 32f1bff..dfee360 100644 --- a/resources/vue/components/forum/discussions/SelectDiscussionType.vue +++ b/resources/vue/components/forum/discussions/SelectDiscussionType.vue @@ -8,17 +8,17 @@ import StudipSelect from '@/vue/components/StudipSelect.vue'; <StudipSelect :placeholder="$gettext('Diskussionstyp (Optional)')" label="name" - v-bind="{...$props, ...$attrs}" + v-bind="$attrs" > <template #selected-option="{name, icon}"> <div class="flex items-center"> - <StudipIcon :shape="icon" :size="18" :style="{ marginRight: '8px'}"/> + <StudipIcon :shape="icon" :size="18" :style="{ marginRight: '8px'}" aria-hidden="true" /> <span class="line-clamp-1 flex-1">{{name}}</span> </div> </template> <template #option="{name, icon}"> <div :style="{ display: 'flex', alignItems: 'center' }"> - <StudipIcon :shape="icon" :size="18" :style="{ marginRight: '8px'}"/> + <StudipIcon :shape="icon" :size="18" :style="{ marginRight: '8px'}" aria-hidden="true" /> <span :style="{ flex: '1'}" class="line-clamp-1">{{name}}</span> </div> </template> diff --git a/resources/vue/components/forum/posts/Post.vue b/resources/vue/components/forum/posts/Post.vue index 26bba20..8f0baf2 100644 --- a/resources/vue/components/forum/posts/Post.vue +++ b/resources/vue/components/forum/posts/Post.vue @@ -158,6 +158,7 @@ onBeforeUnmount(() => postObserver.disconnect()); @click="removePostHighlight(`post_${post.id}`)" > <div v-if="!forumConfig.allowGuestAccess && isUnread" class="post__unread"> + <span class="sr-only">{{ $gettext('Ungelesen') }}</span> </div> <div class="post__body"> <div class="post__author"> diff --git a/resources/vue/components/forum/posts/PostCreateForm.vue b/resources/vue/components/forum/posts/PostCreateForm.vue index ff1878c..2c66bdb 100644 --- a/resources/vue/components/forum/posts/PostCreateForm.vue +++ b/resources/vue/components/forum/posts/PostCreateForm.vue @@ -118,8 +118,8 @@ const storePost = async () => { <form @submit.prevent="storePost" class="default post-form forum-quote"> <div class="post-form__author"> <a - :href="userProfileURL(authUser.username)" class="post-form__author-image profile-image-container" + :href="userProfileURL(authUser.username)" :title="$gettext('Zum Profil')" :aria-label="$gettext('Zum Profil von %{name}', { name: authUser.name })" > @@ -127,7 +127,10 @@ const storePost = async () => { </a> <p class="post-form__author-name">{{ authUser.name }}</p> </div> - <StudipWysiwyg :required="true" v-model="content" /> + <label for="post-content"> + <span class="sr-only">{{ $gettext('Inhalt') }}</span> + </label> + <StudipWysiwyg id="post-content" :required="true" v-model="content" /> <div v-if="forumConfig.anonymousPost" class="mt-10"> <StudipSwitch name="anonymous" v-model="anonymous" :label="$gettext('Anonym')" /> </div> @@ -136,8 +139,6 @@ const storePost = async () => { type="submit" :disabled="isLoading || !content" class="button button--icon-label" - :title="$gettext('Speichern')" - :aria-label="$gettext('Speichern')" > <StudipIcon shape="accept" :size="20" aria-hidden="true" /> {{ $gettext('Speichern') }} @@ -145,11 +146,9 @@ const storePost = async () => { <button type="button" class="button button--icon-label" - :title="$gettext('Abbrechen')" - :aria-label="$gettext('Abbrechen')" @click="$emit('canceled')" > - <StudipIcon shape="decline" :size="20" aria-hidden="true"/> + <StudipIcon shape="decline" :size="20" aria-hidden="true" /> {{ $gettext('Abbrechen') }} </button> </div> diff --git a/resources/vue/components/forum/posts/PostEditForm.vue b/resources/vue/components/forum/posts/PostEditForm.vue index cd16d7a..5632e19 100644 --- a/resources/vue/components/forum/posts/PostEditForm.vue +++ b/resources/vue/components/forum/posts/PostEditForm.vue @@ -81,17 +81,19 @@ onUnmounted(() => { <template> <form @submit.prevent="updatePost" class="default post-form forum-quote"> - <StudipWysiwyg required="required" v-model="content" /> + <label :for="`post-content-${post.id}`"> + <span class="sr-only">{{ $gettext('Inhalt') }}</span> + </label> + <StudipWysiwyg :id="`post-content-${post.id}`" required="required" v-model="content" /> <div v-if="forumConfig.anonymousPost" class="mt-10"> <StudipSwitch name="anonymous" v-model="anonymous" :label="$gettext('Anonym')" /> </div> <div class="post-form__footer"> <button type="submit" - :disabled="isLoading || !content" class="button button--icon-label" + :disabled="isLoading || !content" :value="$gettext('Speichern')" - :title="$gettext('Speichern')" > <StudipIcon shape="accept" :size="20" aria-hidden="true" /> {{ $gettext('Speichern') }} @@ -99,7 +101,6 @@ onUnmounted(() => { <button type="button" class="button button--icon-label" - :title="$gettext('Abbrechen')" @click="$emit('canceled')" > <StudipIcon shape="decline" :size="20" aria-hidden="true"/> diff --git a/resources/vue/components/forum/posts/PostReactionShow.vue b/resources/vue/components/forum/posts/PostReactionShow.vue index 400fa4e..73cc02c 100644 --- a/resources/vue/components/forum/posts/PostReactionShow.vue +++ b/resources/vue/components/forum/posts/PostReactionShow.vue @@ -48,8 +48,11 @@ onMounted(() => { </colgroup> <thead> <tr class="sortable"> - <th></th> + <th scope="col"> + <span class="sr-only">{{ $gettext('Benutzer') }}</span> + </th> <th + scope="col" :class="getSortClass('user.formatted_name')" :aria-sort="getAriaSortString('user.formatted_name')" :aria-label="getAriaSortLabel('user.formatted_name', $gettext('Name'))" @@ -58,11 +61,14 @@ onMounted(() => { type="button" class="as-link" @click="sortBy('user.formatted_name')" - :title="$gettext('Nach Name sortieren')"> + :title="$gettext('Nach Name sortieren')" + :aria-label="$gettext('Nach Name sortieren')" + > {{ $gettext('Name') }} </button> </th> <th + scope="col" :class="getSortClass('mkdate')" :aria-sort="getAriaSortString('mkdate')" :aria-label="getAriaSortLabel('mkdate', $gettext('Datum'))" @@ -71,7 +77,9 @@ onMounted(() => { type="button" class="as-link" @click="sortBy('mkdate')" - :title="$gettext('Nach Datum sortieren')"> + :title="$gettext('Nach Datum sortieren')" + :aria-label="$gettext('Nach Datum sortieren')" + > {{ $gettext('Datum') }} </button> </th> diff --git a/resources/vue/components/forum/posts/PostReactions.vue b/resources/vue/components/forum/posts/PostReactions.vue index 58af213..2d97ae4 100644 --- a/resources/vue/components/forum/posts/PostReactions.vue +++ b/resources/vue/components/forum/posts/PostReactions.vue @@ -146,7 +146,7 @@ const reactionShowDialog = reactive({ :aria-label="$gettext('Reagieren')" :aria-pressed="showReactions" @click="showReactions = !showReactions"> - <StudipIcon shape="add-reaction" class="add-reaction-icon" :size="18" /> + <StudipIcon shape="add-reaction" class="add-reaction-icon" :size="18" aria-hidden="true" /> </button> <button v-if="transformedReactions.length" @@ -170,7 +170,7 @@ const reactionShowDialog = reactive({ :aria-label="$gettext('Auf diesen Beitrag mit %{emojiName} reagieren', { emojiName: emoji.value })" @click="toggleReaction(emoji.value)" > - <span class="emoji-icon" v-html="emoji.icon"></span> + <span class="emoji-icon" v-html="emoji.icon" aria-hidden="true"></span> </button> </template> </div> diff --git a/resources/vue/components/forum/topics/CreateTopic.vue b/resources/vue/components/forum/topics/CreateTopic.vue index 688c4a8..d92efa2 100644 --- a/resources/vue/components/forum/topics/CreateTopic.vue +++ b/resources/vue/components/forum/topics/CreateTopic.vue @@ -36,11 +36,12 @@ const addTopic = () => { <template> <button - v-if="forumConfig.isModerator" type="button" class="button button--icon-only" + v-if="forumConfig.isModerator" @click="addTopic" :title="$gettext('Neues Thema anlegen')" + :aria-label="$gettext('Neues Thema anlegen')" :class="label ? 'button--icon-label' : 'button--icon-only'" > <StudipIcon shape="add" :size="20" aria-hidden="true" /> diff --git a/resources/vue/components/forum/topics/SelectTopicInput.vue b/resources/vue/components/forum/topics/SelectTopicInput.vue index 8187115..9fd359b 100644 --- a/resources/vue/components/forum/topics/SelectTopicInput.vue +++ b/resources/vue/components/forum/topics/SelectTopicInput.vue @@ -16,7 +16,7 @@ const selectedTopics = defineModel(); return { name: topic }; }" - v-bind="{...$props, ...$attrs}" + v-bind="$attrs" > <template #search="{attributes, events}"> <input @@ -28,13 +28,13 @@ const selectedTopics = defineModel(); </template> <template #selected-option="{name, color}"> <div class="flex items-center"> - <span v-if="color" :style="{ backgroundColor: color, height: '14px', width: '14px', marginRight: '8px'}"></span> + <span v-if="color" :style="{ backgroundColor: color, height: '14px', width: '14px', marginRight: '8px'}" aria-hidden="true"></span> <span class="line-clamp-1 flex-1">{{ name }}</span> </div> </template> <template #option="{name, color}"> <div :style="{ display: 'flex', alignItems: 'center' }"> - <span v-if="color" :style="{ backgroundColor: color, height: '14px', width: '14px', marginRight: '8px'}"></span> + <span v-if="color" :style="{ backgroundColor: color, height: '14px', width: '14px', marginRight: '8px'}" aria-hidden="true"></span> <span :style="{ flex: '1'}" class="line-clamp-1">{{ name }}</span> </div> </template> diff --git a/resources/vue/components/forum/topics/TopicItem.vue b/resources/vue/components/forum/topics/TopicItem.vue index 30ae4d7..e5a5620 100644 --- a/resources/vue/components/forum/topics/TopicItem.vue +++ b/resources/vue/components/forum/topics/TopicItem.vue @@ -75,6 +75,7 @@ const swapTopic = event => { class="drag-link styleless" @keydown="swapTopic" :title="$gettext('Sortierelement für Element %{name}. Drücken Sie die Tasten Pfeil-nach-oben oder Pfeil-nach-unten, um dieses Element in der Liste zu verschieben.', {name: topic.name})" + :aria-label="$gettext('Sortierelement für Element %{name}. Drücken Sie die Tasten Pfeil-nach-oben oder Pfeil-nach-unten, um dieses Element in der Liste zu verschieben.', {name: topic.name})" > <span class="drag-handle"></span> </button> @@ -83,18 +84,23 @@ const swapTopic = event => { <div class="flex-1"> <div class="title-with-actions"> <div class="title-with-actions__content"> - <a class="title-with-actions__link" :href="getTopicURL(topic.id)" :title="$gettext('Zum Thema')"> + <a + class="title-with-actions__link" + :href="getTopicURL(topic.id)" + :title="$gettext('Zum Thema')" + :aria-label="$gettext('Zum Thema: %{name}', {name: topic.name})" + > <span class="topic-title line-clamp-2">{{ topic.name }}</span> <span v-if="!forumConfig.allowGuestAccess && topic.meta.unread_postings_count" class="unread-items-badge" role="status" aria-live="polite" - :aria-label="$gettext('Sie haben %{count} ungelesene Beiträge', {count: topic.meta.unread_postings_count})" :title="$gettext('Sie haben %{count} ungelesene Beiträge', {count: topic.meta.unread_postings_count})" + :aria-label="$gettext('Sie haben %{count} ungelesene Beiträge', {count: topic.meta.unread_postings_count})" > - {{ topic.meta.unread_postings_count }} - </span> + {{ topic.meta.unread_postings_count }} + </span> </a> </div> @@ -120,7 +126,7 @@ const swapTopic = event => { <dl> <dt>{{ $gettext('Anzahl der Teilnehmenden am Thema') }}</dt> <dd class="inline-flex gap-5 items-center"> - <StudipIcon shape="community2" role="info" :size="15" aria-hidden="true"/> + <StudipIcon shape="community2" role="info" :size="15" aria-hidden="true" /> {{ topic.meta.users_count }} </dd> </dl> @@ -128,19 +134,19 @@ const swapTopic = event => { <dl> <dt>{{ $gettext('Anzahl der Diskussionen') }}</dt> <dd class="inline-flex gap-5 items-center"> - <StudipIcon shape="forum" role="info" :size="15" aria-hidden="true"/> + <StudipIcon shape="forum" role="info" :size="15" aria-hidden="true" /> {{ topic.meta.discussions_count }} </dd> <dt>{{ $gettext('Anzahl der Beiträge') }}</dt> <dd class="inline-flex gap-5 items-center"> - <StudipIcon shape="reply" role="info" :size="15" aria-hidden="true"/> + <StudipIcon shape="reply" role="info" :size="15" aria-hidden="true" /> {{ topic.meta.postings_count }} </dd> <dt>{{ $gettext('Letzte Aktivität') }}</dt> <dd class="inline-flex gap-5 items-center"> - <StudipIcon shape="activity" role="info" :size="15" aria-hidden="true"/> + <StudipIcon shape="activity" role="info" :size="15" aria-hidden="true" /> <StudipDateTime v-if="topic.meta.recent_activity" :iso="topic.meta.recent_activity" :relative="true" /> <template v-else>{{ $gettext('Keine Aktivität') }}</template> </dd> @@ -148,30 +154,44 @@ const swapTopic = event => { </div> <!--mobile display: end--> </td> - <td class="nowrap" :title="$gettext('Anzahl der Diskussionen')" :aria-label="$gettext('Anzahl der Diskussionen')"> - {{ topic.meta.discussions_count }} {{ $gettext('Diskussionen') }} + <td class="nowrap"> + <span :title="$gettext('Anzahl der Diskussionen')" :aria-label="$gettext('Anzahl der Diskussionen')"> + {{ topic.meta.discussions_count }} {{ $gettext('Diskussionen') }} + </span> </td> <td> - <span class="inline-flex gap-10 items-center" :title="$gettext('Anzahl der Teilnehmenden am Thema')" role="group"> - <StudipIcon shape="community2" role="info" :size="20" aria-hidden="true" /> - <span class="sr-only">{{ $gettext('Anzahl der Teilnehmenden am Thema') }}:</span> - <span>{{ topic.meta.users_count }}</span> - </span> + <span + role="group" + class="inline-flex gap-10 items-center" + :title="$gettext('Anzahl der Teilnehmenden am Thema')" + > + <StudipIcon shape="community2" role="info" :size="20" aria-hidden="true" /> + <span class="sr-only">{{ $gettext('Anzahl der Teilnehmenden am Thema') }}:</span> + <span>{{ topic.meta.users_count }}</span> + </span> </td> <td> - <span class="inline-flex gap-10 items-center" :title="$gettext('Anzahl der Beiträge')" role="group"> - <StudipIcon shape="reply" role="info" :size="20" aria-hidden="true" /> - <span class="sr-only">{{ $gettext('Anzahl der Beiträge') }}:</span> - <span>{{ topic.meta.postings_count }}</span> - </span> + <span + role="group" + class="inline-flex gap-10 items-center" + :title="$gettext('Anzahl der Beiträge')" + > + <StudipIcon shape="reply" role="info" :size="20" aria-hidden="true" /> + <span class="sr-only">{{ $gettext('Anzahl der Beiträge') }}:</span> + <span>{{ topic.meta.postings_count }}</span> + </span> </td> <td> - <span class="inline-flex gap-10 items-center nowrap" :title="$gettext('Letzte Aktivität')" role="group"> - <StudipIcon shape="activity" role="info" :size="20" aria-hidden="true"/> - <span class="sr-only">{{ $gettext('Letzte Aktivität') }}:</span> - <StudipDateTime v-if="topic.meta.recent_activity" :iso="topic.meta.recent_activity" :relative="true" /> - <template v-else>{{ $gettext('Keine Aktivität') }}</template> - </span> + <span + role="group" + class="inline-flex gap-10 items-center nowrap" + :title="$gettext('Letzte Aktivität')" + > + <StudipIcon shape="activity" role="info" :size="20" aria-hidden="true"/> + <span class="sr-only">{{ $gettext('Letzte Aktivität') }}:</span> + <StudipDateTime v-if="topic.meta.recent_activity" :iso="topic.meta.recent_activity" :relative="true" /> + <template v-else>{{ $gettext('Keine Aktivität') }}</template> + </span> </td> <td class="actions"> <StudipActionMenu @@ -196,10 +216,9 @@ const swapTopic = event => { <div class="topic-card__body"> <div class="flex space-between"> <div class="flex items-start gap-10"> - <span class="topic-card__title topic-title line-clamp-2"> - {{ topic.name }} - </span> - + <span class="topic-card__title topic-title line-clamp-2"> + {{ topic.name }} + </span> <span v-if="!forumConfig.allowGuestAccess && topic.meta.unread_postings_count" class="unread-items-badge" @@ -208,8 +227,8 @@ const swapTopic = event => { :aria-label="$gettext('Sie haben %{count} ungelesene Beiträge', {count: topic.meta.unread_postings_count})" :title="$gettext('Sie haben %{count} ungelesene Beiträge', {count: topic.meta.unread_postings_count})" > - {{ topic.meta.unread_postings_count }} - </span> + {{ topic.meta.unread_postings_count }} + </span> </div> <div class="actions"> @@ -234,31 +253,34 @@ const swapTopic = event => { class="drag-link styleless" @keydown="swapTopic" :title="$gettext('Sortierelement für Element %{name}. Drücken Sie die Tasten Pfeil-nach-oben oder Pfeil-nach-unten, um dieses Element in der Liste zu verschieben.', {name: topic.name})" + :aria-label="$gettext('Sortierelement für Element %{name}. Drücken Sie die Tasten Pfeil-nach-oben oder Pfeil-nach-unten, um dieses Element in der Liste zu verschieben.', {name: topic.name})" > <span class="drag-handle"></span> </button> </div> <div class="topic-card__footer"> - <span class="inline-flex gap-10 items-center" :title="$gettext('Anzahl der Teilnehmenden am Thema')" role="group"> - <StudipIcon shape="community2" role="info" :size="15" aria-hidden="true" /> - <span class="sr-only">{{ $gettext('Anzahl der Teilnehmenden am Thema') }}:</span> - <small>{{ topic.meta.users_count }}</small> - </span> + <span class="inline-flex gap-10 items-center" :title="$gettext('Anzahl der Teilnehmenden am Thema')" role="group"> + <StudipIcon shape="community2" role="info" :size="15" aria-hidden="true" /> + <span class="sr-only">{{ $gettext('Anzahl der Teilnehmenden am Thema') }}:</span> + <small>{{ topic.meta.users_count }}</small> + </span> + <span class="inline-flex gap-10 items-center" :title="$gettext('Anzahl der Beiträge')" role="group"> - <StudipIcon shape="reply" role="info" :size="15" aria-hidden="true" /> - <span class="sr-only">{{ $gettext('Anzahl der Beiträge') }}:</span> - <small>{{ topic.meta.postings_count }}</small> - </span> + <StudipIcon shape="reply" role="info" :size="15" aria-hidden="true" /> + <span class="sr-only">{{ $gettext('Anzahl der Beiträge') }}:</span> + <small>{{ topic.meta.postings_count }}</small> + </span> + <span class="inline-flex gap-10 items-center" :title="$gettext('Letzte Aktivität')" role="group"> - <StudipIcon shape="activity" role="info" :size="15" aria-hidden="true" /> - <span class="sr-only">{{ $gettext('Letzte Aktivität') }}:</span> - <small v-if="topic.meta.recent_activity"> - <StudipDateTime :iso="topic.meta.recent_activity" :relative="true" /> - </small> - <small v-else> - {{ $gettext('Keine Aktivität') }} - </small> - </span> + <StudipIcon shape="activity" role="info" :size="15" aria-hidden="true" /> + <span class="sr-only">{{ $gettext('Letzte Aktivität') }}:</span> + <small v-if="topic.meta.recent_activity"> + <StudipDateTime :iso="topic.meta.recent_activity" :relative="true" /> + </small> + <small v-else> + {{ $gettext('Keine Aktivität') }} + </small> + </span> </div> </div> </div> diff --git a/resources/vue/components/forum/topics/TopicsIndex.vue b/resources/vue/components/forum/topics/TopicsIndex.vue index 3da69c6..9c5367b 100644 --- a/resources/vue/components/forum/topics/TopicsIndex.vue +++ b/resources/vue/components/forum/topics/TopicsIndex.vue @@ -174,6 +174,7 @@ const showCategoryDialog = category => currentCategory.value = category; <thead> <tr class="sortable"> <th + scope="col" :class="getSortClass('name')" :aria-sort="getAriaSortString('name')" :aria-label="getAriaSortLabel('name', $gettext('Name'))" @@ -182,11 +183,14 @@ const showCategoryDialog = category => currentCategory.value = category; type="button" class="as-link" @click="sortBy('name')" - :title="$gettext('Nach Name sortieren')"> + :title="$gettext('Nach Name sortieren')" + :aria-label="$gettext('Nach Name sortieren')" + > {{ $gettext('Name') }} </button> </th> <th + scope="col" :class="getSortClass('meta.discussions_count')" :aria-sort="getAriaSortString('meta.discussions_count')" :aria-label="getAriaSortLabel('meta.discussions_count', $gettext('Anzahl der Diskussionen'))" @@ -195,11 +199,14 @@ const showCategoryDialog = category => currentCategory.value = category; type="button" class="as-link" @click="sortBy('meta.discussions_count')" - :title="$gettext('Nach Anzahl der Diskussionen sortieren')"> + :title="$gettext('Nach Anzahl der Diskussionen sortieren')" + :aria-label="$gettext('Nach Anzahl der Diskussionen sortieren')" + > {{ $gettext('Diskussionen') }} </button> </th> <th + scope="col" :class="getSortClass('meta.users_count')" :aria-sort="getAriaSortString('meta.users_count')" :aria-label="getAriaSortLabel('meta.users_count', $gettext('Anzahl der Teilnehmenden'))" @@ -208,11 +215,14 @@ const showCategoryDialog = category => currentCategory.value = category; type="button" class="as-link" @click="sortBy('meta.users_count')" - :title="$gettext('Nach Anzahl der Teilnehmenden sortieren')"> + :title="$gettext('Nach Anzahl der Teilnehmenden sortieren')" + :aria-label="$gettext('Nach Anzahl der Teilnehmenden sortieren')" + > {{ $gettext('Teilnehmende') }} </button> </th> <th + scope="col" :class="getSortClass('meta.postings_count')" :aria-sort="getAriaSortString('meta.postings_count')" :aria-label="getAriaSortLabel('meta.postings_count', $gettext('Anzahl der Beiträge'))" @@ -221,11 +231,14 @@ const showCategoryDialog = category => currentCategory.value = category; type="button" class="as-link" @click="sortBy('meta.postings_count')" - :title="$gettext('Nach Anzahl der Beiträge sortieren')"> + :title="$gettext('Nach Anzahl der Beiträge sortieren')" + :aria-label="$gettext('Nach Anzahl der Beiträge sortieren')" + > {{ $gettext('Beiträge') }} </button> </th> <th + scope="col" :class="getSortClass('meta.recent_activity')" :aria-sort="getAriaSortString('meta.recent_activity')" :aria-label="getAriaSortLabel('meta.recent_activity', $gettext('Letzte Aktivität'))" @@ -234,11 +247,15 @@ const showCategoryDialog = category => currentCategory.value = category; type="button" class="as-link" @click="sortBy('meta.recent_activity')" - :title="$gettext('Nach letzter Aktivität sortieren')"> + :title="$gettext('Nach letzter Aktivität sortieren')" + :aria-label="$gettext('Nach letzter Aktivität sortieren')" + > {{ $gettext('Letzte Aktivität') }} </button> </th> - <th></th> + <th scope="col"> + <span class="sr-only">{{ $gettext('Aktionen') }}</span> + </th> </tr> </thead> <draggable |
