aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMurtaza Sultani <sultani@data-quest.de>2026-01-21 11:33:56 +0100
committerMurtaza Sultani <sultani@data-quest.de>2026-01-21 11:33:56 +0100
commitb5fdb4af0fe8c2dafcb62e1e072eac9e142805fa (patch)
tree481911be648d696381b424b5f131b7d36b784c67
parentbb1f39e8aa5fb6e03d4351af0f1024ac730ee96f (diff)
Resolve "Forum3: A11y"
Closes #6181 Merge request studip/studip!4682
-rw-r--r--resources/vue/apps/forum/categories/Index.vue26
-rw-r--r--resources/vue/apps/forum/categories/Show.vue38
-rw-r--r--resources/vue/apps/forum/discussions/Edit.vue33
-rw-r--r--resources/vue/apps/forum/discussions/Index.vue26
-rw-r--r--resources/vue/apps/forum/discussions/Show.vue35
-rw-r--r--resources/vue/apps/forum/discussions_types/Index.vue11
-rw-r--r--resources/vue/apps/forum/search/Index.vue80
-rw-r--r--resources/vue/apps/forum/subscriptions/Index.vue225
-rw-r--r--resources/vue/apps/forum/topics/Edit.vue8
-rw-r--r--resources/vue/apps/forum/topics/Index.vue16
-rw-r--r--resources/vue/apps/forum/topics/Show.vue25
-rw-r--r--resources/vue/components/forum/EmptyForum.vue2
-rw-r--r--resources/vue/components/forum/SelectTagsInput.vue2
-rw-r--r--resources/vue/components/forum/SelectUserInput.vue2
-rw-r--r--resources/vue/components/forum/categories/CategoryItem.vue117
-rw-r--r--resources/vue/components/forum/categories/Create.vue3
-rw-r--r--resources/vue/components/forum/discussions/Create.vue5
-rw-r--r--resources/vue/components/forum/discussions/DiscussionIndex.vue33
-rw-r--r--resources/vue/components/forum/discussions/SelectDiscussionType.vue6
-rw-r--r--resources/vue/components/forum/posts/Post.vue1
-rw-r--r--resources/vue/components/forum/posts/PostCreateForm.vue13
-rw-r--r--resources/vue/components/forum/posts/PostEditForm.vue9
-rw-r--r--resources/vue/components/forum/posts/PostReactionShow.vue14
-rw-r--r--resources/vue/components/forum/posts/PostReactions.vue4
-rw-r--r--resources/vue/components/forum/topics/CreateTopic.vue3
-rw-r--r--resources/vue/components/forum/topics/SelectTopicInput.vue6
-rw-r--r--resources/vue/components/forum/topics/TopicItem.vue122
-rw-r--r--resources/vue/components/forum/topics/TopicsIndex.vue29
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