From 1c55c6f41a32a1aa830b29798d9afd1fb9e3d8dc Mon Sep 17 00:00:00 2001
From: Jan-Hendrik Willms <tleilax+studip@gmail.com>
Date: Wed, 19 Oct 2022 11:32:37 +0000
Subject: [PATCH] lint .vue files as well, fixes #1696

Closes #1696

Merge request studip/studip!772
---
 .eslintrc.json                                |  10 +-
 package.json                                  |   2 +-
 resources/assets/javascripts/bootstrap/oer.js |  18 ++--
 resources/assets/javascripts/lib/blubber.js   |  38 ++++---
 resources/assets/javascripts/lib/files.js     |  24 +++--
 resources/assets/javascripts/lib/oer.js       |  22 ++--
 .../vue/components/BlubberGlobalstream.vue    |  19 ++--
 resources/vue/components/BlubberThread.vue    | 101 +++++++++---------
 .../vue/components/BlubberThreadWidget.vue    |   9 +-
 .../vue/components/CacheAdministration.vue    |   8 +-
 resources/vue/components/Datetimepicker.vue   |   2 +-
 resources/vue/components/EditableList.vue     |  38 +++----
 resources/vue/components/FilesTable.vue       |  49 +++++----
 resources/vue/components/I18nTextarea.vue     |  37 ++++---
 .../vue/components/MyCoursesColorPicker.vue   |   2 +-
 .../vue/components/MyCoursesNavigation.vue    |   2 +-
 resources/vue/components/MyCoursesTables.vue  |   2 +-
 resources/vue/components/MyCoursesTiles.vue   |   8 +-
 resources/vue/components/StudipDialog.vue     |   3 +-
 resources/vue/components/StudipFileSize.vue   |   2 +
 resources/vue/components/StudipMessageBox.vue |   6 +-
 .../CoursewareAccordionContainer.vue          |   4 +-
 .../courseware/CoursewareActionWidget.vue     |   2 -
 .../CoursewareBiographyAchievementsBlock.vue  |   2 -
 .../CoursewareBiographyGoalsBlock.vue         |   2 +
 ...ewareBiographyPersonalInformationBlock.vue |   2 -
 .../courseware/CoursewareChartBlock.vue       |   2 +-
 .../courseware/CoursewareContentBookmarks.vue |   1 +
 .../CoursewareContentOverviewElements.vue     |   4 +-
 .../CoursewareContentPermissions.vue          |   1 +
 .../courseware/CoursewareDefaultBlock.vue     |   8 --
 .../courseware/CoursewareDefaultContainer.vue |   1 -
 .../courseware/CoursewareDialogCardsBlock.vue |   6 +-
 .../courseware/CoursewareHeadlineBlock.vue    |   8 +-
 .../courseware/CoursewareImageMapBlock.vue    |  14 +--
 .../courseware/CoursewareKeyPointBlock.vue    |   5 +-
 .../CoursewareManagerCopySelector.vue         |   6 +-
 .../courseware/CoursewareManagerElement.vue   |   5 +-
 .../CoursewareManagerLinkSelector.vue         |   8 +-
 .../CoursewareStructuralElement.vue           |  16 +--
 .../components/courseware/CoursewareTabs.vue  |   8 +-
 .../courseware/CoursewareTabsContainer.vue    |   4 +-
 .../courseware/CoursewareTimelineBlock.vue    |   6 +-
 .../courseware/CoursewareToolsAdmin.vue       |   1 -
 44 files changed, 269 insertions(+), 249 deletions(-)

