aboutsummaryrefslogtreecommitdiff
path: root/resources
diff options
context:
space:
mode:
authorMurtaza Sultani <sultani@data-quest.de>2025-10-20 15:31:57 +0200
committerRasmus Fuhse <fuhse@data-quest.de>2025-10-20 13:31:57 +0000
commit93d6f8ea1adef72ddfe46e7508031938d172249a (patch)
tree4508c11c16bdb86da9f151925b1786e1b331d32f /resources
parentaa7e597f94d34d352551e6963014f9a26e3e03a0 (diff)
Resolve "Vue-Komponenten UserAvatar und UserAvatarDropdown als globale Komponenten registrieren"
Closes #5924 Merge request studip/studip!4526
Diffstat (limited to 'resources')
-rw-r--r--resources/assets/javascripts/bootstrap/forms.js14
-rw-r--r--resources/assets/javascripts/bootstrap/use-vue-components.js18
-rw-r--r--resources/assets/javascripts/entry-base.js1
-rw-r--r--resources/assets/stylesheets/studip.scss34
-rw-r--r--resources/vue/base-components.js2
-rw-r--r--resources/vue/components/StudipUserAvatar.vue38
-rw-r--r--resources/vue/components/avatar/UserAvatar.vue (renamed from resources/vue/components/UserAvatar.vue)6
-rw-r--r--resources/vue/components/avatar/UserAvatarDropdown.vue (renamed from resources/vue/components/forum/UserAvatarDropdown.vue)10
-rw-r--r--resources/vue/components/courseware/tasks/peer-review/PeerReviewListItem.vue40
-rw-r--r--resources/vue/components/forum/ForumMembers.vue8
-rw-r--r--resources/vue/components/forum/posts/Post.vue2
-rw-r--r--resources/vue/components/forum/posts/PostReactionShow.vue2
12 files changed, 94 insertions, 81 deletions
diff --git a/resources/assets/javascripts/bootstrap/forms.js b/resources/assets/javascripts/bootstrap/forms.js
index dc57674..d22b15a 100644
--- a/resources/assets/javascripts/bootstrap/forms.js
+++ b/resources/assets/javascripts/bootstrap/forms.js
@@ -435,20 +435,6 @@ STUDIP.ready(function () {
});
}
- /*
- * Form elements with the "simplevue" class are meant for forms that just need some vue components
- * to do something fancy inside the form but which do not need the full functionality of the form builder.
- */
- let simple_vue_items = document.querySelectorAll('form .simplevue:not(.vueified)');
- if (simple_vue_items.length > 0) {
- STUDIP.Vue.load().then(({createApp}) => {
- simple_vue_items.forEach(f => {
- f.classList.add('vueified');
- createApp().mount(f);
- });
- });
- }
-
// Well, this is really nasty: Select2 can't determine the select
// element's width if it is hidden (by itself or by its parent).
// This is due to the fact that elements are not rendered when hidden
diff --git a/resources/assets/javascripts/bootstrap/use-vue-components.js b/resources/assets/javascripts/bootstrap/use-vue-components.js
new file mode 100644
index 0000000..e262899
--- /dev/null
+++ b/resources/assets/javascripts/bootstrap/use-vue-components.js
@@ -0,0 +1,18 @@
+STUDIP.ready(function () {
+ const selectors = [
+ '.use-vue-components',
+ 'form .simplevue'
+ ];
+ const selector = selectors.map(selector => `${selector}:not(.vueified)`).join(',');
+
+ const containers = document.querySelectorAll(selector);
+
+ if (containers.length > 0) {
+ STUDIP.Vue.load().then(({ createApp }) => {
+ containers.forEach(container => {
+ container.classList.add('vueified');
+ createApp().mount(container)
+ });
+ });
+ }
+});
diff --git a/resources/assets/javascripts/entry-base.js b/resources/assets/javascripts/entry-base.js
index 896189e..02fd471 100644
--- a/resources/assets/javascripts/entry-base.js
+++ b/resources/assets/javascripts/entry-base.js
@@ -78,6 +78,7 @@ import "./bootstrap/courseware.js"
import "./bootstrap/external_pages.js"
import "./bootstrap/vips.js"
import "./bootstrap/admission.js"
+import "./bootstrap/use-vue-components.js"
import "./mvv_course_wizard.js"
import "./mvv.js"
diff --git a/resources/assets/stylesheets/studip.scss b/resources/assets/stylesheets/studip.scss
index f6c8e9e..2bd65b9 100644
--- a/resources/assets/stylesheets/studip.scss
+++ b/resources/assets/stylesheets/studip.scss
@@ -588,7 +588,8 @@ div.info { padding-left: 1%; }
}
// course members
-a.new-member {
+a.new-member,
+.new-member .user-avatar-dropdown__username {
@include icon(after, star, attention, 8px);
}
@@ -654,6 +655,24 @@ input.allow-plaintext-toggle {
}
}
+.users-table,
+.scores-table {
+ &__avatar-container {
+ display: inline-flex;
+ align-items: center;
+ gap: 5px;
+ }
+}
+
+.scores-table {
+ &__avatar-container {
+ .dropdown__content {
+ left: 0;
+ right: auto;
+ }
+ }
+}
+
.links-preview {
&__item {
border: solid 1px $color--content-box-border;
@@ -827,6 +846,10 @@ input.allow-plaintext-toggle {
hr {
margin: 10px 0;
+ border-top: 1px solid var(--color--divider);
+ border-bottom: none;
+ border-left: none;
+ border-right: none;
}
&__header {
@@ -844,6 +867,7 @@ input.allow-plaintext-toggle {
.user-info {
flex: 1;
+ margin-right: 20px;
.user-name {
text-wrap: auto;
@@ -891,13 +915,19 @@ input.allow-plaintext-toggle {
}
}
+.user-avatar-container {
+ width: max-content;
+ display: inline-flex;
+ align-items: center;
+ gap: 5px;
+}
+
.user-avatar-dropdown {
&__preview {
background: none;
border: none;
padding: 0;
cursor: pointer;
- display: flex;
&:hover,
&:focus,
diff --git a/resources/vue/base-components.js b/resources/vue/base-components.js
index 2524642..904afa1 100644
--- a/resources/vue/base-components.js
+++ b/resources/vue/base-components.js
@@ -34,6 +34,8 @@ const BaseComponents = {
StudipTooltipIcon: defineAsyncComponent(() => import('./components/StudipTooltipIcon.vue')),
StudipWysiwyg: defineAsyncComponent(() => import('./components/StudipWysiwyg.vue')),
UserFilterInput: defineAsyncComponent(() => import('./components/form_inputs/UserFilterInput.vue')),
+ UserAvatar: defineAsyncComponent(() => import('./components/avatar/UserAvatar.vue')),
+ UserAvatarDropdown: defineAsyncComponent(() => import('./components/avatar/UserAvatarDropdown.vue')),
};
export default BaseComponents;
diff --git a/resources/vue/components/StudipUserAvatar.vue b/resources/vue/components/StudipUserAvatar.vue
deleted file mode 100644
index 1020839..0000000
--- a/resources/vue/components/StudipUserAvatar.vue
+++ /dev/null
@@ -1,38 +0,0 @@
-<template>
- <div class="studip-user-avatar" :class="{ 'studip-user-avatar-small': small }">
- <span>
- <img :src="avatarUrl" role="presentation" />
- </span>
- <span>{{ formattedName }}</span>
- </div>
-</template>
-
-<script>
-export default {
- props: {
- avatarUrl: {
- type: String,
- required: true,
- },
- formattedName: {
- type: String,
- required: true,
- },
- small: {
- type: Boolean,
- default: false,
- },
- },
-};
-</script>
-
-<style scoped>
-.studip-user-avatar {
- align-items: center;
- display: flex;
- gap: 0.25rem;
-}
-.studip-user-avatar-small img {
- width: 1em;
-}
-</style>
diff --git a/resources/vue/components/UserAvatar.vue b/resources/vue/components/avatar/UserAvatar.vue
index 91153e9..def6719 100644
--- a/resources/vue/components/UserAvatar.vue
+++ b/resources/vue/components/avatar/UserAvatar.vue
@@ -1,6 +1,6 @@
<script setup>
-import {$gettext} from "../../assets/javascripts/lib/gettext";
-import StudipIcon from "./StudipIcon.vue";
+import {$gettext} from "@/assets/javascripts/lib/gettext";
+import StudipIcon from "@/vue/components/StudipIcon.vue";
const props = defineProps({
user: {
@@ -53,6 +53,7 @@ const openBlubberChat = () => {
<ul class="user-avatar__actions">
<li>
<button
+ type="button"
v-if="user.id !== AUTH_ID"
@click="openBlubberChat"
class="action-item button-base"
@@ -76,6 +77,7 @@ const openBlubberChat = () => {
</li>
<li>
<button
+ type="button"
v-if="user.id !== AUTH_ID"
class="action-item button-base"
:title="$gettext('Nachricht schreiben')"
diff --git a/resources/vue/components/forum/UserAvatarDropdown.vue b/resources/vue/components/avatar/UserAvatarDropdown.vue
index 82f693d..6706990 100644
--- a/resources/vue/components/forum/UserAvatarDropdown.vue
+++ b/resources/vue/components/avatar/UserAvatarDropdown.vue
@@ -1,7 +1,6 @@
<script setup>
-import {$gettext} from "../../../assets/javascripts/lib/gettext";
-import Dropdown from "../Dropdown.vue";
-import UserAvatar from "../UserAvatar.vue";
+import Dropdown from "@/vue/components/Dropdown.vue";
+import UserAvatar from "@/vue/components/avatar/UserAvatar.vue";
defineProps({
user: {
@@ -24,14 +23,15 @@ const isOpen = defineModel({ default: false });
<Dropdown class="user-avatar-dropdown" v-model="isOpen">
<template #trigger>
<button
- class="user-avatar-dropdown__preview"
+ class="user-avatar-dropdown__preview button-base"
+ type="button"
@click="isOpen = !isOpen"
v-bind="$attrs"
:class="{
'active': isOpen
}"
:title="label ?? user.name"
- :aria-label="label ?? $gettext('vCard')"
+ :aria-label="label ?? user.name"
:aria-pressed="isOpen"
>
<img class="user-profile" :src="user.avatar_url" :style="{ width: size, height: size }" :alt="user.name" />
diff --git a/resources/vue/components/courseware/tasks/peer-review/PeerReviewListItem.vue b/resources/vue/components/courseware/tasks/peer-review/PeerReviewListItem.vue
index d24616e..ad7aec9 100644
--- a/resources/vue/components/courseware/tasks/peer-review/PeerReviewListItem.vue
+++ b/resources/vue/components/courseware/tasks/peer-review/PeerReviewListItem.vue
@@ -6,25 +6,37 @@
</a>
</td>
<td>
- <a v-if="isUser(submitter)" :href="userProfile(submitter)">
- <UserAvatar
- :avatar-url="submitter.meta.avatar.small"
- :formatted-name="submitter.attributes['formatted-name']"
- small
+ <div v-if="isUser(submitter)" class="user-avatar-container">
+ <UserAvatarDropdown
+ :user="{
+ id: submitter.id,
+ avatar_url: submitter.meta.avatar.small,
+ username: submitter.attributes['username'],
+ name: submitter.attributes['formatted-name']
+ }"
/>
- </a>
+ <a :href="userProfile(submitter)">
+ {{ submitter.attributes['formatted-name'] }}
+ </a>
+ </div>
<a v-else :href="statusGroupUrl(submitter)">
{{ submitter.attributes.name }}
</a>
</td>
<td>
- <a v-if="isUser(reviewer)" :href="userProfile(reviewer)">
- <UserAvatar
- :avatar-url="reviewer.meta.avatar.small"
- :formatted-name="reviewer.attributes['formatted-name']"
- small
+ <div v-if="isUser(reviewer)" class="user-avatar-container">
+ <UserAvatarDropdown
+ :user="{
+ id: reviewer.id,
+ avatar_url: reviewer.meta.avatar.small,
+ username: reviewer.attributes['username'],
+ name: reviewer.attributes['formatted-name']
+ }"
/>
- </a>
+ <a :href="userProfile(reviewer)">
+ {{ reviewer.attributes['formatted-name'] }}
+ </a>
+ </div>
<a v-else :href="statusGroupUrl(reviewer)">
{{ reviewer.attributes.name }}
</a>
@@ -51,9 +63,9 @@
<script>
import { mapGetters } from 'vuex';
import StudipDate from '@/vue/components/StudipDate.vue';
-import UserAvatar from '@/vue/components/StudipUserAvatar.vue';
import taskHelper from '../../../../mixins/courseware/task-helper.js';
import { getProcessStatus, ProcessStatus } from './definitions';
+import UserAvatarDropdown from "@/vue/components/avatar/UserAvatarDropdown.vue";
export default {
mixins: [taskHelper],
@@ -71,7 +83,7 @@ export default {
required: true,
},
},
- components: { StudipDate, UserAvatar },
+ components: { StudipDate, UserAvatarDropdown },
computed: {
...mapGetters({
context: 'context',
diff --git a/resources/vue/components/forum/ForumMembers.vue b/resources/vue/components/forum/ForumMembers.vue
index 7e0e385..3258bff 100644
--- a/resources/vue/components/forum/ForumMembers.vue
+++ b/resources/vue/components/forum/ForumMembers.vue
@@ -1,10 +1,10 @@
<script setup>
import {computed, ref} from "vue";
import {$gettext} from "../../../assets/javascripts/lib/gettext";
-import UserAvatarDropdown from "./UserAvatarDropdown.vue";
import Dropdown from "../Dropdown.vue";
import StudipIcon from "@/vue/components/StudipIcon.vue";
-import UserAvatar from "../UserAvatar.vue";
+import UserAvatar from "@/vue/components/avatar/UserAvatar.vue";
+import UserAvatarDropdown from "@/vue/components/avatar/UserAvatarDropdown.vue";
const props = defineProps({
members: {
@@ -110,7 +110,7 @@ const isModerator = role => role === 'moderator';
@click="activeUserAvatar = user.id"
:title="$gettext('Aufklappen')"
:aria-label="$gettext('Aufklappen')"
- class="show-avatar">
+ class="show-avatar button-base">
<StudipIcon shape="arr_1down" :size="15" aria-hidden="true" />
</button>
</div>
@@ -119,7 +119,7 @@ const isModerator = role => role === 'moderator';
@click="activeUserAvatar = ''"
:title="$gettext('Zuklappen')"
:aria-label="$gettext('Zuklappen')"
- class="hide-avatar">
+ class="hide-avatar button-base">
<StudipIcon shape="arr_1up" :size="15" aria-hidden="true" />
</button>
<UserAvatar v-if="activeUserAvatar === user.id" :user="user" />
diff --git a/resources/vue/components/forum/posts/Post.vue b/resources/vue/components/forum/posts/Post.vue
index 48921aa..16332fb 100644
--- a/resources/vue/components/forum/posts/Post.vue
+++ b/resources/vue/components/forum/posts/Post.vue
@@ -3,6 +3,7 @@ import {computed, ref, useTemplateRef} from "vue";
import PostEditForm from "./PostEditForm.vue";
import PostCreateForm from "./PostCreateForm.vue";
import PostContent from "@/vue/components/forum/posts/PostContent.vue";
+import UserAvatarDropdown from "@/vue/components/avatar/UserAvatarDropdown.vue";
import PostReactions from "./PostReactions.vue";
import {useForumPost} from "../../../store/pinia/forum/ForumPost";
import {getDiscussionURL} from "@/vue/components/forum/helpers/urls";
@@ -10,7 +11,6 @@ import StudipDateTime from "@/vue/components/StudipDateTime.vue";
import StudipIcon from "@/vue/components/StudipIcon.vue";
import {$gettext} from "@/assets/javascripts/lib/gettext";
import LinksPreview from "@/vue/components/LinksPreview.vue";
-import UserAvatarDropdown from "../UserAvatarDropdown.vue";
import {userProfileURL} from "../helpers/urls";
import {useForumConfig} from "../../../store/pinia/forum/ForumConfig";
diff --git a/resources/vue/components/forum/posts/PostReactionShow.vue b/resources/vue/components/forum/posts/PostReactionShow.vue
index 8ad13a2..e5790b6 100644
--- a/resources/vue/components/forum/posts/PostReactionShow.vue
+++ b/resources/vue/components/forum/posts/PostReactionShow.vue
@@ -1,7 +1,7 @@
<script setup>
import {$gettext} from "../../../../assets/javascripts/lib/gettext";
import StudipDateTime from "../../StudipDateTime.vue";
-import UserAvatarDropdown from "../UserAvatarDropdown.vue";
+import UserAvatarDropdown from "@/vue/components/avatar/UserAvatarDropdown.vue";
import {REACTION_ICONS} from "./reactions";
import {userProfileURL} from "../helpers/urls";
import {computed, onMounted} from "vue";