aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--resources/assets/stylesheets/scss/forum.scss214
-rw-r--r--resources/assets/stylesheets/scss/personal-notifications.scss4
-rw-r--r--resources/assets/stylesheets/studip.scss1
-rw-r--r--resources/vue/apps/forum/categories/Index.vue4
-rw-r--r--resources/vue/apps/forum/categories/Show.vue4
-rw-r--r--resources/vue/apps/forum/discussions/Show.vue14
-rw-r--r--resources/vue/apps/forum/search/Index.vue7
-rw-r--r--resources/vue/apps/forum/topics/Index.vue4
-rw-r--r--resources/vue/components/forum/EmptyForum.vue10
-rw-r--r--resources/vue/components/forum/SubscriptionDropdown.vue8
-rw-r--r--resources/vue/components/forum/categories/CategoryItem.vue6
-rw-r--r--resources/vue/components/forum/categories/Create.vue3
-rw-r--r--resources/vue/components/forum/discussions/Create.vue4
-rw-r--r--resources/vue/components/forum/discussions/DiscussionIndex.vue2
-rw-r--r--resources/vue/components/forum/posts/Post.vue8
-rw-r--r--resources/vue/components/forum/posts/PostCreateForm.vue12
-rw-r--r--resources/vue/components/forum/posts/PostEditForm.vue12
-rw-r--r--resources/vue/components/forum/posts/PostReactionShow.vue111
-rw-r--r--resources/vue/components/forum/posts/PostReactions.vue95
-rw-r--r--resources/vue/components/forum/topics/CreateTopic.vue3
-rw-r--r--resources/vue/components/forum/topics/TopicItem.vue6
-rw-r--r--templates/personal_notifications/notification.php2
22 files changed, 397 insertions, 137 deletions
diff --git a/resources/assets/stylesheets/scss/forum.scss b/resources/assets/stylesheets/scss/forum.scss
index 9c5efc6..4b309a4 100644
--- a/resources/assets/stylesheets/scss/forum.scss
+++ b/resources/assets/stylesheets/scss/forum.scss
@@ -60,6 +60,11 @@ $card-max-width: 300px;
.subscription-button {
background-color: transparent;
border-color: transparent;
+ justify-content: end;
+ padding: 0;
+ &:hover {
+ color: var(--color--highlight);
+ }
}
}
@@ -216,37 +221,6 @@ $card-max-width: 300px;
}
}
- .icon-button {
- display: flex;
- align-items: center;
- justify-content: center;
- background: white;
- border: 1px solid var(--color--highlight);
- color: var(--color--highlight);
- border-radius: 5px;
- padding: 7px;
- cursor: pointer;
-
- &.--with-label {
- padding-left: 10px;
- padding-right: 10px;
- }
-
- &:hover {
- background-color: $content-color-10;
- }
-
- &:disabled {
- border-color: var(--color--button-inactive-border);
- opacity: 0.5;
- cursor: not-allowed;
- }
-
- .label {
- margin-left: 5px;
- }
- }
-
button.style-less {
background: no-repeat;
border: none;
@@ -254,23 +228,21 @@ $card-max-width: 300px;
}
.button {
- &.--with-icon {
- display: inline-flex;
- gap: 10px;
+ &--icon-only {
+ min-width: unset;
+ margin: 0;
+ padding: 6px;
+ display: flex;
align-items: center;
+ justify-content: center;
}
- .icon-hover {
- display: none;
- }
-
- &:hover {
- .icon-default {
- display: none;
- }
- .icon-hover {
- display: inline-block;
- }
+ &--icon-label {
+ margin: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 10px;
}
}
@@ -329,26 +301,21 @@ $card-max-width: 300px;
align-items: center;
}
- h3 {
- margin: 0;
- font-size: 16px;
- font-weight: normal;
- }
-
p {
margin-top: 5px;
color: var(--color--font-secondary);
}
}
- .discussion-overview {
- h3 {
- margin-top: 0;
- font-size: 16px;
- font-weight: normal;
- }
+ .topic-title,
+ .category-title,
+ .discussion-title {
+ font-size: 16px;
+ }
+ .discussion-overview {
p {
+ margin-top: 5px;
color: var(--color--font-secondary);
}
@@ -455,15 +422,20 @@ $card-max-width: 300px;
z-index: 1;
}
+ &:hover {
+ .topic-card__title {
+ color: var(--color--highlight-hover);
+ text-decoration: underline;
+ }
+ }
+
&__title {
- margin-top: 0;
- margin-bottom: 10px;
- font-size: 16px;
- font-weight: normal;
+ color: var(--color--highlight);
}
p {
- opacity: 60%;
+ margin-top: 5px;
+ color: var(--color--font-secondary);
}
&__content {
@@ -621,7 +593,12 @@ $card-max-width: 300px;
align-items: center;
justify-content: center;
- &__create-button {
+ &__button-group {
+ display: inline-flex;
+ align-items: center;
+ }
+
+ &__add-reaction {
display: inline-flex;
cursor: pointer;
background: none;
@@ -648,6 +625,11 @@ $card-max-width: 300px;
}
}
+ &__show-reactions {
+ border: none;
+ background: none;
+ }
+
&__container {
position: absolute;
top: -50px;
@@ -776,6 +758,14 @@ $card-max-width: 300px;
}
}
}
+
+ .post-form__footer {
+ margin-top: 20px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ gap: 15px;
+ }
}
.user-avatar-dropdown {
@@ -1104,10 +1094,10 @@ $card-max-width: 300px;
border-color: $base-color-60;
}
}
+ }
- .html-emoji {
- font-family: apple color emoji, segoe ui emoji, notocoloremoji, segoe ui symbol, android emoji, emojisymbols, emojione mozilla;
- }
+ .emoji-icon {
+ font-family: apple color emoji, segoe ui emoji, notocoloremoji, segoe ui symbol, android emoji, emojisymbols, emojione mozilla;
}
.forum-subscriptions-dropdown {
@@ -1176,9 +1166,8 @@ $card-max-width: 300px;
.subscription-button {
white-space: nowrap;
+ min-width: unset;
display: inline-flex;
- gap: 6px;
- align-items: center;
}
}
@@ -1488,6 +1477,95 @@ $card-max-width: 300px;
}
}
+ .tab {
+ &__buttons {
+ display: flex;
+ gap: 10px;
+ border-bottom: 2px solid var(--color--divider);
+ }
+
+ &__button {
+ label {
+ position: relative;
+ transition: color 0.3s ease;
+ color: var(--color--highlight);
+ font-weight: bold;
+ padding: 6px 12px;
+ cursor: pointer;
+ display: inline-flex;
+ align-items: center;
+ gap: 5px;
+
+ &:hover {
+ color: var(--color--highlight-hover);
+
+ &::after {
+ background-color: var(--color--focus);
+ }
+ }
+
+ &:hover,
+ &.is-checked {
+ &::after {
+ content: '';
+ position: absolute;
+ left: 0;
+ bottom: -2px;
+ width: 100%;
+ height: 2px;
+ }
+ }
+
+ &.is-checked {
+ &::after {
+ background-color: var(--color--highlight);
+ }
+ }
+ }
+
+ input[type="radio"] {
+ position: absolute;
+ opacity: 0;
+ width: 0;
+ height: 0;
+ pointer-events: none;
+
+ &:focus + label {
+ outline: 2px solid var(--color--focus);
+ outline-offset: 2px;
+ border-radius: 2px;
+ }
+ }
+
+ }
+
+ &__content {
+ margin-top: 15px;
+ }
+ }
+
+ .post-reactions-dialog {
+ .user-avatar-dropdown {
+ .dropdown__content {
+ right: auto;
+ left: 0;
+ }
+ }
+ }
+
+ .user-reaction {
+ position: relative;
+ width: 35px;
+ height: 35px;
+
+ .emoji-icon {
+ position: absolute;
+ bottom: 0;
+ right: 0;
+ z-index: 1;
+ }
+ }
+
/*
vue Transition --start--
*/
diff --git a/resources/assets/stylesheets/scss/personal-notifications.scss b/resources/assets/stylesheets/scss/personal-notifications.scss
index 8d80fbb..1e63488 100644
--- a/resources/assets/stylesheets/scss/personal-notifications.scss
+++ b/resources/assets/stylesheets/scss/personal-notifications.scss
@@ -187,7 +187,7 @@
display: block;
padding: 0;
&:hover {
- color: var(--color--highlight-hover);
+ color: var(--color--highlight-hover);
text-decoration: none;
}
}
@@ -224,7 +224,7 @@
.item:hover .options.hidden { visibility: visible; }
}
- .html-emoji {
+ .emoji-icon {
font-family: apple color emoji, segoe ui emoji, notocoloremoji, segoe ui symbol, android emoji, emojisymbols, emojione mozilla;
font-size: 20px;
margin-right: 10px;
diff --git a/resources/assets/stylesheets/studip.scss b/resources/assets/stylesheets/studip.scss
index d923f64..080ca40 100644
--- a/resources/assets/stylesheets/studip.scss
+++ b/resources/assets/stylesheets/studip.scss
@@ -779,6 +779,7 @@ input.allow-plaintext-toggle {
z-index: 1;
right: 10px;
top: 10px;
+ padding: 0;
display: flex;
align-items: center;
justify-content: center;
diff --git a/resources/vue/apps/forum/categories/Index.vue b/resources/vue/apps/forum/categories/Index.vue
index 51646ba..abb98c0 100644
--- a/resources/vue/apps/forum/categories/Index.vue
+++ b/resources/vue/apps/forum/categories/Index.vue
@@ -127,14 +127,14 @@ const swapCategory = (categoryId, step) => {
v-if="forumConfig.tileLayout"
@click="forumConfig.toggleForumLayout()"
type="button"
- :title="$gettext('Tabellarische Ansicht')" class="icon-button">
+ :title="$gettext('Tabellarische Ansicht')" class="button button--icon-only">
<StudipIcon shape="view-list" :size="20" />
</button>
<button
v-else
@click="forumConfig.toggleForumLayout()"
type="button"
- :title="$gettext('Kachelansicht')" class="icon-button">
+ :title="$gettext('Kachelansicht')" class="button button--icon-only">
<StudipIcon shape="view-wall" :size="20" />
</button>
<div aria-live="polite" class="sr-only" role="status">{{ toggleLayoutMessage }}</div>
diff --git a/resources/vue/apps/forum/categories/Show.vue b/resources/vue/apps/forum/categories/Show.vue
index 2445646..4c340cd 100644
--- a/resources/vue/apps/forum/categories/Show.vue
+++ b/resources/vue/apps/forum/categories/Show.vue
@@ -95,7 +95,7 @@ onMounted(async () => {
@click="forumConfig.toggleForumLayout()"
type="button"
:title="$gettext('Tabellarische Ansicht')"
- class="icon-button">
+ class="button button--icon-only">
<StudipIcon shape="view-list" :size="20" />
</button>
<button
@@ -103,7 +103,7 @@ onMounted(async () => {
@click="forumConfig.toggleForumLayout()"
type="button"
:title="$gettext('Kachelansicht')"
- class="icon-button">
+ class="button button--icon-only">
<StudipIcon shape="view-wall" :size="20" />
</button>
<div aria-live="polite" class="sr-only" role="status">{{ toggleLayoutMessage }}</div>
diff --git a/resources/vue/apps/forum/discussions/Show.vue b/resources/vue/apps/forum/discussions/Show.vue
index ed5043c..9343047 100644
--- a/resources/vue/apps/forum/discussions/Show.vue
+++ b/resources/vue/apps/forum/discussions/Show.vue
@@ -91,7 +91,7 @@ const fetchPostings = async () => {
`forum-discussions/${props.discussion.discussion_id}/postings`,
{
data: {
- include: 'author,opengraph-urls,posting,reactions,reactions.user&fields[users]=id',
+ include: 'author,opengraph-urls,posting,reactions,reactions.user&fields[users]=id,username,formatted-name',
page: { offset }
}
}
@@ -194,7 +194,7 @@ onMounted(async () => {
</em>
<StudipIcon shape="lock-locked2" :size="20" role="inactive" />
</div>
- <button v-if="canEditDiscussion" @click="editDiscussion(discussion.discussion_id)" type="button" :title="$gettext('Diskussion bearbeiten')" class="icon-button">
+ <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>
<SubscriptionDropdown
@@ -243,7 +243,7 @@ onMounted(async () => {
<a
v-if="!discussion.closed_at"
href="#new-post"
- class="button --with-icon m-0"
+ class="button button--icon-label"
role="button"
:title="$gettext('Antworten')"
:aria-label="$gettext('Antworten')"
@@ -252,8 +252,7 @@ onMounted(async () => {
}"
@click="postCreateForm = true"
>
- <StudipIcon shape="reply" :size="20" class="icon-default" aria-hidden="true" />
- <StudipIcon shape="reply" :size="20" class="icon-hover" role="info_alt" aria-hidden="true" />
+ <StudipIcon shape="reply" :size="20" aria-hidden="true" />
{{ $gettext('Antworten') }}
</a>
</div>
@@ -296,7 +295,7 @@ onMounted(async () => {
<a
v-if="!discussion.closed_at"
href="#new-post"
- class="button --with-icon m-0"
+ class="button button--icon-label"
role="button"
:title="$gettext('Antworten')"
:aria-label="$gettext('Antworten')"
@@ -305,8 +304,7 @@ onMounted(async () => {
}"
@click="postCreateForm = true"
>
- <StudipIcon shape="reply" :size="20" class="icon-default" aria-hidden="true" />
- <StudipIcon shape="reply" :size="20" class="icon-hover" role="info_alt" aria-hidden="true" />
+ <StudipIcon shape="reply" :size="20" aria-hidden="true" />
{{ $gettext('Antworten') }}
</a>
</div>
diff --git a/resources/vue/apps/forum/search/Index.vue b/resources/vue/apps/forum/search/Index.vue
index 9e7c36a..2b5c4bb 100644
--- a/resources/vue/apps/forum/search/Index.vue
+++ b/resources/vue/apps/forum/search/Index.vue
@@ -146,14 +146,13 @@ onMounted(() => {
</div>
<button
type="submit"
- class="button m-0 --with-icon"
+ class="button button--icon-label"
:title="$gettext('Suchen')"
>
- <StudipIcon shape="search" :size="20" class="icon-default" aria-hidden="true" />
- <StudipIcon shape="search" :size="20" class="icon-hover" role="info_alt" aria-hidden="true" />
+ <StudipIcon shape="search" :size="20" aria-hidden="true" />
{{ $gettext('Suchen') }}
</button>
- <button @click="resetSearchForm" type="button" class="icon-button" :title="$gettext('Zurücksetzen')">
+ <button @click="resetSearchForm" type="button" class="button button--icon-only" :title="$gettext('Zurücksetzen')">
<StudipIcon shape="decline" :size="20" />
</button>
</div>
diff --git a/resources/vue/apps/forum/topics/Index.vue b/resources/vue/apps/forum/topics/Index.vue
index 9d70dc7..9df0a78 100644
--- a/resources/vue/apps/forum/topics/Index.vue
+++ b/resources/vue/apps/forum/topics/Index.vue
@@ -69,7 +69,7 @@ onMounted(async () => {
@click="forumConfig.toggleForumLayout();"
:title="$gettext('Tabellarische Ansicht')"
type="button"
- class="icon-button">
+ class="button button--icon-only">
<StudipIcon shape="view-list" :size="20" />
</button>
<button
@@ -77,7 +77,7 @@ onMounted(async () => {
@click="forumConfig.toggleForumLayout();"
:title="$gettext('Kachelansicht')"
type="button"
- class="icon-button">
+ class="button button--icon-only">
<StudipIcon shape="view-wall" :size="20" />
</button>
<div aria-live="polite" class="sr-only" role="status">{{ toggleLayoutMessage }}</div>
diff --git a/resources/vue/components/forum/EmptyForum.vue b/resources/vue/components/forum/EmptyForum.vue
index bc7d25d..8a2c40c 100644
--- a/resources/vue/components/forum/EmptyForum.vue
+++ b/resources/vue/components/forum/EmptyForum.vue
@@ -24,14 +24,12 @@ const emptyForumIllustration = `${STUDIP.ASSETS_URL}images/forum/forum-keyvisual
</p>
<div class="buttons-container">
- <button type="button" class="button --with-icon">
- <StudipIcon shape="lightbulb" :size="20" class="icon-default" aria-hidden="true" />
- <StudipIcon shape="lightbulb" :size="20" class="icon-hover" role="info_alt" aria-hidden="true" />
+ <button type="button" class="button button--icon-label">
+ <StudipIcon shape="lightbulb" :size="20" aria-hidden="true" />
{{ $gettext('Tour ansehen') }}
</button>
- <a :href="getDiscussionCreateURL()" data-dialog="width=900;height=700" class="button --with-icon">
- <StudipIcon shape="add" :size="20" class="icon-default" aria-hidden="true" />
- <StudipIcon shape="add" :size="20" class="icon-hover" role="info_alt" aria-hidden="true" />
+ <a :href="getDiscussionCreateURL()" data-dialog="width=900;height=700" class="button button--icon-label">
+ <StudipIcon shape="add" :size="20" aria-hidden="true" />
{{ $gettext('Eine Diskussion starten') }}
</a>
</div>
diff --git a/resources/vue/components/forum/SubscriptionDropdown.vue b/resources/vue/components/forum/SubscriptionDropdown.vue
index c641011..422c0ae 100644
--- a/resources/vue/components/forum/SubscriptionDropdown.vue
+++ b/resources/vue/components/forum/SubscriptionDropdown.vue
@@ -128,7 +128,13 @@ const subscribe = async (notification_type = 'all') => {
<template>
<Dropdown class="forum-subscriptions-dropdown" v-model="isOpen" :title="title">
<template #trigger>
- <button class="icon-button subscription-button" type="button" @click="isOpen = !isOpen" :title="title">
+ <button
+ type="button"
+ :title="title"
+ class="button subscription-button"
+ :class="subscriptionButtonLabel ? 'button--icon-label' : 'button--icon-only'"
+ @click="isOpen = !isOpen"
+ >
<span v-if="subscriptionButtonLabel">
{{ subscriptionButtonLabel }}
</span>
diff --git a/resources/vue/components/forum/categories/CategoryItem.vue b/resources/vue/components/forum/categories/CategoryItem.vue
index 55685a7..0035ad1 100644
--- a/resources/vue/components/forum/categories/CategoryItem.vue
+++ b/resources/vue/components/forum/categories/CategoryItem.vue
@@ -74,7 +74,7 @@ const swapCategory = event => {
class="title-with-actions__link"
:href="getCategoryURL(category.id)"
:title="$gettext('Zur Kategorie')">
- <h3 class="line-clamp-2">{{ category.name }}</h3>
+ <span class="category-title line-clamp-2">{{ category.name }}</span>
<span
v-if="category.meta.postings_count > category.meta.user_read_index"
class="unread-items-badge"
@@ -185,9 +185,9 @@ const swapCategory = event => {
<div class="topic-card__body">
<div class="flex space-between">
<div class="flex items-start gap-10">
- <h3 class="topic-card__title line-clamp-2">
+ <span class="topic-card__title category-title line-clamp-2">
{{ category.name }}
- </h3>
+ </span>
<span
v-if="category.meta.postings_count > category.meta.user_read_index"
diff --git a/resources/vue/components/forum/categories/Create.vue b/resources/vue/components/forum/categories/Create.vue
index fa79752..a7b47df 100644
--- a/resources/vue/components/forum/categories/Create.vue
+++ b/resources/vue/components/forum/categories/Create.vue
@@ -18,7 +18,8 @@ defineProps({
data-dialog="size=700"
:title="$gettext('Neue Kategorie anlegen')"
:aria-label="$gettext('Neue Kategorie anlegen')"
- class="icon-button"
+ class="button"
+ :class="label ? 'button--icon-label' : 'button--icon-only'"
role="button"
>
<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 1d88d78..ee76c72 100644
--- a/resources/vue/components/forum/discussions/Create.vue
+++ b/resources/vue/components/forum/discussions/Create.vue
@@ -20,8 +20,8 @@ const discussionCreateURL = computed(() => {
:href="discussionCreateURL"
:title="$gettext('Neue Diskussion starten')"
data-dialog="width=900;height=750"
- type="button"
- class="icon-button">
+ role="button"
+ class="button button--icon-only">
<StudipIcon shape="add" :size="20" aria-hidden="true" />
</a>
</template>
diff --git a/resources/vue/components/forum/discussions/DiscussionIndex.vue b/resources/vue/components/forum/discussions/DiscussionIndex.vue
index 86fdf8a..2369888 100644
--- a/resources/vue/components/forum/discussions/DiscussionIndex.vue
+++ b/resources/vue/components/forum/discussions/DiscussionIndex.vue
@@ -167,7 +167,7 @@ onMounted(() => {
class="title-with-actions__link"
:href="getDiscussionURL(discussion.id, {redirect})"
:title="$gettext('Zur Diskussion')">
- <h3 class="title-with-actions_title line-clamp-2 m-0">{{ discussion.title }}</h3>
+ <span class="title-with-actions_title discussion-title line-clamp-2 m-0">{{ discussion.title }}</span>
<span
v-if="redirect !== 'recent' && discussion.meta.postings_count > discussion.meta.user_read_index"
class="unread-items-badge"
diff --git a/resources/vue/components/forum/posts/Post.vue b/resources/vue/components/forum/posts/Post.vue
index f954b94..28e70d2 100644
--- a/resources/vue/components/forum/posts/Post.vue
+++ b/resources/vue/components/forum/posts/Post.vue
@@ -203,17 +203,17 @@ const removePostHighlight = id => {
<div class="inline-flex items-center gap-40">
<div v-if="!discussion.closed_at" class="inline-flex items-center gap-10">
<template v-if="post.author?.id === auth_user.id">
- <button :disabled="editPost === post.id" @click="editPost = post.id" type="button" class="icon-button" :title="$gettext('Beitrag bearbeiten')" :aria-label="$gettext('Beitrag bearbeiten')">
+ <button :disabled="editPost === post.id" @click="editPost = post.id" type="button" class="button button--icon-only" :title="$gettext('Beitrag bearbeiten')" :aria-label="$gettext('Beitrag bearbeiten')">
<StudipIcon shape="edit" :size="20" aria-hidden="true" />
</button>
- <button @click="deletePost(post)" type="button" class="icon-button" :title="$gettext('Beitrag löschen')" :aria-label="$gettext('Beitrag löschen')">
+ <button @click="deletePost(post)" type="button" class="button button--icon-only" :title="$gettext('Beitrag löschen')" :aria-label="$gettext('Beitrag löschen')">
<StudipIcon shape="trash" :size="20" aria-hidden="true" />
</button>
</template>
- <button type="button" @click="forwardPost(post)" class="icon-button" :title="$gettext('Beitrage weiterleiten')" :aria-label="$gettext('Beitrage weiterleiten')">
+ <button type="button" @click="forwardPost(post)" class="button button--icon-only" :title="$gettext('Beitrage weiterleiten')" :aria-label="$gettext('Beitrage weiterleiten')">
<StudipIcon shape="export" :size="20" aria-hidden="true" />
</button>
- <button :disabled="postCreateForm" @click="addReply(post)" type="button" class="icon-button" :title="$gettext('Zitieren und antworten')" :aria-label="$gettext('Zitieren und Antworten')">
+ <button :disabled="postCreateForm" @click="addReply(post)" type="button" class="button button--icon-only" :title="$gettext('Zitieren und antworten')" :aria-label="$gettext('Zitieren und Antworten')">
<StudipIcon shape="quote" :size="20" aria-hidden="true" />
</button>
</div>
diff --git a/resources/vue/components/forum/posts/PostCreateForm.vue b/resources/vue/components/forum/posts/PostCreateForm.vue
index 095413a..00daeba 100644
--- a/resources/vue/components/forum/posts/PostCreateForm.vue
+++ b/resources/vue/components/forum/posts/PostCreateForm.vue
@@ -133,27 +133,25 @@ const storePost = async () => {
<div v-if="forumConfig.anonymousPost" class="mt-10">
<StudipSwitch name="anonymous" v-model="anonymous" :label="$gettext('Anonym')" />
</div>
- <div class="flex items-center gap-10">
+ <div class="post-form__footer">
<button
type="submit"
:disabled="isLoading || !content"
- class="button --with-icon"
+ class="button button--icon-label"
:title="$gettext('Antworten')"
:aria-label="$gettext('Antworten')"
>
- <StudipIcon shape="reply" :size="20" class="icon-default" aria-hidden="true" />
- <StudipIcon shape="reply" :size="20" class="icon-hover" role="info_alt" aria-hidden="true" />
+ <StudipIcon shape="reply" :size="20" aria-hidden="true" />
{{ $gettext('Antworten') }}
</button>
<button
type="button"
- class="button --with-icon"
+ class="button button--icon-label"
:title="$gettext('Abbrechen')"
:aria-label="$gettext('Abbrechen')"
@click="$emit('canceled')"
>
- <StudipIcon shape="decline" :size="20" class="icon-default" aria-hidden="true"/>
- <StudipIcon shape="decline" :size="20" class="icon-hover" role="info_alt" 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 9c50f90..5c9c311 100644
--- a/resources/vue/components/forum/posts/PostEditForm.vue
+++ b/resources/vue/components/forum/posts/PostEditForm.vue
@@ -80,26 +80,24 @@ onUnmounted(() => {
<div v-if="forumConfig.anonymousPost" class="mt-10">
<StudipSwitch name="anonymous" v-model="anonymous" :label="$gettext('Anonym')" />
</div>
- <div class="flex items-center gap-10">
+ <div class="post-form__footer">
<button
type="submit"
:disabled="isLoading || !content"
- class="button --with-icon"
+ class="button button--icon-label"
:value="$gettext('Speichern')"
:title="$gettext('Speichern')"
>
- <StudipIcon shape="reply" :size="20" class="icon-default" aria-hidden="true" />
- <StudipIcon shape="reply" :size="20" class="icon-hover" role="info_alt" aria-hidden="true" />
+ <StudipIcon shape="reply" :size="20" aria-hidden="true" />
{{ $gettext('Speichern') }}
</button>
<button
type="button"
- class="button --with-icon"
+ class="button button--icon-label"
:title="$gettext('Abbrechen')"
@click="$emit('canceled')"
>
- <StudipIcon shape="decline" :size="20" class="icon-default" aria-hidden="true"/>
- <StudipIcon shape="decline" :size="20" class="icon-hover" role="info_alt" aria-hidden="true"/>
+ <StudipIcon shape="decline" :size="20" aria-hidden="true"/>
{{ $gettext('Abbrechen') }}
</button>
</div>
diff --git a/resources/vue/components/forum/posts/PostReactionShow.vue b/resources/vue/components/forum/posts/PostReactionShow.vue
new file mode 100644
index 0000000..a15df91
--- /dev/null
+++ b/resources/vue/components/forum/posts/PostReactionShow.vue
@@ -0,0 +1,111 @@
+<script setup>
+import {$gettext} from "../../../../assets/javascripts/lib/gettext";
+import StudipDateTime from "../../StudipDateTime.vue";
+import UserAvatarDropdown from "../UserAvatarDropdown.vue";
+import {REACTION_ICONS} from "./reactions";
+import {userProfileURL} from "../helpers/urls";
+import {computed, onMounted} from "vue";
+import {useSortable} from "../../../composables/useSortable";
+
+const props = defineProps({
+ reactions: {
+ type: Array,
+ required: true
+ },
+ emoji: {
+ type: String,
+ default: 'all'
+ }
+});
+
+const computedReactions = computed(() => {
+ if (props.emoji === 'all') {
+ return props.reactions;
+ }
+
+ return props.reactions.filter(({ emoji }) => emoji === props.emoji);
+});
+
+const {
+ sortedData: sortedReactions,
+ sortBy,
+ getSortClass,
+ getAriaSortString,
+ getAriaSortLabel
+} = useSortable(computedReactions);
+
+onMounted(() => {
+ sortBy('mkdate', 'desc');
+});
+</script>
+
+<template>
+ <table class="default forum-table --posts-reactors">
+ <colgroup>
+ <col style="width: 50px;">
+ <col>
+ <col>
+ </colgroup>
+ <thead>
+ <tr class="sortable">
+ <th></th>
+ <th
+ :class="getSortClass('user.formatted_name')"
+ :aria-sort="getAriaSortString('user.formatted_name')"
+ :aria-label="getAriaSortLabel('user.formatted_name', $gettext('Name'))"
+ >
+ <a
+ href="#"
+ @click.prevent="sortBy('user.formatted_name')"
+ :title="$gettext('Nach Name sortieren')">
+ {{ $gettext('Name') }}
+ </a>
+ </th>
+ <th
+ :class="getSortClass('mkdate')"
+ :aria-sort="getAriaSortString('mkdate')"
+ :aria-label="getAriaSortLabel('mkdate', $gettext('Datum'))"
+ >
+ <a
+ href="#"
+ @click.prevent="sortBy('mkdate')"
+ :title="$gettext('Nach Datum sortieren')">
+ {{ $gettext('Datum') }}
+ </a>
+ </th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr v-for="(reaction, index) in sortedReactions" :key="index">
+ <td>
+ <div class="user-reaction">
+ <UserAvatarDropdown
+ size="30px"
+ :user="{
+ id: reaction.user.id,
+ username: reaction.user.username,
+ name: reaction.user.formatted_name,
+ avatar_url: reaction.user.meta.avatar.medium
+ }"
+ />
+ <span class="emoji-icon" v-html="REACTION_ICONS[reaction.emoji].icon"></span>
+ <span class="sr-only">{{ emoji }}</span>
+ </div>
+ </td>
+ <td>
+ <a
+ :href="userProfileURL(reaction.user.username)"
+ :title="$gettext('Zum Profil')"
+ :aria-label="$gettext('Zum Profil von %{name}', { name: reaction.user.formatted_name })"
+ class="author-name"
+ >
+ {{ reaction.user.formatted_name }}
+ </a>
+ </td>
+ <td>
+ <StudipDateTime :iso="reaction.mkdate" :relative="true" />
+ </td>
+ </tr>
+ </tbody>
+ </table>
+</template>
diff --git a/resources/vue/components/forum/posts/PostReactions.vue b/resources/vue/components/forum/posts/PostReactions.vue
index 816ed15..49d6025 100644
--- a/resources/vue/components/forum/posts/PostReactions.vue
+++ b/resources/vue/components/forum/posts/PostReactions.vue
@@ -1,5 +1,5 @@
<script setup>
-import {computed, ref, useTemplateRef} from "vue";
+import {computed, reactive, ref, useTemplateRef} from "vue";
import {REACTION_ICONS} from "./reactions";
import {$gettext} from "@/assets/javascripts/lib/gettext";
import {numberFormatter} from "../../../../assets/javascripts/lib/number_formatter";
@@ -7,6 +7,8 @@ import useDetectOutsideClick from "../../../composables/useDetectOutsideClick";
import {useForumPost} from "../../../store/pinia/forum/ForumPost";
import {deserializeJSONAPIResponse} from "../../../../assets/javascripts/lib/jsonapiUtils";
import StudipIcon from "../../StudipIcon.vue";
+import PostReactionShow from "./PostReactionShow.vue";
+import StudipDialog from "../../StudipDialog.vue";
const forumDiscussionPost = useForumPost();
const props = defineProps({
@@ -20,6 +22,7 @@ const props = defineProps({
}
});
+
const showReactions = ref(false);
const reactionStatusMessage = ref(null);
@@ -87,6 +90,11 @@ const findUserReaction = (emoji, reactions = props.reactions) => reactions.find(
const reactionCreate = useTemplateRef('reactionCreate');
useDetectOutsideClick(reactionCreate, () => showReactions.value = false);
+
+const reactionShowDialog = reactive({
+ isOpen: false,
+ emoji: 'all'
+});
</script>
<template>
@@ -104,21 +112,31 @@ useDetectOutsideClick(reactionCreate, () => showReactions.value = false);
:title="findUserReaction(emoji, reaction) ? $gettext('Reaktion zurücknehmen') : $gettext('Reaktion hinzufügen')"
:aria-label="findUserReaction(emoji, reaction) ? $gettext('Reaktion zurücknehmen') : $gettext('Reaktion hinzufügen')"
@click="toggleReaction(emoji, reaction)">
- <span class="html-emoji" v-html="REACTION_ICONS[emoji].icon"></span>
+ <span class="emoji-icon" v-html="REACTION_ICONS[emoji].icon"></span>
<span>{{ numberFormatter(reaction.length, 1) }}</span>
</button>
</template>
</template>
<div ref="reactionCreate" class="post-reactions">
- <button
- class="post-reactions__create-button"
- type="button"
- :title="$gettext('Reagieren')"
- :aria-label="$gettext('Reagieren')"
- @click="showReactions = !showReactions">
- <StudipIcon shape="add-reaction" class="add-reaction-icon" :size="18" />
- <p>{{ numberFormatter(reactions.length, 1) }}</p>
- </button>
+ <div class="post-reactions__button-group">
+ <button
+ type="button"
+ class="post-reactions__add-reaction"
+ :title="$gettext('Reagieren')"
+ :aria-label="$gettext('Reagieren')"
+ @click="showReactions = !showReactions">
+ <StudipIcon shape="add-reaction" class="add-reaction-icon" :size="18" />
+ </button>
+ <button
+ v-if="reactions.length"
+ type="button"
+ class="post-reactions__show-reactions"
+ :title="$gettext('Reaktionen anzeigen')"
+ :aria-label="$gettext('%{count} Reaktionen anzeigen', { count: reactions.length })"
+ @click="reactionShowDialog.isOpen = true">
+ {{ numberFormatter(reactions.length, 1) }}
+ </button>
+ </div>
<Transition name="fade">
<div v-if="showReactions" class="post-reactions__container">
<template v-for="(emoji, index) in REACTION_ICONS" :key="index">
@@ -131,11 +149,64 @@ useDetectOutsideClick(reactionCreate, () => showReactions.value = false);
:aria-label="$gettext('Auf diesen Beitrag mit %{emojiName} reagieren', { emojiName: emoji.value })"
@click="toggleReaction(emoji.value)"
>
- <span class="html-emoji" v-html="emoji.icon"></span>
+ <span class="emoji-icon" v-html="emoji.icon"></span>
</button>
</template>
</div>
</Transition>
</div>
</div>
+
+ <StudipDialog
+ v-if="reactionShowDialog.isOpen && reactions.length"
+ :title="$gettext('Reaktionen anzeigen')"
+ :closeText="$gettext('Schließen')"
+ closeClass="cancel"
+ height="700"
+ width="600"
+ @close="reactionShowDialog.isOpen = false"
+ >
+ <template #dialogContent>
+ <div class="forum">
+ <div class="tab post-reactions-dialog">
+ <div class="tab__buttons" role="radiogroup" :aria-label="$gettext('Emoji-Filter')">
+ <div class="tab__button">
+ <input
+ type="radio"
+ id="reaction-all"
+ name="reaction-filter"
+ value="all"
+ v-model="reactionShowDialog.emoji"
+ />
+ <label for="reaction-all" :class="{ 'is-checked': reactionShowDialog.emoji === 'all' }">
+ {{ $gettext('Alle') }}
+ <span>{{ numberFormatter(reactions.length, 1) }}</span>
+ </label>
+ </div>
+ <div
+ v-for="(reaction, emoji) in groupedReactions"
+ :key="emoji"
+ class="tab__button"
+ >
+ <input
+ type="radio"
+ :id="`reaction-${emoji}`"
+ name="reaction-filter"
+ :value="emoji"
+ v-model="reactionShowDialog.emoji"
+ />
+ <label :for="`reaction-${emoji}`" :class="{ 'is-checked': reactionShowDialog.emoji === emoji }">
+ <span class="emoji-icon" v-html="REACTION_ICONS[emoji].icon" aria-hidden="true"></span>
+ <span class="sr-only">{{ emoji }}</span>
+ <span>{{ numberFormatter(reaction.length, 1) }}</span>
+ </label>
+ </div>
+ </div>
+ <div class="tab__content">
+ <PostReactionShow :reactions="reactions" :emoji="reactionShowDialog.emoji" />
+ </div>
+ </div>
+ </div>
+ </template>
+ </StudipDialog>
</template>
diff --git a/resources/vue/components/forum/topics/CreateTopic.vue b/resources/vue/components/forum/topics/CreateTopic.vue
index d6d3d5f..03b7542 100644
--- a/resources/vue/components/forum/topics/CreateTopic.vue
+++ b/resources/vue/components/forum/topics/CreateTopic.vue
@@ -28,7 +28,8 @@ const topicCreateURL = computed(() => {
data-dialog="width=700"
:title="$gettext('Neues Thema anlegen')"
:aria-label="$gettext('Neues Thema anlegen')"
- class="icon-button"
+ class="button button--icon-only"
+ :class="label ? 'button--icon-label' : 'button--icon-only'"
role="button"
>
<StudipIcon shape="add" :size="20" aria-hidden="true" />
diff --git a/resources/vue/components/forum/topics/TopicItem.vue b/resources/vue/components/forum/topics/TopicItem.vue
index d783524..a499941 100644
--- a/resources/vue/components/forum/topics/TopicItem.vue
+++ b/resources/vue/components/forum/topics/TopicItem.vue
@@ -70,7 +70,7 @@ const swapTopic = event => {
<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')">
- <h3 class="line-clamp-2">{{ topic.name }}</h3>
+ <span class="topic-title line-clamp-2">{{ topic.name }}</span>
<span
v-if="topic.meta.postings_count > topic.meta.user_read_index"
class="unread-items-badge"
@@ -173,9 +173,9 @@ const swapTopic = event => {
<div class="topic-card__body">
<div class="flex space-between">
<div class="flex items-start gap-10">
- <h3 class="topic-card__title line-clamp-2">
+ <span class="topic-card__title topic-title line-clamp-2">
{{ topic.name }}
- </h3>
+ </span>
<span
v-if="topic.meta.postings_count > topic.meta.user_read_index"
diff --git a/templates/personal_notifications/notification.php b/templates/personal_notifications/notification.php
index b45fc58..20f1f9d 100644
--- a/templates/personal_notifications/notification.php
+++ b/templates/personal_notifications/notification.php
@@ -5,7 +5,7 @@
<? if (filter_var($notification['avatar'], FILTER_VALIDATE_URL)): ?>
<div class="avatar" style="background-color: currentColor; mask: url(<?= $notification['avatar'] ?>) no-repeat center / contain;;"></div>
<? else: ?>
- <div class="html-emoji">
+ <div class="emoji-icon">
<?= $notification['avatar'] ?>
</div>
<? endif ?>