diff --git a/.eslintrc.json b/.eslintrc.json
index d9a963e6315..55c17a0dfa1 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -1,13 +1,16 @@
 {
-    "parser": "@babel/eslint-parser",
     "parserOptions": {
+        "parser": "@babel/eslint-parser",
         "sourceType": "module"
     },
     "env": {
         "browser": true,
         "es6": true
     },
-    "extends": "eslint:recommended",
+    "extends": [
+        "eslint:recommended",
+        "plugin:vue/essential"
+    ],
     "globals": {
         "STUDIP": "writable",
         "CKEDITOR": "writable",
@@ -15,6 +18,9 @@
         "_": "writable",
         "jQuery": "writable"
     },
+    "plugins": [
+        "vue"
+    ],
     "rules": {
         "no-unused-vars": "off",
 
diff --git a/package.json b/package.json
index 18f37b309e3..7d8898bebd5 100644
--- a/package.json
+++ b/package.json
@@ -4,7 +4,7 @@
     "description": "Stud.IP",
     "private": true,
     "scripts": {
-        "lint": "eslint resources/assets/javascripts resources/vue",
+        "lint": "eslint --ext .js,.vue resources/assets/javascripts resources/vue",
         "css-lint": "stylelint resources/assets/stylesheets",
         "webpack-dev": "webpack --config webpack.dev.js --mode development",
         "webpack-prod": "webpack --config webpack.prod.js --mode production",
diff --git a/resources/assets/javascripts/bootstrap/oer.js b/resources/assets/javascripts/bootstrap/oer.js
index eff927f4797..2b471496a64 100644
--- a/resources/assets/javascripts/bootstrap/oer.js
+++ b/resources/assets/javascripts/bootstrap/oer.js
@@ -57,14 +57,16 @@ STUDIP.ready(() => {
         STUDIP.Vue.load().then(({createApp}) => {
             STUDIP.OER.EditApp = createApp({
                 el: '.oercampus_editmaterial',
-                data: {
-                    name: $('.oercampus_editmaterial input.oername').val(),
-                    logo_url: $('.oercampus_editmaterial .logo_file').data("oldurl"),
-                    customlogo: $('.oercampus_editmaterial .logo_file').data("customlogo"),
-                    filename: $('.oercampus_editmaterial .file.drag-and-drop').data("filename"),
-                    filesize: $('.oercampus_editmaterial .file.drag-and-drop').data("filesize"),
-                    tags: $('.oercampus_editmaterial .oer_tags').data("defaulttags"),
-                    minimumTags: 5
+                data() {
+                    return {
+                        name: $('.oercampus_editmaterial input.oername').val(),
+                        logo_url: $('.oercampus_editmaterial .logo_file').data("oldurl"),
+                        customlogo: $('.oercampus_editmaterial .logo_file').data("customlogo"),
+                        filename: $('.oercampus_editmaterial .file.drag-and-drop').data("filename"),
+                        filesize: $('.oercampus_editmaterial .file.drag-and-drop').data("filesize"),
+                        tags: $('.oercampus_editmaterial .oer_tags').data("defaulttags"),
+                        minimumTags: 5
+                    };
                 },
                 mounted: function () {
                     jQuery("#difficulty_slider_edit").slider({
diff --git a/resources/assets/javascripts/lib/blubber.js b/resources/assets/javascripts/lib/blubber.js
index 6a72382e7b6..777f221b52b 100644
--- a/resources/assets/javascripts/lib/blubber.js
+++ b/resources/assets/javascripts/lib/blubber.js
@@ -19,13 +19,15 @@ const Blubber = {
             STUDIP.Vue.load().then(({createApp}) => {
                 STUDIP.Blubber.App = createApp({
                     el: '#layout_container',
-                    data: {
-                        threads: $('.blubber_threads_widget').data('threads_data'),
-                        thread_data: panel_data.thread_data,
-                        active_thread: panel_data.active_thread,
-                        threads_more_down: panel_data.threads_more_down,
-                        waiting: false,
-                        display_context_posting: 0
+                    data() {
+                        return {
+                            threads: $('.blubber_threads_widget').data('threads_data'),
+                            thread_data: panel_data.thread_data,
+                            active_thread: panel_data.active_thread,
+                            threads_more_down: panel_data.threads_more_down,
+                            waiting: false,
+                            display_context_posting: 0
+                        };
                     },
                     methods: {
                         changeActiveThread: function (thread_id) {
@@ -96,13 +98,15 @@ const Blubber = {
                 STUDIP.Vue.load().then(({createApp}) => {
                     createApp({
                         el: this,
-                        data: {
-                            threads: panel_data.threads_data,
-                            thread_data: panel_data.thread_data,
-                            active_thread: panel_data.active_thread,
-                            threads_more_down: panel_data.threads_more_down,
-                            waiting: false,
-                            display_context_posting: 0
+                        data () {
+                            return {
+                                threads: panel_data.threads_data,
+                                thread_data: panel_data.thread_data,
+                                active_thread: panel_data.active_thread,
+                                threads_more_down: panel_data.threads_more_down,
+                                waiting: false,
+                                display_context_posting: 0
+                            };
                         },
                         components,
                     });
@@ -186,8 +190,10 @@ const Blubber = {
                 let components = STUDIP.Blubber.components;
                 return createApp({
                     el: '#blubber_contact_ids',
-                    data: {
-                        users: []
+                    data () {
+                        return {
+                            users: []
+                        };
                     },
                     methods: {
                         addUser: function (user_id, name) {
diff --git a/resources/assets/javascripts/lib/files.js b/resources/assets/javascripts/lib/files.js
index 9d06a18ea15..c013107ec23 100644
--- a/resources/assets/javascripts/lib/files.js
+++ b/resources/assets/javascripts/lib/files.js
@@ -10,11 +10,13 @@ const Files = {
             STUDIP.Vue.load().then(({createApp}) => {
                 this.filesapp = createApp({
                     el: "#layout_content",
-                    data: {
-                        "files":       jQuery("#files_table_form").data("files") || [],
-                        "folders":     jQuery("#files_table_form").data("folders") || [],
-                        "topfolder":   jQuery("#files_table_form").data("topfolder"),
-                        "breadcrumbs": jQuery("#files_table_form").data("breadcrumbs") || []
+                    data() {
+                        return {
+                            files: jQuery("#files_table_form").data("files") || [],
+                            folders: jQuery("#files_table_form").data("folders") || [],
+                            topfolder: jQuery("#files_table_form").data("topfolder"),
+                            breadcrumbs: jQuery("#files_table_form").data("breadcrumbs") || []
+                        };
                     },
                     methods: {
                         hasFilesOfType (type) {
@@ -45,11 +47,13 @@ const Files = {
                 STUDIP.Vue.load().then(({createApp}) => {
                     createApp({
                         el: table,
-                        data: {
-                            "files":       jQuery(table).data("files") || [],
-                            "folders":     jQuery(table).data("folders") || [],
-                            "topfolder":   jQuery(table).data("topfolder"),
-                            "breadcrumbs": jQuery(table).data("breadcrumbs") || []
+                        data() {
+                            return {
+                                files: jQuery(table).data("files") || [],
+                                folders: jQuery(table).data("folders") || [],
+                                topfolder: jQuery(table).data("topfolder"),
+                                breadcrumbs: jQuery(table).data("breadcrumbs") || []
+                            };
                         },
                         components: { FilesTable, },
                     });
diff --git a/resources/assets/javascripts/lib/oer.js b/resources/assets/javascripts/lib/oer.js
index dac19ec56ad..0542a1e5f36 100644
--- a/resources/assets/javascripts/lib/oer.js
+++ b/resources/assets/javascripts/lib/oer.js
@@ -37,16 +37,18 @@ const OER = {
         STUDIP.Vue.load().then(({createApp}) => {
             STUDIP.OER.Search = createApp({
                 el: ".oer_search",
-                data: {
-                    browseMode: false,
-                    tags: $(".oer_search").data("tags"),
-                    tagHistory: [],
-                    searchtext: "",
-                    activeFilterPanel: false,
-                    difficulty: [1, 12],
-                    category: null,
-                    results: false,
-                    material_select_url_template: $(".oer_search").data("material_select_url_template")
+                data() {
+                    return {
+                        browseMode: false,
+                        tags: $(".oer_search").data("tags"),
+                        tagHistory: [],
+                        searchtext: "",
+                        activeFilterPanel: false,
+                        difficulty: [1, 12],
+                        category: null,
+                        results: false,
+                        material_select_url_template: $(".oer_search").data("material_select_url_template")
+                    };
                 },
                 methods: {
                     sync_search_text: function () {
diff --git a/resources/vue/components/BlubberGlobalstream.vue b/resources/vue/components/BlubberGlobalstream.vue
index 0c4976c887f..548e1fd57ba 100644
--- a/resources/vue/components/BlubberGlobalstream.vue
+++ b/resources/vue/components/BlubberGlobalstream.vue
@@ -3,7 +3,7 @@
         <div class="scrollable_area" v-scroll>
             <blubber-public-composer></blubber-public-composer>
             <ol class="postings" aria-live="polite">
-                <li class="more" v-if="stream_data.more_up">
+                <li class="more" v-if="streamData.more_up">
                     <studip-asset-img file="ajax-indicator-black.svg" width="20"></studip-asset-img>
                 </li>
 
@@ -37,7 +37,8 @@
         name: 'blubber-globalstream',
         data: function () {
             return {
-                already_loading_down: 0
+                already_loading_down: 0,
+                streamData: this.stream_data,
             };
         },
         props: ['stream_data', 'more_down'],
@@ -52,14 +53,14 @@
             addPosting: function (posting) {
                 let exists = false;
                 for (let i in this.stream_data) {
-                    if (this.stream_data[i].thread_id === posting.thread_id) {
+                    if (this.streamData[i].thread_id === posting.thread_id) {
                         exists = true;
                         return;
                     }
                 }
                 if (!exists) {
                     posting.class = posting.class + " new";
-                    this.stream_data.push(posting);
+                    this.streamData.push(posting);
                     this.$nextTick(() => {
                         STUDIP.Markup.element($(this.$el).find(`.postings > li[data-thread_id="${posting.thread_id}"]`));
                     });
@@ -74,8 +75,8 @@
             });
         },
         computed: {
-            sortedPostings: function () {
-                return this.stream_data.sort((a, b) => b.mkdate - a.mkdate);
+            sortedPostings() {
+                return [...this.streamData].sort((a, b) => b.mkdate - a.mkdate);
             }
         },
         directives: {
@@ -94,9 +95,9 @@
                             stream.already_loading_down = 1;
 
                             let earliest_mkdate = null;
-                            for (let i in stream.stream_data) {
-                                if ((earliest_mkdate === null) || stream.stream_data[i].mkdate < earliest_mkdate) {
-                                    earliest_mkdate = stream.stream_data[i].mkdate;
+                            for (let i in stream.streamData) {
+                                if ((earliest_mkdate === null) || stream.streamData[i].mkdate < earliest_mkdate) {
+                                    earliest_mkdate = stream.streamData[i].mkdate;
                                 }
                             }
                             //load older comments
diff --git a/resources/vue/components/BlubberThread.vue b/resources/vue/components/BlubberThread.vue
index bb742feaa7d..a3a5d7027a4 100644
--- a/resources/vue/components/BlubberThread.vue
+++ b/resources/vue/components/BlubberThread.vue
@@ -1,13 +1,13 @@
 <template>
     <div class="blubber_thread" :class="{dragover: dragging}"
-         :id="'blubberthread_' + thread_data.thread_posting.thread_id"
+         :id="'blubberthread_' + threadData.thread_posting.thread_id"
          @dragover.prevent="dragover" @dragleave.prevent="dragleave"
          @drop.prevent="upload">
-        <div class="responsive-visible context_info" v-if="thread_data.notifications">
+        <div class="responsive-visible context_info" v-if="threadData.notifications">
             <a href="#"
                @click.prevent="toggleFollow()"
                class="followunfollow"
-               :class="{unfollowed: !thread_data.followed}"
+               :class="{unfollowed: !threadData.followed}"
                :title="$gettext('Benachrichtigungen für diese Konversation abstellen.')"
                :data-thread_id="thread_data.thread_posting.thread_id">
                 <StudipIcon shape="decline" :size="20" class="follow text-bottom"></StudipIcon>
@@ -17,23 +17,23 @@
         </div>
         <div class="scrollable_area" v-scroll>
             <div class="all_content">
-                <div class="thread_posting" v-if="hasContent(thread_data.thread_posting.content)">
+                <div class="thread_posting" v-if="hasContent(threadData.thread_posting.content)">
                     <div class="contextinfo">
-                        <studip-date-time :timestamp="thread_data.thread_posting.mkdate" :relative="true"></studip-date-time>
-                        <a :href="getUserProfileURL(thread_data.thread_posting.user_id, thread_data.thread_posting.user_username)">{{ thread_data.thread_posting.user_name }}</a>
-                        <a :href="getUserProfileURL(thread_data.thread_posting.user_id, thread_data.thread_posting.user_username)" class="avatar" :style="{ backgroundImage: 'url(' + thread_data.thread_posting.avatar + ')' }"></a>
+                        <studip-date-time :timestamp="threadData.thread_posting.mkdate" :relative="true"></studip-date-time>
+                        <a :href="getUserProfileURL(threadData.thread_posting.user_id, threadData.thread_posting.user_username)">{{ threadData.thread_posting.user_name }}</a>
+                        <a :href="getUserProfileURL(threadData.thread_posting.user_id, threadData.thread_posting.user_username)" class="avatar" :style="{ backgroundImage: 'url(' + threadData.thread_posting.avatar + ')' }"></a>
                     </div>
-                    <div class="content" v-html="thread_data.thread_posting.html"></div>
+                    <div class="content" v-html="threadData.thread_posting.html"></div>
                     <div class="link_to_comments"></div>
                 </div>
 
-                <div v-if="!hasContent(thread_data.thread_posting.content) && !thread_data.comments.length" class="empty_blubber_background">
+                <div v-if="!hasContent(threadData.thread_posting.content) && !threadData.comments.length" class="empty_blubber_background">
                     <div v-translate>Starte die Konversation jetzt!</div>
                 </div>
 
                 <ol class="comments" aria-live="polite">
 
-                    <li class="more" v-if="thread_data.more_up">
+                    <li class="more" v-if="threadData.more_up">
                         <studip-asset-img file="ajax-indicator-black.svg" width="20"></studip-asset-img>
                     </li>
 
@@ -61,14 +61,14 @@
                         </div>
                     </li>
 
-                    <li class="more" v-if="thread_data.more_down">
+                    <li class="more" v-if="threadData.more_down">
                         <studip-asset-img file="ajax-indicator-black.svg" width="20"></studip-asset-img>
                     </li>
 
                 </ol>
             </div>
         </div>
-        <div class="writer" v-if="thread_data.thread_posting.commentable">
+        <div class="writer" v-if="threadData.thread_posting.commentable">
             <studip-icon shape="blubber" size="30" role="info"></studip-icon>
             <textarea :placeholder="writerTextareaPlaceholder"
                       @keyup.enter.exact="submit"
@@ -93,7 +93,8 @@
             return {
                 already_loading_up: 0,
                 already_loading_down: 0,
-                dragging: false
+                dragging: false,
+                threadData: this.thread_data
             };
         },
         props: ['thread_data'],
@@ -102,9 +103,9 @@
                 if (!text || typeof text !== "string") {
                     text = $(this.$el).find(".writer textarea").val();
                     $(this.$el).find(".writer textarea").val("");
-                    if (this.thread_data.thread_posting.thread_id) {
+                    if (this.threadData.thread_posting.thread_id) {
                         sessionStorage.removeItem(
-                            'BlubberMemory-Writer-' + this.thread_data.thread_posting.thread_id
+                            'BlubberMemory-Writer-' + this.threadData.thread_posting.thread_id
                         );
                     }
                 }
@@ -126,14 +127,14 @@
                 let thread = this;
 
                 //AJAX-Request ...
-                STUDIP.api.POST(`blubber/threads/${this.thread_data.thread_posting.thread_id}/comments`, {
+                STUDIP.api.POST(`blubber/threads/${this.threadData.thread_posting.thread_id}/comments`, {
                     data: {
                         content: text
                     }
                 }).then(data => {
                     // Check following state
-                    if (this.thread_data.notifications) {
-                        STUDIP.api.GET(`blubber/threads/${this.thread_data.thread_posting.thread_id}/follow`).then(followed => {
+                    if (this.threadData.notifications) {
+                        STUDIP.api.GET(`blubber/threads/${this.threadData.thread_posting.thread_id}/follow`).then(followed => {
                             jQuery('.followunfollow').toggleClass('unfollowed', !followed);
                         });
                     }
@@ -158,9 +159,9 @@
             },
             saveCommentToSession (event) {
                 let value = event.target.value;
-                if (this.thread_data.thread_posting.thread_id) {
+                if (this.threadData.thread_posting.thread_id) {
                     sessionStorage.setItem(
-                        `BlubberMemory-Writer-${this.thread_data.thread_posting.thread_id}`,
+                        `BlubberMemory-Writer-${this.threadData.thread_posting.thread_id}`,
                         value
                     );
                 }
@@ -195,19 +196,19 @@
                 this.$nextTick(() => {
                     STUDIP.Markup.element($(this.$el).find(`.comments > li[data-comment_id="${comment.comment_id}"]`));
                 });
-                for (let i in this.thread_data.comments) {
-                    if (this.thread_data.comments[i].comment_id === comment.comment_id) {
-                        this.thread_data.comments[i].content = comment.content;
-                        this.thread_data.comments[i].html = comment.html;
+                for (let i in this.threadData.comments) {
+                    if (this.threadData.comments[i].comment_id === comment.comment_id) {
+                        this.threadData.comments[i].content = comment.content;
+                        this.threadData.comments[i].html = comment.html;
                         return;
                     }
                 }
-                this.thread_data.comments.push(comment);
+                this.threadData.comments.push(comment);
             },
             removeComment (comment_id) {
-                this.thread_data.comments.forEach((comment, i) => {
+                this.threadData.comments.forEach((comment, i) => {
                     if (comment.comment_id === comment_id) {
-                        this.$delete(this.thread_data.comments, i);
+                        this.$delete(this.threadData.comments, i);
                     }
                 });
             },
@@ -295,7 +296,7 @@
                 }
                 let comment_id = $(li).data('comment_id');
                 let comment_data = null;
-                this.thread_data.comments.forEach((comment, i) => {
+                this.threadData.comments.forEach((comment, i) => {
                     if (comment.comment_id === comment_id) {
                         comment_data = comment;
                     }
@@ -314,7 +315,7 @@
                 let comment_id = li.data('comment_id');
                 let content = li.find('textarea').val();
 
-                thread.thread_data.comments.forEach((comment) => {
+                thread.threadData.comments.forEach((comment) => {
                     if (comment.comment_id === comment_id) {
                         comment.html = content;
                     }
@@ -322,13 +323,13 @@
 
                 li.find('.content').removeClass('editing');
 
-                STUDIP.api.PUT(`blubber/threads/${this.thread_data.thread_posting.thread_id}/comments/${comment_id}`, {
+                STUDIP.api.PUT(`blubber/threads/${this.threadData.thread_posting.thread_id}/comments/${comment_id}`, {
                     data: {
                         content: content
                     },
                 }).done((output) => {
                     if (this.hasContent(output.content)) {
-                        thread.thread_data.comments.forEach((comment) => {
+                        thread.threadData.comments.forEach((comment) => {
                             if (comment.comment_id === comment_id) {
                                 comment.html = output.html;
                                 comment.content = output.content;
@@ -359,10 +360,10 @@
             },
             toggleFollow () {
                 STUDIP.Blubber.followunfollow(
-                    this.thread_data.thread_posting.thread_id,
-                    !this.thread_data.followed
+                    this.threadData.thread_posting.thread_id,
+                    !this.threadData.followed
                 ).done(state => {
-                    this.thread_data.followed = state;
+                    this.threadData.followed = state;
                 });
             },
             hasContent (input) {
@@ -390,15 +391,15 @@
                         thread.$root.display_context_posting = top >= $(el).find('.all_content .thread_posting').height()
                             ? 1
                             : 0;
-                        if (thread.thread_data.more_up && top < 1000 && !thread.already_loading_up) {
+                        if (thread.threadData.more_up && top < 1000 && !thread.already_loading_up) {
                             thread.already_loading_up = 1;
 
-                            let earliest_mkdate = thread.thread_data.comments.reduce((min, comment) => {
+                            let earliest_mkdate = thread.threadData.comments.reduce((min, comment) => {
                                 return min === null ? comment.mkdate : Math.min(min, comment.mkdate);
                             }, null);
 
                             //load older comments
-                            STUDIP.api.GET(`blubber/threads/${thread.thread_data.thread_posting.thread_id}/comments`, {
+                            STUDIP.api.GET(`blubber/threads/${thread.threadData.thread_posting.thread_id}/comments`, {
                                 data: {
                                     modifier: 'olderthan',
                                     timestamp: earliest_mkdate,
@@ -407,7 +408,7 @@
                             }).done((data) => {
                                 top = $(el).scrollTop();
                                 thread.addComments(data.comments, false);
-                                thread.thread_data.more_up = data.more_up;
+                                thread.threadData.more_up = data.more_up;
                                 thread.$nextTick(function () {
                                     //scroll to the position where we were:
                                     let new_height = $(el).find(".all_content").height();
@@ -421,15 +422,15 @@
                             });
                         }
 
-                        if (thread.thread_data.more_down && (top > $(thread).find(".scrollable_area .all_content").height() - 1000) && !thread.already_loading_down) {
+                        if (thread.threadData.more_down && (top > $(thread).find(".scrollable_area .all_content").height() - 1000) && !thread.already_loading_down) {
                             thread.already_loading_down = 1;
 
-                            let latest_mkdate = thread.thread_data.comments.reduce((max, comment) => {
+                            let latest_mkdate = thread.threadData.comments.reduce((max, comment) => {
                                 return Math.max(max, comment.mkdate);
                             }, null);
 
                             //load newer comments
-                            STUDIP.api.GET(`blubber/threads/${thread.thread_data.thread_posting.thread_id}/comments`, {
+                            STUDIP.api.GET(`blubber/threads/${thread.threadData.thread_posting.thread_id}/comments`, {
                                 data: {
                                     modifier: 'newerthan',
                                     timestamp: latest_mkdate,
@@ -437,7 +438,7 @@
                                 }
                             }).done((data) => {
                                 thread.addComments(data.comments, false);
-                                thread.thread_data.more_down = data.more_down;
+                                thread.threadData.more_down = data.more_down;
                             }).always(() => {
                                 thread.already_loading_down = 0;
                             });
@@ -448,7 +449,7 @@
         },
         mounted () { //when everything is initialized
             this.$nextTick(function () {
-                if (this.thread_data.comments.length > 0) {
+                if (this.threadData.comments.length > 0) {
                     this.scrollDown();
                 }
 
@@ -462,8 +463,8 @@
                     STUDIP.Markup.element(this);
                 });
 
-                if (this.thread_data.thread_posting.thread_id) {
-                    let memory = sessionStorage.getItem(`BlubberMemory-Writer-${this.thread_data.thread_posting.thread_id}`);
+                if (this.threadData.thread_posting.thread_id) {
+                    let memory = sessionStorage.getItem(`BlubberMemory-Writer-${this.threadData.thread_posting.thread_id}`);
                     if (memory) {
                         $(this.$el)
                             .find('.writer').addClass('filled')
@@ -474,24 +475,24 @@
         },
         computed: {
             sortedComments () {
-                return this.thread_data.comments.sort((a, b) => a.mkdate - b.mkdate);
+                return [...this.threadData.comments].sort((a, b) => a.mkdate - b.mkdate);
             },
             writerTextareaPlaceholder() {
-                return this.hasContent(this.thread_data.thread_posting.content)
+                return this.hasContent(this.threadData.thread_posting.content)
                     ? this.$gettext('Kommentar schreiben. Enter zum Abschicken.')
                     : this.$gettext('Nachricht schreiben. Enter zum Abschicken.');
             }
         },
         updated () {
             this.$nextTick(function () {
-                if (this.thread_data.thread_posting.thread_id) {
-                    let memory = sessionStorage.getItem('BlubberMemory-Writer-' + this.thread_data.thread_posting.thread_id);
+                if (this.threadData.thread_posting.thread_id) {
+                    let memory = sessionStorage.getItem('BlubberMemory-Writer-' + this.threadData.thread_posting.thread_id);
                     $(this.$el).find('.writer textarea').val(memory);
                 }
             });
         },
         watch: {
-            thread_data (new_data, old_data) {
+            threadData (new_data, old_data) {
                 if (new_data.thread_posting.thread_id !== old_data.thread_posting.thread_id) {
                     //if the thread got reloaded by a new thread
                     //markup contents
diff --git a/resources/vue/components/BlubberThreadWidget.vue b/resources/vue/components/BlubberThreadWidget.vue
index 2a2de1ca5f7..0606eb88972 100644
--- a/resources/vue/components/BlubberThreadWidget.vue
+++ b/resources/vue/components/BlubberThreadWidget.vue
@@ -34,7 +34,8 @@
         data () {
             return {
                 display_more_down: this.more_down,
-                already_loading_down: 0
+                already_loading_down: 0,
+                allThreads: this.threads
             };
         },
         methods: {
@@ -50,11 +51,11 @@
                 return STUDIP.URLHelper.getURL(`dispatch.php/blubber/index/${thread_id}`);
             },
             addThread (thread) {
-                let thread_ids = this.threads.map((t) => t.thread_id);
+                let thread_ids = this.allThreads.map((t) => t.thread_id);
                 if (thread_ids.indexOf(thread.thread_id) !== -1) {
                     return;
                 }
-                this.threads.push(thread);
+                this.allThreads.push(thread);
             }
         },
         directives: {
@@ -104,7 +105,7 @@
         },
         computed: {
             sortedThreads () {
-                return this.threads.sort((a, b) => {
+                return [...this.allThreads].sort((a, b) => {
                     return b.timestamp - a.timestamp
                         || b.mkdate - a.mkdate
                         || b.name.localeCompare(a.name);
diff --git a/resources/vue/components/CacheAdministration.vue b/resources/vue/components/CacheAdministration.vue
index 14e1d53f583..af3d461f695 100644
--- a/resources/vue/components/CacheAdministration.vue
+++ b/resources/vue/components/CacheAdministration.vue
@@ -55,9 +55,11 @@ export default {
         },
         currentConfig: {
             type: Object,
-            default: {
-                component: null,
-                props: []
+            default() {
+                return {
+                    component: null,
+                    props: []
+                };
             }
         }
     },
diff --git a/resources/vue/components/Datetimepicker.vue b/resources/vue/components/Datetimepicker.vue
index 3ec930c12e0..87f6dfd2940 100644
--- a/resources/vue/components/Datetimepicker.vue
+++ b/resources/vue/components/Datetimepicker.vue
@@ -38,7 +38,7 @@ export default {
         }
     },
     mounted () {
-        let value = parseInt(this.value, 10) !== NaN ? parseInt(this.value, 10) : this.value;
+        let value = !isNaN(parseInt(this.value, 10)) ? parseInt(this.value, 10) : this.value;
         if (Number.isInteger(value)) {
             let date = new Date(value * 1000);
             let formatted_date =
diff --git a/resources/vue/components/EditableList.vue b/resources/vue/components/EditableList.vue
index 8b224227213..e35d1d6c155 100644
--- a/resources/vue/components/EditableList.vue
+++ b/resources/vue/components/EditableList.vue
@@ -18,11 +18,15 @@
             <translate>Oder aus Liste auswählen:</translate>
             <select @change="quickselect" @keydown="navigate_or_select">
                 <option value=""><translate>Direkt auswählen ...</translate></option>
-                <template v-for="opt in selectable">
-                    <optgroup v-if="opt.label && opt.options" :label="opt.label">
-                        <option v-for="option in opt.options" :disabled="isSelected(option.value)" :value="JSON.stringify({value: option.value, name: option.name})">{{ option.name + (isSelected(option.value) ? ' ✓' : '') }}</option>
+                <template v-for="(opt, idx) in selectable">
+                    <optgroup v-if="opt.label && opt.options" :label="opt.label" :key="idx">
+                        <option v-for="(option, index) in opt.options" :disabled="isSelected(option.value)" :value="JSON.stringify({value: option.value, name: option.name})" :key="index">
+                            {{ option.name + (isSelected(option.value) ? ' ✓' : '') }}
+                        </option>
                     </optgroup>
-                    <option v-else :disabled="isSelected(opt.value)" @click="quicksearch" :value="JSON.stringify({value: opt.value, name: opt.name})">{{ opt.name + (isSelected(option.value) ? ' ✓' : '') }}</option>
+                    <option v-else :disabled="isSelected(opt.value)" @click="quicksearch" :value="JSON.stringify({value: opt.value, name: opt.name})" :key="idx">
+                        {{ opt.name + (isSelected(option.value) ? ' ✓' : '') }}
+                    </option>
                 </template>
             </select>
 
@@ -52,13 +56,14 @@ export default {
         category_order: {
             type: Array,
             required: false,
-            default: []
+            default: () => [],
         }
     },
     data () {
         return {
             resort: false, //this is just for triggering the computed property sortedItems to be sorted again
-            preventChangeOfQuickselect: false
+            preventChangeOfQuickselect: false,
+            allItems: this.items
         };
     },
     methods: {
@@ -68,16 +73,8 @@ export default {
                 icon = id.split('__')[1];
                 id = id.split('__')[0];
             }
-            let insert = true;
-            for (let i in this.items) {
-                if (this.items[i].value === id) {
-                    insert = false;
-                    break;
-                }
-            }
-
-            if (insert) {
-                this.items.push({
+            if (!this.allItems.find(item => item.value === id)) {
+                this.allItems.push({
                     value: id,
                     name: name,
                     icon: icon,
@@ -143,22 +140,19 @@ export default {
     },
     computed: {
         sortedItems () {
-            let v = this;
-            let i = this.resort;
-            let items = this.items.sort(function (a, b) {
+            return [...this.items].sort((a, b) => {
                 if (a.icon === b.icon) {
                     return a.name.localeCompare(b.name);
                 } else {
                     let a_icon = a.icon || '';
                     let b_icon = b.icon || '';
-                    if (v.category_order.indexOf(a_icon) > -1 && v.category_order.indexOf(b_icon) > -1) {
-                        return v.category_order.indexOf(a_icon) < v.category_order.indexOf(b_icon) ? -1 : 1;
+                    if (this.category_order.indexOf(a_icon) > -1 && this.category_order.indexOf(b_icon) > -1) {
+                        return this.category_order.indexOf(a_icon) < this.category_order.indexOf(b_icon) ? -1 : 1;
                     } else {
                         return a_icon.localeCompare(b_icon);
                     }
                 }
             });
-            return items;
         }
     },
     mounted () {
diff --git a/resources/vue/components/FilesTable.vue b/resources/vue/components/FilesTable.vue
index 15f2d49802f..e58d2140b02 100644
--- a/resources/vue/components/FilesTable.vue
+++ b/resources/vue/components/FilesTable.vue
@@ -15,9 +15,7 @@
                                 {{ breadcrumbs[0].name }}
                             </span>
                         </a>
-                        <span v-for="(breadcrumb, index) in breadcrumbs"
-                                  :key="breadcrumb.folder_id"
-                                  v-if="index > 0">
+                        <span v-for="breadcrumb in breadcrumbs.slice(1)" :key="breadcrumb.folder_id">
                             /<a :href="breadcrumb.url">
                                 {{ breadcrumb.name }}
                             </a>
@@ -36,8 +34,7 @@
                 <col v-if="showdownloads" style="width: 100px" class="responsive-hidden">
                 <col style="width: 150px" class="responsive-hidden">
                 <col style="width: 120px" class="responsive-hidden">
-                <col v-if="topfolder.additionalColumns"
-                     v-for="(name, index) in topfolder.additionalColumns"
+                <col v-for="(name, index) in additionalColumns"
                      :key="index"
                      data-filter-ignore
                      class="responsive-hidden">
@@ -81,8 +78,7 @@
                             {{ $gettext('Datum') }}
                         </a>
                     </th>
-                    <th v-if="topfolder.additionalColumns"
-                        v-for="(name, index) in topfolder.additionalColumns"
+                    <th v-for="(name, index) in additionalColumns"
                         :key="index"
                         @click="sort(index)"
                         class="responsive-hidden"
@@ -112,7 +108,8 @@
             <tbody class="subfolders" v-if="displayedFolders.length > 0">
                 <tr v-for="folder in displayedFolders"
                     :id="'row_folder_' + folder.id "
-                    :data-permissions="folder.permissions">
+                    :data-permissions="folder.permissions"
+                    :key="folder.id">
                     <td v-if="show_bulk_actions">
                         <studip-proxied-checkbox
                             name="ids[]"
@@ -146,12 +143,12 @@
                     <td class="responsive-hidden" style="white-space: nowrap;">
                         <studip-date-time :timestamp="folder.chdate" :relative="true"></studip-date-time>
                     </td>
-                    <template v-if="topfolder.additionalColumns"
-                              v-for="(name, index)  in topfolder.additionalColumns">
+                    <template v-for="(name, index) in additionalColumns">
                         <td v-if="folder.additionalColumns && folder.additionalColumns[index] && folder.additionalColumns[index].html"
                             class="responsive-hidden"
-                            v-html="folder.additionalColumns[index].html"></td>
-                        <td v-else class="responsive-hidden"></td>
+                            v-html="folder.additionalColumns[index].html"
+                            :key="index"></td>
+                        <td v-else class="responsive-hidden" :key="index"></td>
                     </template>
                     <td class="actions" v-html="folder.actions">
                     </td>
@@ -162,7 +159,8 @@
                     :class="file.new ? 'new' : ''"
                     :id="'fileref_' + file.id"
                     role="row"
-                    :data-permissions="getPermissions(file)">
+                    :data-permissions="getPermissions(file)"
+                    :key="file.id">
                     <td v-if="show_bulk_actions">
                         <studip-proxied-checkbox
                             name="ids[]"
@@ -209,12 +207,12 @@
                     <td data-sort-value="file.chdate" class="responsive-hidden" style="white-space: nowrap;">
                         <studip-date-time :timestamp="file.chdate" :relative="true"></studip-date-time>
                     </td>
-                    <template v-if="topfolder.additionalColumns"
-                              v-for="(name, index)  in topfolder.additionalColumns">
+                    <template v-for="(name, index)  in additionalColumns">
                         <td v-if="file.additionalColumns && file.additionalColumns[index] && file.additionalColumns[index].html"
                             class="responsive-hidden"
-                            v-html="file.additionalColumns[index].html"></td>
-                        <td v-else class="responsive-hidden"></td>
+                            v-html="file.additionalColumns[index].html"
+                            :key="index"></td>
+                        <td v-else class="responsive-hidden" :key="index"></td>
                     </template>
                     <td class="actions" v-html="file.actions">
                     </td>
@@ -299,6 +297,8 @@ export default {
             selectedIds: [undefined], // Includes invalid value to trigger watch on mounted
             sortedBy: this.initial_sort.sortedBy,
             sortDirection: this.initial_sort.sortDirection,
+            allFiles: this.files,
+            allFolders: this.folders,
             filter: ''
         };
     },
@@ -318,10 +318,10 @@ export default {
             return classes;
         },
         removeFile (id) {
-            this.files = this.files.filter(file => file.id != id)
+            this.allFiles = this.allFiles.filter(file => file.id != id)
         },
         removeFolder (id) {
-            this.folders = this.folders.filter(folder => folder.id != id)
+            this.allFolders = this.allFolders.filter(folder => folder.id != id)
         },
         sortArray (array) {
             if (!array.length) {
@@ -344,7 +344,7 @@ export default {
             }
 
             // Additional sorting
-            if (this.topfolder.additionalColumns.hasOwnProperty(this.sortedBy) && arrayHasKey) {
+            if (this.topfolder.additionalColumns[this.sortedBy] !== undefined && arrayHasKey) {
                 const is_string = array.some(item => {
                     return typeof item.additionalColumns[this.sortedBy].order === "string"
                         && !isNaN(parseFloat(item.additionalColumns[this.sortedBy].order));
@@ -379,7 +379,7 @@ export default {
             let highlighted = sanitizeHTML(string);
             if (this.needleForFilter.length > 0) {
                 // Escape needle for regexp, see https://stackoverflow.com/a/3561711
-                const pattern = this.needleForFilter.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')
+                const pattern = this.needleForFilter.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&')
                 const regExp = new RegExp(pattern, 'gi');
                 highlighted = highlighted.replace(regExp, '<span class="filter-match">$&</span>');
             }
@@ -392,6 +392,9 @@ export default {
                 + (this.showdownloads ? 1 : 0)
                 + Object.keys(this.topfolder.additionalColumns).length;
         },
+        additionalColumns () {
+            return this.topfolder.additionalColumns || [];
+        },
         sortedFiles () {
             return this.sortArray(this.files);
         },
@@ -402,7 +405,7 @@ export default {
             return [].concat(this.files.map(file => file.id)).concat(this.folders.map(folder => folder.id));
         },
         displayedFiles () {
-            let files = [].concat(this.files);
+            let files = [...this.allFiles];
             if (this.needleForFilter.length > 0) {
                 files = files.filter(file => {
                     return this.valueMatchesFilter(file.name)
@@ -412,7 +415,7 @@ export default {
             return this.sortArray(files);
         },
         displayedFolders () {
-            let folders = [].concat(this.folders);
+            let folders = [...this.allFolders];
             if (this.needleForFilter.length > 0) {
                 folders = folders.filter(folder => {
                     return this.valueMatchesFilter(folder.name)
diff --git a/resources/vue/components/I18nTextarea.vue b/resources/vue/components/I18nTextarea.vue
index 084cdfb5c71..76a025c9aee 100644
--- a/resources/vue/components/I18nTextarea.vue
+++ b/resources/vue/components/I18nTextarea.vue
@@ -1,15 +1,13 @@
 <template>
     <div class="i18n_group" v-if="languages.length > 1">
         <div class="i18n"
-             v-for="language in languages"
-             v-if="(selectedLanguage !== null) && (language.id === selectedLanguage.id)"
-             :data-lang="language.name"
-             :data-icon="'url(' + assetsURL + 'images/languages/' + language.picture + ')'">
+             :data-lang="primaryLanguage.name"
+             :data-icon="'url(' + assetsURL + 'images/languages/' + primaryLanguage.picture + ')'">
             <input type=text
                    ref="inputfield"
                    :name="nameOfInput(language.id)"
-                   v-model="values[selectedLanguage.id]"
-                   :required="required && defaultLanguage === language.id"
+                   v-model="values[primaryLanguage.id]"
+                   :required="required && defaultLanguage === primaryLanguage.id"
                    v-bind="$attrs"
                    v-on="$listeners"
                    v-if="type === 'text'">
@@ -19,34 +17,36 @@
                       v-on="$listeners"
                       v-model="values[language.id]"
                       :required="required && defaultLanguage === language.id"
-                      v-else-if="type === 'textarea'">{{ values[language.id] }}</textarea>
+                      v-else-if="type === 'textarea'"></textarea>
             <studip-wysiwyg :name="nameOfInput(language.id)"
                             ref="inputfield"
                             v-model="values[selectedLanguage.id]"
                             v-bind="$attrs"
                             v-on="$listeners"
-                            :required="required && defaultLanguage === language.id"
+                            :required="required && defaultLanguage === primaryLanguage.id"
                             v-else-if="type === 'wysiwyg' && !wysiwyg_disabled"></studip-wysiwyg>
             <textarea-with-toolbar :name="nameOfInput(language.id)"
                       ref="inputfield"
                       v-else
-                      v-model="values[selectedLanguage.id]"
+                      v-model="values[primaryLanguage.id]"
                       v-bind="$attrs"
-                      :required="required && defaultLanguage === language.id"
+                      :required="required && defaultLanguage === primaryLanguage.id"
                       v-on="$listeners"></textarea-with-toolbar>
         </div>
         <input type="hidden"
-               v-for="language in languages"
-               v-if="(selectedLanguage !== null) && (language.id !== selectedLanguage.id)"
+               v-for="language in secondaryLanguages"
                v-model="values[language.id]"
                :required="required && defaultLanguage === language.id"
-               :name="nameOfInput(language.id)">
+               :name="nameOfInput(language.id)"
+               :key="language.id">
         <select class="i18n"
                 tabindex="0"
                 @change="selectLanguage"
                 :aria-label="$gettext('Sprache des Textfeldes auswählen.')"
                 :style="'background-image: url(' + assetsURL + 'images/languages/' + selectedLanguage.picture + ')'">
-            <option v-for="language in languages" :value="language.id">{{language.name}}</option>
+            <option v-for="language in languages" :value="language.id" :key="language.id">
+                {{language.name}}
+            </option>
         </select>
     </div>
     <div v-else>
@@ -130,10 +130,11 @@ export default {
             this.selectedLanguage = this.languages[i];
             break;
         }
-        let jsonvalue = false;
+        let jsonvalue;
         try {
             jsonvalue = JSON.parse(this.value);
         } catch (except) {
+            jsonvalue = false;
         }
         if (jsonvalue !== false) {
             this.values = jsonvalue;
@@ -179,6 +180,12 @@ export default {
                 }
             }
             return languages;
+        },
+        primaryLanguage () {
+            return this.languages.find(language => language.id === this.selectedLanguage.id);
+        },
+        secondaryLanguages () {
+            return this.languages.filter(language => language.id !== this.selectedLanguage.id);
         }
     },
     inheritAttrs: false,
diff --git a/resources/vue/components/MyCoursesColorPicker.vue b/resources/vue/components/MyCoursesColorPicker.vue
index ecf24a7ff93..368ae5fd5dc 100644
--- a/resources/vue/components/MyCoursesColorPicker.vue
+++ b/resources/vue/components/MyCoursesColorPicker.vue
@@ -1,6 +1,6 @@
 <template>
     <ul class="my-courses-color-picker">
-        <li v-for="(i, index) in color_count" :id="i" :class="getCSSClasses(index)">
+        <li v-for="(i, index) in color_count" :id="i" :class="getCSSClasses(index)" :key="index">
             <a @click="selectColor(index)" :title="getTitle(i, index)">
                 {{ getTitle(i) }}
             </a>
diff --git a/resources/vue/components/MyCoursesNavigation.vue b/resources/vue/components/MyCoursesNavigation.vue
index f3dbad2816a..739668c6f2c 100644
--- a/resources/vue/components/MyCoursesNavigation.vue
+++ b/resources/vue/components/MyCoursesNavigation.vue
@@ -1,6 +1,6 @@
 <template>
     <ul class="my-courses-navigation" v-if="navigationLength > 0">
-        <li v-for="nav in navigation" class="my-courses-navigation-item" :class="nav.class">
+        <li v-for="(nav, index) in navigation" class="my-courses-navigation-item" :class="nav.class" :key="index">
             <a v-if="nav" :href="nav.url" v-bind="nav.attr">
                 <studip-icon :shape="nav.icon.shape" :role="nav.icon.role" :size="iconSize"></studip-icon>
             </a>
diff --git a/resources/vue/components/MyCoursesTables.vue b/resources/vue/components/MyCoursesTables.vue
index c542e3d3899..0a5abad846c 100644
--- a/resources/vue/components/MyCoursesTables.vue
+++ b/resources/vue/components/MyCoursesTables.vue
@@ -36,7 +36,7 @@
                     </th>
                     <th v-if="!responsiveDisplay" class="dont-hide" colspan="2"></th>
                 </tr>
-                <tr v-for="course in getOrderedCourses(subgroup.ids)" :data-course-id="course.id" :class="getCourseClasses(course)">
+                <tr v-for="course in getOrderedCourses(subgroup.ids)" :data-course-id="course.id" :class="getCourseClasses(course)" :key="course.id">
                     <td :class="`gruppe${course.group}`"></td>
                     <td :class="{'subcourse-indented': isChild(course)}">
                         <span :style="{backgroundImage: `url(${course.avatar}`}" class="my-courses-avatar course-avatar-small" :title="course.name" alt=""></span>
diff --git a/resources/vue/components/MyCoursesTiles.vue b/resources/vue/components/MyCoursesTiles.vue
index 28ff6c6a15d..71c5b645384 100644
--- a/resources/vue/components/MyCoursesTiles.vue
+++ b/resources/vue/components/MyCoursesTiles.vue
@@ -1,7 +1,7 @@
 <template>
     <div class="my-courses my-courses--tiles">
-        <template v-for="group in groups">
-            <div class="group-label">{{ group.name }}</div>
+        <template v-for="(group, index) in groups">
+            <div class="group-label" :key="index">{{ group.name }}</div>
             <article class="studip" v-for="subgroup in group.data" :key="subgroup.id" :class="getGroupCssClasses(subgroup)">
                 <header v-if="subgroup.label">
                     <h1>
@@ -10,11 +10,11 @@
                 </header>
                 <section class="studip-grid">
                     <template v-for="course in getOrderedCourses(subgroup.ids)">
-                        <div class="course-group-label" v-if="isParent(course)">
+                        <div class="course-group-label" v-if="isParent(course)" :key="course.id">
                             {{ getCourseName(course, getConfig('sem_number')) }}
                         </div>
 
-                        <article class="studip-grid-element" :data-course-id="course.id" :class="getCourseCssClasses(course)">
+                        <article class="studip-grid-element" :data-course-id="course.id" :class="getCourseCssClasses(course)" :key="course.id">
                             <header class="tiles-grid-element-header">
                                 <span class="tiles-grid-element-options">
                                     <studip-action-menu :items="getActionMenuForCourse(course, true)"
diff --git a/resources/vue/components/StudipDialog.vue b/resources/vue/components/StudipDialog.vue
index 242bc230ac0..603b8b04942 100644
--- a/resources/vue/components/StudipDialog.vue
+++ b/resources/vue/components/StudipDialog.vue
@@ -190,6 +190,7 @@ export default {
             if (this.message) {
                 return this.$gettext('Information');
             }
+            return '';
         },
         dialogWidth() {
             return this.currentWidth ? (this.currentWidth - dialogPadding * 4) + 'px' : 'unset';
@@ -217,7 +218,7 @@ export default {
                 this.left = 5;
                 this.currentWidth = window.innerWidth - 16;
             }
-            
+
             this.top = (window.innerHeight - this.currentHeight) / 2;
             this.footerHeight = this.$refs.footer.offsetHeight;
         },
diff --git a/resources/vue/components/StudipFileSize.vue b/resources/vue/components/StudipFileSize.vue
index 46a0699f3d6..4b26c15e33c 100644
--- a/resources/vue/components/StudipFileSize.vue
+++ b/resources/vue/components/StudipFileSize.vue
@@ -31,6 +31,8 @@
                     let rel = (this.size / 1024 / 1024 / 1024 / 1024).toFixed(1);
                     return rel + " TB";
                 }
+
+                return this.size;
             }
         }
     }
diff --git a/resources/vue/components/StudipMessageBox.vue b/resources/vue/components/StudipMessageBox.vue
index 471225ee38f..0e2fcb74599 100644
--- a/resources/vue/components/StudipMessageBox.vue
+++ b/resources/vue/components/StudipMessageBox.vue
@@ -5,14 +5,14 @@
                 <span>{{ $gettext('Detailanzeige umschalten') }}</span>
             </a>
             <a v-if="!hideClose" class="close" href="" :title="$gettext('Nachrichtenbox schließen')" @click.prevent="closed = true">
-                <span>{{ $gettext('Nachrichtenbox schließen') }}</span>
+                <span>{{ $gettext('Nachrichtenbox schließen') }}</span>
             </a>
         </div>
         <slot></slot>
         <div v-if="showDetails" class="messagebox_details">
             <slot name="details">
                 <ul>
-                    <li v-for="detail in details" v-html="detail"></li>
+                    <li v-for="(detail, index) in details" v-html="detail" :key="index"></li>
                 </ul>
             </slot>
         </div>
@@ -32,7 +32,7 @@ export default {
         },
         details: {
             type: Array,
-            default: [],
+            default: () => [],
         },
         hideDetails: {
             type: Boolean,
diff --git a/resources/vue/components/courseware/CoursewareAccordionContainer.vue b/resources/vue/components/courseware/CoursewareAccordionContainer.vue
index 7d9f7626232..8e4d0984d1a 100644
--- a/resources/vue/components/courseware/CoursewareAccordionContainer.vue
+++ b/resources/vue/components/courseware/CoursewareAccordionContainer.vue
@@ -67,7 +67,7 @@
                             <template #open-indicator="selectAttributes">
                                 <span v-bind="selectAttributes"><studip-icon shape="arr_1down" size="10"/></span>
                             </template>
-                            <template #no-options="{ search, searching, loading }">
+                            <template #no-options>
                                 <translate>Es steht keine Auswahl zur Verfügung.</translate>
                             </template>
                             <template #selected-option="option">
@@ -241,7 +241,7 @@ export default {
             return null;
         },
         updateContent(blockAdder) {
-            if(blockAdder.hasOwnProperty('container') && blockAdder.container.id === this.container.id) {
+            if (blockAdder.container !== undefined && blockAdder.container.id === this.container.id) {
                 this.initCurrentData();
             }
         }
diff --git a/resources/vue/components/courseware/CoursewareActionWidget.vue b/resources/vue/components/courseware/CoursewareActionWidget.vue
index 2db5c2f964d..fa79d124892 100644
--- a/resources/vue/components/courseware/CoursewareActionWidget.vue
+++ b/resources/vue/components/courseware/CoursewareActionWidget.vue
@@ -63,7 +63,6 @@
 </template>
 
 <script>
-import StudipIcon from './../StudipIcon.vue';
 import SidebarWidget from '../SidebarWidget.vue';
 import CoursewareExport from '@/vue/mixins/courseware/export.js';
 import { mapActions, mapGetters } from 'vuex';
@@ -72,7 +71,6 @@ export default {
     name: 'courseware-action-widget',
     props: ['structuralElement', 'canVisit'],
     components: {
-        StudipIcon,
         SidebarWidget,
     },
     mixins: [CoursewareExport],
diff --git a/resources/vue/components/courseware/CoursewareBiographyAchievementsBlock.vue b/resources/vue/components/courseware/CoursewareBiographyAchievementsBlock.vue
index 7a3ce6dc82e..532352f5b88 100755
--- a/resources/vue/components/courseware/CoursewareBiographyAchievementsBlock.vue
+++ b/resources/vue/components/courseware/CoursewareBiographyAchievementsBlock.vue
@@ -81,7 +81,6 @@
 import CoursewareDefaultBlock from './CoursewareDefaultBlock.vue';
 import { mapActions } from 'vuex';
 import { blockMixin } from './block-mixin.js';
-import StudipIcon from '../StudipIcon.vue';
 import StudipWysiwyg from '../StudipWysiwyg.vue';
 
 export default {
@@ -89,7 +88,6 @@ export default {
     mixins: [blockMixin],
     components: {
         CoursewareDefaultBlock,
-        StudipIcon,
         StudipWysiwyg,
     },
     props: {
diff --git a/resources/vue/components/courseware/CoursewareBiographyGoalsBlock.vue b/resources/vue/components/courseware/CoursewareBiographyGoalsBlock.vue
index 4ff944ba4a2..5938a2853ad 100755
--- a/resources/vue/components/courseware/CoursewareBiographyGoalsBlock.vue
+++ b/resources/vue/components/courseware/CoursewareBiographyGoalsBlock.vue
@@ -85,6 +85,8 @@ export default {
                 case 'professional':
                     return this.$gettext('Berufliches Ziel');
             }
+
+            throw new Error('Undefined data type ' + this.currentData.type);
         },
     },
     mounted() {
diff --git a/resources/vue/components/courseware/CoursewareBiographyPersonalInformationBlock.vue b/resources/vue/components/courseware/CoursewareBiographyPersonalInformationBlock.vue
index e8ee5b40ea5..14ba1be9acc 100755
--- a/resources/vue/components/courseware/CoursewareBiographyPersonalInformationBlock.vue
+++ b/resources/vue/components/courseware/CoursewareBiographyPersonalInformationBlock.vue
@@ -75,14 +75,12 @@
 import CoursewareDefaultBlock from './CoursewareDefaultBlock.vue';
 import { mapActions } from 'vuex';
 import { blockMixin } from './block-mixin.js';
-import StudipIcon from '../StudipIcon.vue';
 
 export default {
     name: 'courseware-biography-personal-information-block',
     mixins: [blockMixin],
     components: {
         CoursewareDefaultBlock,
-        StudipIcon,
     },
     props: {
         block: Object,
diff --git a/resources/vue/components/courseware/CoursewareChartBlock.vue b/resources/vue/components/courseware/CoursewareChartBlock.vue
index 4329659fe90..96726bf9e58 100644
--- a/resources/vue/components/courseware/CoursewareChartBlock.vue
+++ b/resources/vue/components/courseware/CoursewareChartBlock.vue
@@ -61,7 +61,7 @@
                                 <template #open-indicator="selectAttributes">
                                     <span v-bind="selectAttributes"><studip-icon shape="arr_1down" size="10"/></span>
                                 </template>
-                                <template #no-options="{ search, searching, loading }">
+                                <template #no-options>
                                     <translate>Es steht keine Auswahl zur Verfügung.</translate>
                                 </template>
                                 <template #selected-option="{name, rgb}">
diff --git a/resources/vue/components/courseware/CoursewareContentBookmarks.vue b/resources/vue/components/courseware/CoursewareContentBookmarks.vue
index 329289c22df..85345428b85 100644
--- a/resources/vue/components/courseware/CoursewareContentBookmarks.vue
+++ b/resources/vue/components/courseware/CoursewareContentBookmarks.vue
@@ -62,6 +62,7 @@ export default {
                     return bookmark.relationships.course.data.id === this.bookmarkFilter;
                 });
             }
+            return [];
         }
     },
     methods: {
diff --git a/resources/vue/components/courseware/CoursewareContentOverviewElements.vue b/resources/vue/components/courseware/CoursewareContentOverviewElements.vue
index c23e6d621b1..0e6a87fde0c 100644
--- a/resources/vue/components/courseware/CoursewareContentOverviewElements.vue
+++ b/resources/vue/components/courseware/CoursewareContentOverviewElements.vue
@@ -193,7 +193,7 @@
                                             ><studip-icon shape="arr_1down" size="10"
                                         /></span>
                                     </template>
-                                    <template #no-options="{ search, searching, loading }">
+                                    <template #no-options>
                                         <translate>Es steht keine Auswahl zur Verfügung.</translate>
                                     </template>
                                     <template #selected-option="{ name, hex }">
@@ -540,7 +540,7 @@ export default {
             const ownerId = element.relationships.owner.data.id;
             const owner = this.userById({ id: ownerId });
 
-            return owner.attributes['formatted-name']; 
+            return owner.attributes['formatted-name'];
         },
         addElement() {
             this.setShowOverviewElementAddDialog(true);
diff --git a/resources/vue/components/courseware/CoursewareContentPermissions.vue b/resources/vue/components/courseware/CoursewareContentPermissions.vue
index 22640721aa2..a2de339d4f2 100755
--- a/resources/vue/components/courseware/CoursewareContentPermissions.vue
+++ b/resources/vue/components/courseware/CoursewareContentPermissions.vue
@@ -233,6 +233,7 @@ export default {
                 this.userPermsWriteUsers = this.element.attributes['write-approval'].users;
             }
 
+            /* eslint-disable no-await-in-loop */
             for (const user_perm_obj of this.userPermsReadUsers) {
                 let userObj = await this.getUser(user_perm_obj.id);
                 let writePerm = this.userPermsWriteUsers.some(user_write_perm => user_write_perm.id === user_perm_obj.id) ? true : false;
diff --git a/resources/vue/components/courseware/CoursewareDefaultBlock.vue b/resources/vue/components/courseware/CoursewareDefaultBlock.vue
index 5df64c99a5a..b3023f5b409 100644
--- a/resources/vue/components/courseware/CoursewareDefaultBlock.vue
+++ b/resources/vue/components/courseware/CoursewareDefaultBlock.vue
@@ -79,14 +79,10 @@
 </template>
 
 <script>
-import CoursewareBlockComments from './CoursewareBlockComments.vue';
 import CoursewareBlockEdit from './CoursewareBlockEdit.vue';
 import CoursewareBlockExportOptions from './CoursewareBlockExportOptions.vue';
-import CoursewareBlockFeedback from './CoursewareBlockFeedback.vue';
 import CoursewareBlockInfo from './CoursewareBlockInfo.vue';
 import CoursewareBlockActions from './CoursewareBlockActions.vue';
-import CoursewareTabs from './CoursewareTabs.vue';
-import CoursewareTab from './CoursewareTab.vue';
 import StudipDialog from '../StudipDialog.vue';
 import StudipIcon from '../StudipIcon.vue';
 import { blockMixin } from './block-mixin.js';
@@ -97,14 +93,10 @@ export default {
     name: 'courseware-default-block',
     mixins: [blockMixin],
     components: {
-        CoursewareBlockComments,
         CoursewareBlockEdit,
         CoursewareBlockExportOptions,
-        CoursewareBlockFeedback,
         CoursewareBlockActions,
         CoursewareBlockInfo,
-        CoursewareTabs,
-        CoursewareTab,
         StudipDialog,
         StudipIcon,
         CoursewareBlockDiscussion,
diff --git a/resources/vue/components/courseware/CoursewareDefaultContainer.vue b/resources/vue/components/courseware/CoursewareDefaultContainer.vue
index 962febba6f3..5822072f482 100644
--- a/resources/vue/components/courseware/CoursewareDefaultContainer.vue
+++ b/resources/vue/components/courseware/CoursewareDefaultContainer.vue
@@ -137,7 +137,6 @@ export default {
             deleteContainer: 'deleteContainer',
             lockObject: 'lockObject',
             unlockObject: 'unlockObject',
-            companionInfo: 'companionInfo',
         }),
         async displayEditDialog() {
             await this.loadContainer({ id: this.container.id, options: { include: 'edit-blocker' } });
diff --git a/resources/vue/components/courseware/CoursewareDialogCardsBlock.vue b/resources/vue/components/courseware/CoursewareDialogCardsBlock.vue
index 022590bb005..b051aecb862 100644
--- a/resources/vue/components/courseware/CoursewareDialogCardsBlock.vue
+++ b/resources/vue/components/courseware/CoursewareDialogCardsBlock.vue
@@ -11,7 +11,7 @@
         >
             <template #content>
                 <div class="cw-block-dialog-cards-content">
-                    <button 
+                    <button
                         class="cw-dialogcards-prev cw-dialogcards-navbutton"
                         :class="{'cw-dialogcards-prev-disabled': hasNoPerv}"
                         @click="prevCard"
@@ -69,7 +69,7 @@
                         :name="$gettext('Karte') +  ' ' + (index + 1).toString()"
                         :selected="index === 0"
                         canBeEmpty
-                    > 
+                    >
                         <form class="default" @submit.prevent="">
                             <label>
                                 <translate>Bild Vorderseite</translate>:
@@ -118,7 +118,6 @@ import CoursewareTabs from './CoursewareTabs.vue';
 import CoursewareTab from './CoursewareTab.vue';
 import { blockMixin } from './block-mixin.js';
 import { mapActions } from 'vuex';
-import StudipIcon from '../StudipIcon.vue';
 
 export default {
     name: 'courseware-dialog-cards-block',
@@ -128,7 +127,6 @@ export default {
         CoursewareFileChooser,
         CoursewareTabs,
         CoursewareTab,
-        StudipIcon,
     },
     props: {
         block: Object,
diff --git a/resources/vue/components/courseware/CoursewareHeadlineBlock.vue b/resources/vue/components/courseware/CoursewareHeadlineBlock.vue
index 8a5bc6ce5d8..292ad60b119 100644
--- a/resources/vue/components/courseware/CoursewareHeadlineBlock.vue
+++ b/resources/vue/components/courseware/CoursewareHeadlineBlock.vue
@@ -68,7 +68,7 @@
                             <template #open-indicator="selectAttributes">
                                 <span v-bind="selectAttributes"><studip-icon shape="arr_1down" size="10"/></span>
                             </template>
-                            <template #no-options="{ search, searching, loading }">
+                            <template #no-options>
                                 <translate>Es steht keine Auswahl zur Verfügung.</translate>
                             </template>
                             <template #selected-option="{name, hex}">
@@ -85,7 +85,7 @@
                             <template #open-indicator="selectAttributes">
                                 <span v-bind="selectAttributes"><studip-icon shape="arr_1down" size="10"/></span>
                             </template>
-                            <template #no-options="{ search, searching, loading }">
+                            <template #no-options>
                                 <translate>Es steht keine Auswahl zur Verfügung.</translate>
                             </template>
                             <template #selected-option="option">
@@ -108,7 +108,7 @@
                             <template #open-indicator="selectAttributes">
                                 <span v-bind="selectAttributes"><studip-icon shape="arr_1down" size="10"/></span>
                             </template>
-                            <template #no-options="{ search, searching, loading }">
+                            <template #no-options>
                                 <translate>Es steht keine Auswahl zur Verfügung.</translate>
                             </template>
                             <template #selected-option="{name, hex}">
@@ -138,7 +138,7 @@
                             <template #open-indicator="selectAttributes">
                                 <span v-bind="selectAttributes"><studip-icon shape="arr_1down" size="10"/></span>
                             </template>
-                            <template #no-options="{ search, searching, loading }">
+                            <template #no-options>
                                 <translate>Es steht keine Auswahl zur Verfügung.</translate>
                             </template>
                             <template #selected-option="{name, hex}">
diff --git a/resources/vue/components/courseware/CoursewareImageMapBlock.vue b/resources/vue/components/courseware/CoursewareImageMapBlock.vue
index f097e782bc6..597213b52b1 100644
--- a/resources/vue/components/courseware/CoursewareImageMapBlock.vue
+++ b/resources/vue/components/courseware/CoursewareImageMapBlock.vue
@@ -72,7 +72,7 @@
                                     <template #open-indicator="selectAttributes">
                                         <span v-bind="selectAttributes"><studip-icon shape="arr_1down" size="10"/></span>
                                     </template>
-                                    <template #no-options="{ search, searching, loading }">
+                                    <template #no-options>
                                         <translate>Es steht keine Auswahl zur Verfügung.</translate>
                                     </template>
                                     <template #selected-option="{name, rgba}">
@@ -417,6 +417,9 @@ export default {
             this.currentShapes.forEach((value, key) => {
                 let shape = value;
                 let area = {};
+                let coords = '';
+                let x = 0;
+                let y = 0;
                 area.id = 'shape-' + key;
 
                 switch (shape.type) {
@@ -425,9 +428,6 @@ export default {
                         area.coords = shape.data.centerX + ', ' + shape.data.centerY + ', ' + shape.data.radius;
                         break;
                     case 'ellipse':
-                        let coords = '';
-                        let x = 0;
-                        let y = 0;
                         for (let theta = 0; theta < 2 * Math.PI; theta += (2 * Math.PI) / 20) {
                             x = parseInt(shape.data.X) + Math.round(parseInt(shape.data.radiusX) * Math.cos(theta));
                             y = parseInt(shape.data.Y) + Math.round(parseInt(shape.data.radiusY) * Math.sin(theta));
@@ -438,10 +438,10 @@ export default {
                         break;
                     case 'rect':
                     case 'text':
-                        let x2 = parseInt(shape.data.X) + parseInt(shape.data.width);
-                        let y2 = parseInt(shape.data.Y) + parseInt(shape.data.height);
+                        x = parseInt(shape.data.X) + parseInt(shape.data.width);
+                        y = parseInt(shape.data.Y) + parseInt(shape.data.height);
                         area.shape = 'rect';
-                        area.coords = shape.data.X + ', ' + shape.data.Y + ', ' + x2 + ', ' + y2;
+                        area.coords = shape.data.X + ', ' + shape.data.Y + ', ' + x + ', ' + y;
                         break;
                 }
                 area.title = shape.title;
diff --git a/resources/vue/components/courseware/CoursewareKeyPointBlock.vue b/resources/vue/components/courseware/CoursewareKeyPointBlock.vue
index 05d5d2fd701..2fc29b45935 100644
--- a/resources/vue/components/courseware/CoursewareKeyPointBlock.vue
+++ b/resources/vue/components/courseware/CoursewareKeyPointBlock.vue
@@ -40,7 +40,7 @@
                             <template #open-indicator="selectAttributes">
                                 <span v-bind="selectAttributes"><studip-icon shape="arr_1down" size="10"/></span>
                             </template>
-                            <template #no-options="{ search, searching, loading }">
+                            <template #no-options>
                                 <translate>Es steht keine Auswahl zur Verfügung.</translate>
                             </template>
                             <template #selected-option="{name, hex}">
@@ -58,7 +58,7 @@
                             <template #open-indicator="selectAttributes">
                                 <span v-bind="selectAttributes"><studip-icon shape="arr_1down" size="10"/></span>
                             </template>
-                            <template #no-options="{ search, searching, loading }">
+                            <template #no-options>
                                 <translate>Es steht keine Auswahl zur Verfügung.</translate>
                             </template>
                             <template #selected-option="option">
@@ -171,6 +171,7 @@ export default {
                     return 'status-yellow';
 
                 case 'blue':
+                default:
                     return 'clickable';
             }
         }
diff --git a/resources/vue/components/courseware/CoursewareManagerCopySelector.vue b/resources/vue/components/courseware/CoursewareManagerCopySelector.vue
index 3c22dcd9a14..084e9a2f39c 100644
--- a/resources/vue/components/courseware/CoursewareManagerCopySelector.vue
+++ b/resources/vue/components/courseware/CoursewareManagerCopySelector.vue
@@ -28,7 +28,7 @@
                         </ul>
                     </li>
                 </ul>
-                <courseware-companion-box 
+                <courseware-companion-box
                     v-if="!hasRemoteCid && semesterMap.length === 0"
                     :msgCompanion="$gettext('Es wurden keine Veranstaltung mit Courseware-Inhalten gefunden.')"
                     mood="sad"
@@ -113,7 +113,7 @@ export default {
             return this.remoteCid !== '';
         },
         loadedCourses() {
-            return this.courses.sort((a, b) => a.attributes.title > b.attributes.title);
+            return [...this.courses].sort((a, b) => a.attributes.title > b.attributes.title);
         }
     },
     methods: {
@@ -218,7 +218,7 @@ export default {
                     elementId: elementId,
                     migrate: true
                 });
-            } catch(error) { 
+            } catch(error) {
                 console.debug(error);
                 this.copyAllInProgressText = this.$gettext('Beim Kopiervorgang sind Fehler aufgetreten.');
             }
diff --git a/resources/vue/components/courseware/CoursewareManagerElement.vue b/resources/vue/components/courseware/CoursewareManagerElement.vue
index 2bd892c2ed5..bae3725a7cd 100644
--- a/resources/vue/components/courseware/CoursewareManagerElement.vue
+++ b/resources/vue/components/courseware/CoursewareManagerElement.vue
@@ -471,6 +471,9 @@ export default {
             if(!this.insertingInProgress) {
                 this.insertingInProgress = true;
                 if (source === 'self') {
+                    block.relationships.container.data.id = this.filingData.parentItem.id;
+                    block.attributes.position = this.filingData.parentItem.relationships.blocks.data.length + 1;
+
                     let sourceContainer = await this.containerById({id: block.relationships.container.data.id});
                     sourceContainer.attributes.payload.sections.forEach(section => {
                         let index = section.blocks.indexOf(block.id);
@@ -494,8 +497,6 @@ export default {
                     });
                     await this.unlockObject({id: destinationContainer.id, type: 'courseware-containers'});
 
-                    block.relationships.container.data.id = this.filingData.parentItem.id;
-                    block.attributes.position = this.filingData.parentItem.relationships.blocks.data.length + 1;
                     await this.lockObject({id: block.id, type: 'courseware-blocks'});
                     await this.updateBlock({
                         block: block,
diff --git a/resources/vue/components/courseware/CoursewareManagerLinkSelector.vue b/resources/vue/components/courseware/CoursewareManagerLinkSelector.vue
index 1f4774ff05a..a735024a2ad 100644
--- a/resources/vue/components/courseware/CoursewareManagerLinkSelector.vue
+++ b/resources/vue/components/courseware/CoursewareManagerLinkSelector.vue
@@ -13,15 +13,11 @@
 
 <script>
 import CoursewareManagerElement from './CoursewareManagerElement.vue';
-import CoursewareCompanionBox from './CoursewareCompanionBox.vue';
 import { mapActions, mapGetters } from 'vuex';
 
 export default {
     name: 'courseware-manager-link-selector',
-    components: {
-        CoursewareManagerElement,
-        CoursewareCompanionBox,
-    },
+    components: { CoursewareManagerElement },
 
     data() {
         return {
@@ -75,4 +71,4 @@ export default {
         await this.loadOwnCourseware();
     },
 }
-</script>
\ No newline at end of file
+</script>
diff --git a/resources/vue/components/courseware/CoursewareStructuralElement.vue b/resources/vue/components/courseware/CoursewareStructuralElement.vue
index 25fff6f6c12..e8de3b1518b 100644
--- a/resources/vue/components/courseware/CoursewareStructuralElement.vue
+++ b/resources/vue/components/courseware/CoursewareStructuralElement.vue
@@ -229,7 +229,7 @@
                                                     ><studip-icon shape="arr_1down" size="10"
                                                 /></span>
                                             </template>
-                                            <template #no-options="{ search, searching, loading }">
+                                            <template #no-options>
                                                 <translate>Es steht keine Auswahl zur Verfügung</translate>.
                                             </template>
                                             <template #selected-option="{ name, hex }">
@@ -1278,13 +1278,15 @@ export default {
 
             return containers;
         },
+
         owner() {
-            const user = this.$store.getters['users/related']({
-                parent: { type: this.structuralElement.type, id: this.structuralElement.id },
-                relationship: 'owner'
+            const owner = this.relatedUsers({
+                parent: this.structuralElement,
+                relationship: 'owner',
             });
-            return user ?? null;
+            return owner ?? null;
         },
+
         ownerName() {
             return this.owner?.attributes['formatted-name'] ?? '?';
         },
@@ -1574,7 +1576,9 @@ export default {
         async createElement() {
             let title = this.newChapterName; // this is the title of the new element
             let parent_id = this.currentId; // new page is descandant as default
-            if (this.errorEmptyChapterName = title.trim() === '') {
+
+            this.errorEmptyChapterName = title.trim();
+            if (this.errorEmptyChapterName === '') {
                 return;
             }
             if (this.newChapterParent === 'sibling') {
diff --git a/resources/vue/components/courseware/CoursewareTabs.vue b/resources/vue/components/courseware/CoursewareTabs.vue
index 13a6b79d34d..b5cc5a60f76 100644
--- a/resources/vue/components/courseware/CoursewareTabs.vue
+++ b/resources/vue/components/courseware/CoursewareTabs.vue
@@ -39,8 +39,8 @@ export default {
     },
     computed: {
         sortedTabs() {
-            return this.tabs.sort((a, b) => {
-                return a.index > b.index ? 1 : a.index < b.index ? -1 : 0;
+            return [...this.tabs].sort((a, b) => {
+                return a.index - b.index;
             });
         }
     },
@@ -106,7 +106,7 @@ export default {
         },
         getTabButtonByName(name) {
             let selectorId = null;
-            this.tabs.forEach((tab) => { 
+            this.tabs.forEach((tab) => {
                 if (tab.name === name) {
                     selectorId = tab.selectorId;
                 }
@@ -118,7 +118,7 @@ export default {
         },
         getTabButtonByAlias(alias) {
             let selectorId = null;
-            this.tabs.forEach((tab) => { 
+            this.tabs.forEach((tab) => {
                 if (tab.alias === alias) {
                     selectorId = tab.selectorId;
                 }
diff --git a/resources/vue/components/courseware/CoursewareTabsContainer.vue b/resources/vue/components/courseware/CoursewareTabsContainer.vue
index 15b3c87b574..b07d2a6b35b 100644
--- a/resources/vue/components/courseware/CoursewareTabsContainer.vue
+++ b/resources/vue/components/courseware/CoursewareTabsContainer.vue
@@ -78,7 +78,7 @@
                             <template #open-indicator="selectAttributes">
                                 <span v-bind="selectAttributes"><studip-icon shape="arr_1down" size="10"/></span>
                             </template>
-                            <template #no-options="{ search, searching, loading }">
+                            <template #no-options>
                                 <translate>Es steht keine Auswahl zur Verfügung.</translate>
                             </template>
                             <template #selected-option="option">
@@ -259,7 +259,7 @@ export default {
             return null;
         },
         updateContent(blockAdder) {
-            if(blockAdder.hasOwnProperty('container') && blockAdder.container.id === this.container.id) {
+            if(blockAdder.container !== undefined && blockAdder.container.id === this.container.id) {
                 this.initCurrentData();
             }
         }
diff --git a/resources/vue/components/courseware/CoursewareTimelineBlock.vue b/resources/vue/components/courseware/CoursewareTimelineBlock.vue
index 451478ecad9..d8db9176710 100755
--- a/resources/vue/components/courseware/CoursewareTimelineBlock.vue
+++ b/resources/vue/components/courseware/CoursewareTimelineBlock.vue
@@ -11,7 +11,7 @@
         >
             <template #content>
                 <ol class="cw-timeline">
-                    <li 
+                    <li
                         v-for="(item, index) in sortedItems"
                         :key="index"
                         class="cw-timeline-item"
@@ -90,7 +90,7 @@
                                     <template #open-indicator="selectAttributes">
                                         <span v-bind="selectAttributes"><studip-icon shape="arr_1down" size="10"/></span>
                                     </template>
-                                    <template #no-options="{ search, searching, loading }">
+                                    <template #no-options>
                                         <translate>Es steht keine Auswahl zur Verfügung.</translate>
                                     </template>
                                     <template #selected-option="{name, hex}">
@@ -107,7 +107,7 @@
                                     <template #open-indicator="selectAttributes">
                                         <span v-bind="selectAttributes"><studip-icon shape="arr_1down" size="10"/></span>
                                     </template>
-                                    <template #no-options="{ search, searching, loading }">
+                                    <template #no-options>
                                         <translate>Es steht keine Auswahl zur Verfügung.</translate>
                                     </template>
                                     <template #selected-option="option">
diff --git a/resources/vue/components/courseware/CoursewareToolsAdmin.vue b/resources/vue/components/courseware/CoursewareToolsAdmin.vue
index 36dfee21f21..337a39e47fe 100644
--- a/resources/vue/components/courseware/CoursewareToolsAdmin.vue
+++ b/resources/vue/components/courseware/CoursewareToolsAdmin.vue
@@ -57,7 +57,6 @@ export default {
                 permission: this.currentPermissionLevel,
                 progression: this.currentProgression,
             });
-;
         },
     },
     mounted() {
-- 
GitLab