From dd20ec005462f4e13d89a44d366bf0ba6714d1d9 Mon Sep 17 00:00:00 2001
From: Ron Lucke <lucke@elan-ev.de>
Date: Fri, 1 Dec 2023 11:35:23 +0000
Subject: [PATCH] =?UTF-8?q?Werkzeugleiste=20f=C3=BCr=20Courseware?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2488

Merge request studip/studip!2005
---
 .../assets/stylesheets/scss/courseware.scss   |   1 +
 .../scss/courseware/blockadder.scss           |  97 ++--
 .../scss/courseware/layouts/ribbon.scss       |  12 +-
 .../stylesheets/scss/courseware/sortable.scss |   2 +
 .../stylesheets/scss/courseware/toolbar.scss  | 175 +++++++
 .../containers/CoursewareBlockAdderArea.vue   |   4 -
 .../structural-element/CoursewareRibbon.vue   |   2 +-
 .../CoursewareRibbonToolbar.vue               |  60 +--
 .../CoursewareStructuralElement.vue           | 251 +++++-----
 .../CoursewareToolsBlockadder.vue             | 436 ------------------
 .../structural-element-components.js          |   2 +
 .../toolbar/CoursewareBlockadderItem.vue      | 115 +++++
 .../toolbar/CoursewareClipboardItem.vue       | 246 ++++++++++
 .../toolbar/CoursewareContainerAdderItem.vue  |  52 +++
 .../courseware/toolbar/CoursewareToolbar.vue  | 170 +++++++
 .../toolbar/CoursewareToolbarBlocks.vue       | 212 +++++++++
 .../toolbar/CoursewareToolbarClipboard.vue    | 134 ++++++
 .../toolbar/CoursewareToolbarContainers.vue   |  66 +++
 18 files changed, 1354 insertions(+), 683 deletions(-)
 create mode 100644 resources/assets/stylesheets/scss/courseware/toolbar.scss
 delete mode 100644 resources/vue/components/courseware/structural-element/CoursewareToolsBlockadder.vue
 create mode 100644 resources/vue/components/courseware/toolbar/CoursewareBlockadderItem.vue
 create mode 100644 resources/vue/components/courseware/toolbar/CoursewareClipboardItem.vue
 create mode 100644 resources/vue/components/courseware/toolbar/CoursewareContainerAdderItem.vue
 create mode 100644 resources/vue/components/courseware/toolbar/CoursewareToolbar.vue
 create mode 100644 resources/vue/components/courseware/toolbar/CoursewareToolbarBlocks.vue
 create mode 100644 resources/vue/components/courseware/toolbar/CoursewareToolbarClipboard.vue
 create mode 100644 resources/vue/components/courseware/toolbar/CoursewareToolbarContainers.vue

diff --git a/resources/assets/stylesheets/scss/courseware.scss b/resources/assets/stylesheets/scss/courseware.scss
index f5caa333a45..2ec74e45855 100644
--- a/resources/assets/stylesheets/scss/courseware.scss
+++ b/resources/assets/stylesheets/scss/courseware.scss
@@ -8,6 +8,7 @@
 @import './courseware/content-courses.scss';
 @import './courseware/dashboard.scss';
 @import './courseware/sortable.scss';
+@import './courseware/toolbar.scss';
 @import './courseware/widgets.scss';
 @import './courseware/wizards.scss';
 
diff --git a/resources/assets/stylesheets/scss/courseware/blockadder.scss b/resources/assets/stylesheets/scss/courseware/blockadder.scss
index 12a43a9b2d7..774c3d4a1a0 100644
--- a/resources/assets/stylesheets/scss/courseware/blockadder.scss
+++ b/resources/assets/stylesheets/scss/courseware/blockadder.scss
@@ -89,21 +89,17 @@
     }
 }
 
-.cw-element-adder-wrapper {
-    display: flex;
-    flex-wrap: wrap;
-    justify-content: space-between;
-}
 .cw-blockadder-item-list {
     display: grid;
-    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
+    grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
     grid-auto-rows: auto;
     grid-gap: 4px;
+    margin-bottom: 8px;
 
     .cw-blockadder-item-wrapper {
         display: flex;
         border: solid thin var(--content-color-40);
-        max-width: 254px;
+        max-width: 268px;
 
         &:hover {
             border-color: var(--base-color);
@@ -238,16 +234,12 @@
         &:hover {
             color: var(--active-color);
         }
-        &:not(:first-child) {
-            border-left: solid thin transparent;
-        }
         &.cw-container-style-selector-active {
             background-color: var(--content-color-20);
             border: solid thin var(--base-color);
         }
-
     }
-    input[type=radio] {
+    input[type='radio'] {
         position: absolute;
         opacity: 0;
         width: 0;
@@ -261,54 +253,53 @@
     }
 }
 .cw-element-inserter-wrapper {
-    display: flex;
-    flex-wrap: wrap;
-    justify-content: space-between;
-}
-
+    display: grid;
+    grid-template-columns: repeat(auto-fit, minmax(225px, 1fr));
+    grid-auto-rows: auto;
+    grid-gap: 4px;
+    margin-bottom: 8px;
 
-.cw-clipboard-item-wrapper {
-    display: flex;
-    width: calc(50% - 4px);
-    border: solid thin var(--content-color-40);
-    margin-bottom: 4px;
+    .cw-clipboard-item-wrapper {
+        display: flex;
+        border: solid thin var(--content-color-40);
+        max-width: 248px;
 
-    &:hover {
-        border-color: var(--base-color);
-    }
+        &:hover {
+            border-color: var(--base-color);
+        }
 
-    .cw-clipboard-item {
-        width: 207px;
-        padding: 64px 10px 4px 10px;
-        @include background-icon(unit-test, clickable, 48);
-        background-position: 10px 10px;
-        background-repeat: no-repeat;
-        cursor: pointer;
-        background-color: var(--white);
-        border: none;
-        text-align: left;
-        color: var(--base-color);
+        .cw-clipboard-item {
+            width: calc(100% - 36px);
+            padding: 64px 10px 4px 10px;
+            @include background-icon(unit-test, clickable, 48);
+            background-position: 10px 10px;
+            background-repeat: no-repeat;
+            cursor: pointer;
+            background-color: var(--white);
+            border: none;
+            text-align: left;
+            color: var(--base-color);
 
-        @each $item, $icon in $blockadder-items {
-            &.cw-clipboard-item-#{$item} {
-                @include background-icon($icon, clickable, 48);
+            @each $item, $icon in $blockadder-items {
+                &.cw-clipboard-item-#{$item} {
+                    @include background-icon($icon, clickable, 48);
+                }
             }
-        }
-        @each $item, $icon in $containeradder-items {
-            &.cw-clipboard-item-#{$item} {
-                @include background-icon($icon, clickable, 48);
+            @each $item, $icon in $containeradder-items {
+                &.cw-clipboard-item-#{$item} {
+                    @include background-icon($icon, clickable, 48);
+                }
+            }
+
+            .cw-clipboard-item-title {
+                display: inline-block;
+                font-weight: 600;
+                margin-bottom: 2px;
             }
         }
-    
-        .cw-clipboard-item-title {
-            display: inline-block;
-            font-weight: 600;
-            margin-bottom: 2px;
+        .cw-clipboard-item-action-menu-wrapper {
+            padding: 8px;
         }
-
-    }
-    .cw-clipboard-item-action-menu-wrapper {
-        padding: 8px;
     }
 }
 .action-menu.is-open,
@@ -316,4 +307,4 @@
     &.cw-clipboard-item-action-menu {
         z-index: 42;
     }
-}
\ No newline at end of file
+}
diff --git a/resources/assets/stylesheets/scss/courseware/layouts/ribbon.scss b/resources/assets/stylesheets/scss/courseware/layouts/ribbon.scss
index 1285f05ca6d..f05a518c9a6 100644
--- a/resources/assets/stylesheets/scss/courseware/layouts/ribbon.scss
+++ b/resources/assets/stylesheets/scss/courseware/layouts/ribbon.scss
@@ -185,13 +185,13 @@ $consum_ribbon_width: calc(100% - 58px);
             cursor: default;
         }
     }
+}
 
-    .cw-ribbon-action-menu {
-        vertical-align: text-top;
-        margin: 2px 0 0 2px;
-        &.is-open {
-            z-index: 32;
-        }
+.cw-ribbon-action-menu {
+    vertical-align: text-top;
+    margin: 2px 0 0 2px;
+    &.is-open {
+        z-index: 32;
     }
 }
 
diff --git a/resources/assets/stylesheets/scss/courseware/sortable.scss b/resources/assets/stylesheets/scss/courseware/sortable.scss
index 107b3a8056a..9f90de4e827 100644
--- a/resources/assets/stylesheets/scss/courseware/sortable.scss
+++ b/resources/assets/stylesheets/scss/courseware/sortable.scss
@@ -36,6 +36,8 @@
 
 
 .cw-container-wrapper-edit {
+    width: calc(100% - 64px);
+
     .cw-structural-element-list {
         width: 100%;
         padding: 0;
diff --git a/resources/assets/stylesheets/scss/courseware/toolbar.scss b/resources/assets/stylesheets/scss/courseware/toolbar.scss
new file mode 100644
index 00000000000..bf52e79a058
--- /dev/null
+++ b/resources/assets/stylesheets/scss/courseware/toolbar.scss
@@ -0,0 +1,175 @@
+$toolbar-icons: (
+    toggle-out: arr_2right,
+    toggle-in: arr_2left,
+    add: add,
+    clipboard: clipboard
+);
+
+.cw-toolbar {
+    z-index: 30;
+    display: flex;
+    position: fixed;
+    top: 0;
+    flex-direction: row;
+    justify-content: flex-end;
+    right: 0;
+    margin-left: 4px;
+    height: 600px;
+
+    .cw-toolbar-tools {
+        width: 270px;
+        min-height: 100%;
+        border: solid thin var(--content-color-40);
+        background-color: var(--white);
+        overflow-y: auto;
+        overflow-x: hidden;
+        position: relative;
+        padding: 0 4px;
+        top: 0;
+        right: -270px;
+        transition: right 0.6s;
+        &.hd {
+            width: 480px;
+            right: -480px;
+        }
+        &.wqhd {
+            width: 558px;
+            right: -558px;
+        }
+        &.unfold {
+            right: 0;
+        }
+
+        .cw-toolbar-blocks {
+            .input-group.files-search {
+                &.search {
+                    border: thin solid var(--dark-gray-color-30);
+                    margin-bottom: 0px;
+                    input {
+                        border: none;
+                    }
+                }
+    
+                .input-group-append {
+                    .button {
+                        border: none;
+                        border-left: thin solid var(--dark-gray-color-30);
+                        &.active {
+                            background-color: var(--base-color);
+                        }
+                    }
+                    .reset-search {
+                        border: none;
+                        background-color: var(--white);
+                    }
+                }
+    
+                .active-filter {
+                    display: flex;
+                    align-items: center;
+                    justify-content: space-between;
+                    border: solid thin var(--black);
+                    background-color: var(--content-color-10);
+                    margin: 3px;
+                    padding: 2px 3px;
+    
+                    .removefilter {
+                        border: none;
+                        background-color: transparent;
+                    }
+                }
+            }
+    
+            .cw-block-search {
+                width: inherit;
+            }
+    
+            .filterpanel {
+                margin-bottom: 5px;
+                padding: 2px;
+                border: thin solid var(--dark-gray-color-30);
+                border-top: none;
+                background-color: #fff;
+    
+                .button {
+                    min-width: inherit;
+                    margin: 4px 2px;
+    
+                    &.button-active {
+                        background-color: var(--base-color);
+                        color: var(--white);
+                    }
+                }
+            }
+
+        }
+    }
+    .cw-toolbar-button-wrapper {
+        position: sticky;
+        top: 0;
+        background-color: var(--white);
+        border-bottom: solid thin var(--content-color-40);
+        display: flex;
+        z-index: 31;
+        margin: 0 0 8px -4px;
+        width: calc(100% + 8px);
+    }
+    .cw-toolbar-button {
+        height: 44px;
+        margin: 0 4px 0 4px;
+        padding: 2px 8px 0 8px;
+        border: none;
+        background-color: var(--white);
+        background-repeat: no-repeat;
+        background-position: center center;
+        cursor: pointer;
+        border-bottom: solid 2px transparent;
+
+        @each $type, $icon in $toolbar-icons {
+            &.cw-toolbar-button-#{$type} {
+                @include background-icon(#{$icon}, clickable, 24);
+            }
+        }
+        &.cw-toolbar-button-toggle {
+            right: 0;
+            width: 42px;
+
+            &.cw-toolbar-button-toggle-out {
+                position: absolute;
+            }
+            &.cw-toolbar-button-toggle-in {
+                position: relative;
+            }
+        }
+
+        &.active {
+            border-bottom: solid 2px var(--base-color);
+        }
+    }
+    .cw-toolbar-spacer-right {
+        z-index: 39;
+        flex-shrink: 0;
+        position: relative;
+        background-color: var(--white);
+        width: 15px;
+        height: calc(100% + 2px);
+    }
+
+    .cw-toolbar-tools.hd {
+        .cw-toolbar-button-wrapper {
+            .cw-toolbar-button {
+                width: 128px;
+                padding: 2px 16px 0 16px;
+                &.cw-toolbar-button-toggle {
+                    width: 42px;
+                }
+            }
+        }
+    }
+}
+#contents-courseware-courseware,
+#course-courseware-courseware {
+    #content-wrapper {
+        overflow-x: hidden;
+    }
+}
diff --git a/resources/vue/components/courseware/containers/CoursewareBlockAdderArea.vue b/resources/vue/components/courseware/containers/CoursewareBlockAdderArea.vue
index b81ee96c254..de7ca38c325 100644
--- a/resources/vue/components/courseware/containers/CoursewareBlockAdderArea.vue
+++ b/resources/vue/components/courseware/containers/CoursewareBlockAdderArea.vue
@@ -39,8 +39,6 @@ export default {
     methods: {
         ...mapActions({
             coursewareBlockAdder: 'coursewareBlockAdder',
-            coursewareSelectedToolbarItem: 'coursewareSelectedToolbarItem',
-            coursewareShowToolbar: 'coursewareShowToolbar'
         }),
         selectBlockAdder() {
             if (this.adderActive) {
@@ -49,8 +47,6 @@ export default {
             } else {
                 this.adderActive = true;
                 this.coursewareBlockAdder({ container: this.container, section: this.section });
-                this.coursewareSelectedToolbarItem('blockadder');
-                this.coursewareShowToolbar(true);
             }
         },
     },
diff --git a/resources/vue/components/courseware/structural-element/CoursewareRibbon.vue b/resources/vue/components/courseware/structural-element/CoursewareRibbon.vue
index ddf5c9b2149..25c72607d84 100644
--- a/resources/vue/components/courseware/structural-element/CoursewareRibbon.vue
+++ b/resources/vue/components/courseware/structural-element/CoursewareRibbon.vue
@@ -1,7 +1,7 @@
 <template>
     <div :class="{ 'cw-ribbon-wrapper-consume': consumeMode }" :id="isContentBar ? 'contentbar' : null" >
         <div v-show="stickyRibbon" class="cw-ribbon-sticky-top"></div>
-        <header class="cw-ribbon" :class="{ 'cw-ribbon-sticky': stickyRibbon, 'cw-ribbon-consume': consumeMode }">
+        <header :id="isContentBar ? 'cw-ribbon' : null" class="cw-ribbon" :class="{ 'cw-ribbon-sticky': stickyRibbon, 'cw-ribbon-consume': consumeMode }">
             <div class="cw-ribbon-wrapper-left">
                 <nav class="cw-ribbon-nav" :class="buttonsClass">
                     <slot name="buttons" />
diff --git a/resources/vue/components/courseware/structural-element/CoursewareRibbonToolbar.vue b/resources/vue/components/courseware/structural-element/CoursewareRibbonToolbar.vue
index 164dcaef85f..992c3a1f8b3 100644
--- a/resources/vue/components/courseware/structural-element/CoursewareRibbonToolbar.vue
+++ b/resources/vue/components/courseware/structural-element/CoursewareRibbonToolbar.vue
@@ -9,7 +9,6 @@
                     <courseware-tabs
                         class="cw-ribbon-tool-content-tablist"
                         ref="tabs"
-                        @selectTab="selectTool($event.alias)"
                     >
                         <courseware-tab
                             :name="$gettext('Inhaltsverzeichnis')"
@@ -22,21 +21,6 @@
                                 id="cw-ribbon-tool-contents"
                             />
                         </courseware-tab>
-                        <courseware-tab
-                            v-if="displayAdder"
-                            :name="$gettext('Elemente hinzufügen')"
-                            :selected="showBlockAdder"
-                            alias="blockadder"
-                            class="cw-ribbon-tool-blockadder-tab"
-                            :index="1"
-                        >
-                            <courseware-tools-blockadder
-                                v-if="showBlockAdder"
-                                id="cw-ribbon-tool-blockadder"
-                                :stickyRibbon="stickyRibbon"
-                                @blockAdded="$emit('blockAdded')"
-                            />
-                        </courseware-tab>
                     </courseware-tabs>
                     <button
                         :title="$gettext('schließen')"
@@ -52,7 +36,6 @@
 <script>
 import CoursewareTabs from '../layouts/CoursewareTabs.vue';
 import CoursewareTab from '../layouts/CoursewareTab.vue';
-import CoursewareToolsBlockadder from './CoursewareToolsBlockadder.vue';
 import CoursewareToolsContents from './CoursewareToolsContents.vue';
 import { FocusTrap } from 'focus-trap-vue';
 import { mapActions, mapGetters } from 'vuex';
@@ -62,7 +45,6 @@ export default {
     components: {
         CoursewareTabs,
         CoursewareTab,
-        CoursewareToolsBlockadder,
         CoursewareToolsContents,
         FocusTrap,
     },
@@ -106,22 +88,6 @@ export default {
         showEditMode() {
             return this.viewMode === 'edit';
         },
-        displayAdder() {
-            if (this.disableAdder) {
-                return false;
-            } else {
-                return !this.consumeMode && this.showEditMode && this.canEdit && !this.currentElementisLink;
-            }
-        },
-        displaySettings() {
-            if (this.disableSettings) {
-                return false;
-            } else {
-                let user = this.userById({ id: this.userId });
-                return !this.consumeMode && this.context.type === 'courses' && (this.isTeacher || ['root', 'admin'].includes(user.attributes.permission));
-            }
-            
-        },
         isTeacher() {
             return this.userIsTeacher;
         },
@@ -134,30 +100,6 @@ export default {
             setToolbarItem: 'coursewareSelectedToolbarItem',
             coursewareContainerAdder: 'coursewareContainerAdder'
         }),
-        selectTool(alias) {
-            this.showContents = false;
-            this.showBlockAdder = false;
-
-            switch (alias) {
-                case 'contents':
-                    this.showContents = true;
-                    this.disableContainerAdder();
-                    this.scrollToCurrent();
-                    break;
-                case 'blockadder':
-                    this.showBlockAdder = true;
-                    break;
-            }
-
-            if (this.selectedToolbarItem !== alias) {
-                this.setToolbarItem(alias);
-            }
-        },
-        disableContainerAdder() {
-            if (this.containerAdder !== false) {
-                this.coursewareContainerAdder(false);
-            }
-        },
         scrollToCurrent() {
             setTimeout(() => {
                 let contents = this.$refs.contents.$el; 
@@ -169,7 +111,7 @@ export default {
         },
     },
     mounted () {
-        this.selectTool(this.selectedToolbarItem);
+        this.scrollToCurrent();
     },
     watch: {
         adderStorage(newValue) {
diff --git a/resources/vue/components/courseware/structural-element/CoursewareStructuralElement.vue b/resources/vue/components/courseware/structural-element/CoursewareStructuralElement.vue
index 5edf736fdba..34d7fcbce2c 100644
--- a/resources/vue/components/courseware/structural-element/CoursewareStructuralElement.vue
+++ b/resources/vue/components/courseware/structural-element/CoursewareStructuralElement.vue
@@ -2,11 +2,11 @@
     <focus-trap v-model="consumModeTrap">
         <div>
             <div
+                v-if="validContext"
                 :class="{ 'cw-structural-element-consumemode': consumeMode }"
                 class="cw-structural-element"
-                v-if="validContext"
             >
-                <div class="cw-structural-element-content" v-if="structuralElement">
+                <div v-if="structuralElement" class="cw-structural-element-content">
                     <courseware-ribbon :canEdit="canEdit && canAddElements" :isContentBar="true" @blockAdded="updateContainerList">
                         <template #buttons>
                             <router-link v-if="prevElement" :to="'/structural_element/' + prevElement.id">
@@ -80,133 +80,136 @@
                             />
                         </template>
                     </courseware-ribbon>
-
-                    <div v-if="structuralElementLoaded && !isLink" class="cw-companion-box-wrapper">
-                        <courseware-companion-box
-                            v-if="!canVisit"
-                            mood="sad"
-                            :msgCompanion="$gettext('Diese Seite steht Ihnen leider nicht zur Verfügung.')"
-                        />
-                        <courseware-companion-box
-                            v-if="blockedByAnotherUser"
-                            :msgCompanion="$gettextInterpolate($gettext('Die Einstellungen dieser Seite werden im Moment von %{blockingUserName} bearbeitet'), {blockingUserName: blockingUserName})"
-                            mood="pointing"
-                        >
-                            <template #companionActions>
-                                <button v-if="userIsTeacher" class="button" @click="menuAction('removeLock')">
-                                    {{ textRemoveLock.title }}
-                                </button>
-                            </template>
-                        </courseware-companion-box>
-                        <courseware-empty-element-box
-                            v-if="showEmptyElementBox"
-                            :canEdit="canEdit"
-                            :noContainers="noContainers"
-                        />
-                        <courseware-welcome-screen v-if="noContainers && isRoot && canEdit" />
-                    </div>
-
-                    <div
-                        v-if="canVisit && !editView && !isLink"
-                        class="cw-container-wrapper"
-                        :class="{
-                            'cw-container-wrapper-consume': consumeMode,
-                            'cw-container-wrapper-discuss': discussView,
-                        }"
-                    >
-                        <courseware-structural-element-discussion
-                            v-if="!noContainers && discussView"
-                            :structuralElement="structuralElement"
-                            :canEdit="canEdit"
-                        />
-                        <component
-                            v-for="container in containers"
-                            :key="container.id"
-                            :is="containerComponent(container)"
-                            :container="container"
-                            :canEdit="canEdit"
-                            :canAddElements="canAddElements"
-                            :isTeacher="userIsTeacher"
-                            class="cw-container-item"
-                        />
-                    </div>
-                    <div
-                        v-if="isLink"
-                        class="cw-container-wrapper"
-                        :class="{
-                            'cw-container-wrapper-consume': consumeMode,
-                            'cw-container-wrapper-discuss': discussView,
-                        }"
-                    >
-                        <courseware-structural-element-discussion
-                            v-if="discussView"
-                            :structuralElement="structuralElement"
-                            :canEdit="canEdit"
-                        />
-                        <div v-if="editView" class="cw-companion-box-wrapper">
-                            <courseware-companion-box
-                                :msgCompanion="$gettextInterpolate($gettext('Dieser Inhalt ist aus den persönlichen Lernmaterialien von %{ ownerName } verlinkt und kann nur dort bearbeitet werden.'), { ownerName: ownerName })"
-                                mood="pointing"
-                            />
-                        </div>
-                        <component
-                            v-for="container in linkedContainers"
-                            :key="container.id"
-                            :is="containerComponent(container)"
-                            :container="container"
-                            :canEdit="false"
-                            :canAddElements="false"
-                            :isTeacher="userIsTeacher"
-                            class="cw-container-item"
-                        />
-                    </div>
-                    <div v-if="canVisit && canEdit && editView && !isLink" class="cw-container-wrapper cw-container-wrapper-edit">
-                        <template v-if="!processing">
-                            <span aria-live="assertive" class="assistive-text">{{ assistiveLive }}</span>
-                            <span id="operation" class="assistive-text">
-                                {{$gettext('Drücken Sie die Leertaste, um neu anzuordnen.')}}
-                            </span>
-                            <draggable
-                                class="cw-structural-element-list"
-                                tag="ol"
-                                role="listbox"
-                                v-model="containerList"
-                                v-bind="dragOptions"
-                                handle=".cw-sortable-handle"
-                                @start="isDragging = true"
-                                @end="dropContainer"
+                    <div class="cw-page-wrapper">
+                        <div class="cw-page-content">
+                            <div v-if="structuralElementLoaded && !isLink" class="cw-companion-box-wrapper">
+                                <courseware-companion-box
+                                    v-if="!canVisit"
+                                    mood="sad"
+                                    :msgCompanion="$gettext('Diese Seite steht Ihnen leider nicht zur Verfügung.')"
+                                />
+                                <courseware-companion-box
+                                    v-if="blockedByAnotherUser"
+                                    :msgCompanion="$gettextInterpolate($gettext('Die Einstellungen dieser Seite werden im Moment von %{blockingUserName} bearbeitet'), {blockingUserName: blockingUserName})"
+                                    mood="pointing"
+                                >
+                                    <template #companionActions>
+                                        <button v-if="userIsTeacher" class="button" @click="menuAction('removeLock')">
+                                            {{ textRemoveLock.title }}
+                                        </button>
+                                    </template>
+                                </courseware-companion-box>
+                                <courseware-empty-element-box
+                                    v-if="showEmptyElementBox"
+                                    :canEdit="canEdit"
+                                    :noContainers="noContainers"
+                                />
+                                <courseware-welcome-screen v-if="noContainers && isRoot && canEdit" />
+                            </div>
+
+                            <div
+                                v-if="canVisit && !editView && !isLink"
+                                class="cw-container-wrapper"
+                                :class="{
+                                    'cw-container-wrapper-consume': consumeMode,
+                                    'cw-container-wrapper-discuss': discussView,
+                                }"
                             >
-                                <li
-                                    v-for="container in containerList"
+                                <courseware-structural-element-discussion
+                                    v-if="!noContainers && discussView"
+                                    :structuralElement="structuralElement"
+                                    :canEdit="canEdit"
+                                />
+                                <component
+                                    v-for="container in containers"
                                     :key="container.id"
-                                    class="cw-container-item-sortable"
-                                >
-                                    <span
-                                        :class="{ 'cw-sortable-handle-dragging': isDragging }"
-                                        class="cw-sortable-handle"
-                                        tabindex="0"
-                                        role="button"
-                                        aria-describedby="operation"
-                                        :ref="'sortableHandle' + container.id"
-                                        @keydown="keyHandler($event, container.id)"
-                                    ></span>
-                                    <component
-                                        :is="containerComponent(container)"
-                                        :container="container"
-                                        :canEdit="canEdit"
-                                        :canAddElements="canAddElements"
-                                        :isTeacher="userIsTeacher"
-                                        class="cw-container-item"
-                                        ref="containers"
-                                        :class="{ 'cw-container-item-selected': keyboardSelected === container.id}"
+                                    :is="containerComponent(container)"
+                                    :container="container"
+                                    :canEdit="canEdit"
+                                    :canAddElements="canAddElements"
+                                    :isTeacher="userIsTeacher"
+                                    class="cw-container-item"
+                                />
+                            </div>
+                            <div
+                                v-if="isLink"
+                                class="cw-container-wrapper"
+                                :class="{
+                                    'cw-container-wrapper-consume': consumeMode,
+                                    'cw-container-wrapper-discuss': discussView,
+                                }"
+                            >
+                                <courseware-structural-element-discussion
+                                    v-if="discussView"
+                                    :structuralElement="structuralElement"
+                                    :canEdit="canEdit"
+                                />
+                                <div v-if="editView" class="cw-companion-box-wrapper">
+                                    <courseware-companion-box
+                                        :msgCompanion="$gettextInterpolate($gettext('Dieser Inhalt ist aus den persönlichen Lernmaterialien von %{ ownerName } verlinkt und kann nur dort bearbeitet werden.'), { ownerName: ownerName })"
+                                        mood="pointing"
                                     />
-                                </li>
-                            </draggable>
-                        </template>
-                        <studip-progress-indicator v-if="processing" :description="$gettext('Vorgang wird bearbeitet...')" />
+                                </div>
+                                <component
+                                    v-for="container in linkedContainers"
+                                    :key="container.id"
+                                    :is="containerComponent(container)"
+                                    :container="container"
+                                    :canEdit="false"
+                                    :canAddElements="false"
+                                    :isTeacher="userIsTeacher"
+                                    class="cw-container-item"
+                                />
+                            </div>
+                            <div v-if="canVisit && canEdit && editView && !isLink" class="cw-container-wrapper cw-container-wrapper-edit">
+                                <template v-if="!processing">
+                                    <span aria-live="assertive" class="assistive-text">{{ assistiveLive }}</span>
+                                    <span id="operation" class="assistive-text">
+                                        {{$gettext('Drücken Sie die Leertaste, um neu anzuordnen.')}}
+                                    </span>
+                                    <draggable
+                                        class="cw-structural-element-list"
+                                        tag="ol"
+                                        role="listbox"
+                                        v-model="containerList"
+                                        v-bind="dragOptions"
+                                        handle=".cw-sortable-handle"
+                                        @start="isDragging = true"
+                                        @end="dropContainer"
+                                    >
+                                        <li
+                                            v-for="container in containerList"
+                                            :key="container.id"
+                                            class="cw-container-item-sortable"
+                                        >
+                                            <span
+                                                :class="{ 'cw-sortable-handle-dragging': isDragging }"
+                                                class="cw-sortable-handle"
+                                                tabindex="0"
+                                                role="option"
+                                                aria-describedby="operation"
+                                                :ref="'sortableHandle' + container.id"
+                                                @keydown="keyHandler($event, container.id)"
+                                            ></span>
+                                            <component
+                                                :is="containerComponent(container)"
+                                                :container="container"
+                                                :canEdit="canEdit"
+                                                :canAddElements="canAddElements"
+                                                :isTeacher="userIsTeacher"
+                                                class="cw-container-item"
+                                                ref="containers"
+                                                :class="{ 'cw-container-item-selected': keyboardSelected === container.id}"
+                                            />
+                                        </li>
+                                    </draggable>
+                                </template>
+                                <studip-progress-indicator v-if="processing" :description="$gettext('Vorgang wird bearbeitet...')" />
+                            </div>
+                        </div>
+                        <courseware-toolbar v-if="canVisit && canEdit && editView && !isLink" /> 
                     </div>
                 </div>
-
                 <studip-dialog
                     v-if="showEditDialog"
                     :title="textEdit.title"
diff --git a/resources/vue/components/courseware/structural-element/CoursewareToolsBlockadder.vue b/resources/vue/components/courseware/structural-element/CoursewareToolsBlockadder.vue
deleted file mode 100644
index 86b42467fdf..00000000000
--- a/resources/vue/components/courseware/structural-element/CoursewareToolsBlockadder.vue
+++ /dev/null
@@ -1,436 +0,0 @@
-<template>
-    <div class="cw-tools-element-adder">
-        <courseware-tabs class="cw-tools-element-adder-tabs">
-            <courseware-tab :name="$gettext('Blöcke')" :selected="showBlockadder" :index="0" :style="{ maxHeight: maxHeight + 'px' }">
-                <form @submit.prevent="loadSearch">
-                    <div class="input-group files-search search cw-block-search">
-                        <input
-                            ref="searchBox"
-                            type="text"
-                            v-model="searchInput"
-                            @click.stop
-                            :label="$gettext('Geben Sie einen Suchbegriff mit mindestens 3 Zeichen ein.')"
-                        />
-                        <span class="input-group-append" @click.stop>
-                            <button v-if="searchInput"
-                                type="button"
-                                class="button reset-search"
-                                id="reset-search"
-                                :title="$gettext('Suche zurücksetzen')"
-                                @click="resetSearch"
-                            >
-                                <studip-icon shape="decline" :size="20"></studip-icon>
-                            </button>
-                            <button 
-                                type="submit"
-                                class="button"
-                                id="search-btn"
-                                :title="$gettext('Suche starten')"
-                                @click="loadSearch"
-                            >
-                                <studip-icon shape="search" :size="20"></studip-icon>
-                            </button>
-                        </span>
-                    </div>
-                </form>
-
-                <div class="filterpanel">
-                    <span class="sr-only">{{ $gettext('Kategorien Filter') }}</span>
-                    <button
-                        v-for="category in blockCategories"
-                        :key="category.type"
-                        class="button"
-                        :class="{'button-active': category.type ===  currentFilterCategory }"
-                        :aria-pressed="category.type ===  currentFilterCategory ? 'true' : 'false'"
-                        @click="selectCategory(category.type)"
-                    >
-                        {{ category.title }}
-                    </button>
-                </div>
-
-                <div v-if="filteredBlockTypes.length > 0" class="cw-blockadder-item-list">
-                    <courseware-blockadder-item
-                        v-for="(block, index) in filteredBlockTypes"
-                        :key="index"
-                        :title="block.title"
-                        :type="block.type"
-                        :description="block.description"
-                        @blockAdded="$emit('blockAdded')"
-                    />
-                </div>
-                <courseware-companion-box
-                    v-else
-                    :msgCompanion="$gettext('Es wurden keine passenden Blöcke gefunden.')"
-                    mood="pointing"
-                />
-            </courseware-tab>
-            <courseware-tab :name="$gettext('Abschnitte')" :selected="showContaineradder" :index="1" :style="{ maxHeight: maxHeight + 'px' }">
-                <div class="cw-container-style-selector" role="group" aria-labelledby="cw-containeradder-style">
-                    <p class="sr-only" id="cw-containeradder-style">{{ $gettext('Abschnitt-Stil') }}</p>
-                    <template
-                        v-for="style in containerStyles"
-                    >
-                        <input
-                            :key="style.key  + '-input'"
-                            type="radio"
-                            name="container-style"
-                            :id="'style-' + style.colspan"
-                            v-model="selectedContainerStyle"
-                            :value="style.colspan"
-                        />
-                        <label
-                            :key="style.key + '-label'"
-                            :for="'style-' + style.colspan"
-                            :class="[selectedContainerStyle === style.colspan ? 'cw-container-style-selector-active' : '', style.colspan]"
-                        >
-                            {{ style.title }}
-                        </label>
-                        
-                    </template>
-                </div>
-                <courseware-container-adder-item
-                    v-for="container in containerTypes"
-                    :key="container.type"
-                    :title="container.title"
-                    :type="container.type"
-                    :colspan="selectedContainerStyle"
-                    :description="container.description"
-                    :firstSection="$gettext('erstes Element')"
-                    :secondSection="$gettext('zweites Element')"
-                ></courseware-container-adder-item>
-            </courseware-tab>
-            <courseware-tab :name="$gettext('Merkliste')" :selected="showClipboard" :index="2" :style="{ maxHeight: maxHeight + 'px' }">
-                <courseware-collapsible-box :title="$gettext('Blöcke')" :open="clipboardBlocks.length > 0">
-                    <template v-if="clipboardBlocks.length > 0">
-                        <div class="cw-element-inserter-wrapper">
-                            <courseware-clipboard-item
-                                v-for="(clipboard, index) in clipboardBlocks"
-                                :key="index"
-                                :clipboard="clipboard"
-                                @inserted="$emit('blockAdded')"
-                            />
-                        </div>
-                        <button class="button trash" @click="clearClipboard('courseware-blocks')">
-                            {{ $gettext('Alle Blöcke aus Merkliste entfernen') }}
-                        </button>
-                    </template>
-                    <courseware-companion-box
-                        v-else
-                        mood="pointing"
-                        :msgCompanion="$gettext('Die Merkliste enthält keine Blöcke.')"
-                    />
-                </courseware-collapsible-box>
-                <courseware-collapsible-box :title="$gettext('Abschnitte')" :open="clipboardContainers.length > 0">
-                    <template v-if="clipboardContainers.length > 0">
-                        <div class="cw-element-inserter-wrapper">
-                            <courseware-clipboard-item
-                                v-for="(clipboard, index) in clipboardContainers"
-                                :key="index"
-                                :clipboard="clipboard"
-                            />
-                        </div>
-                        <button class="button trash" @click="clearClipboard('courseware-containers')">
-                            {{ $gettext('Alle Abschnitte aus Merkliste entfernen') }}
-                        </button>
-                    </template>
-                    <courseware-companion-box
-                        v-else
-                        mood="pointing"
-                        :msgCompanion="$gettext('Die Merkliste enthält keine Abschnitte.')"
-                    />
-                </courseware-collapsible-box>
-            </courseware-tab>
-        </courseware-tabs>
-        <studip-dialog
-            v-if="showDeleteClipboardDialog"
-            :title="textDeleteClipboardTitle"
-            :question="textDeleteClipboardAlert"
-            height="200"
-            width="500"
-            @confirm="executeDeleteClipboard"
-            @close="closeDeleteClipboardDialog"
-        ></studip-dialog>
-    </div>
-</template>
-
-<script>
-import CoursewareTabs from '../layouts/CoursewareTabs.vue';
-import CoursewareTab from '../layouts/CoursewareTab.vue';
-import CoursewareBlockadderItem from './CoursewareBlockadderItem.vue';
-import CoursewareClipboardItem from './CoursewareClipboardItem.vue';
-import CoursewareContainerAdderItem from '../containers/CoursewareContainerAdderItem.vue';
-import CoursewareCompanionBox from '../layouts/CoursewareCompanionBox.vue';
-import CoursewareCollapsibleBox from '../layouts/CoursewareCollapsibleBox.vue';
-import StudipDialog from '../../StudipDialog.vue';
-import { mapActions, mapGetters } from 'vuex';
-
-export default {
-    name: 'cw-tools-blockadder',
-    components: {
-        CoursewareTabs,
-        CoursewareTab,
-        CoursewareBlockadderItem,
-        CoursewareClipboardItem,
-        CoursewareContainerAdderItem,
-        CoursewareCompanionBox,
-        CoursewareCollapsibleBox,
-
-        StudipDialog,
-    },
-    props: {
-        stickyRibbon: {
-            type: Boolean,
-            default: false,
-        },
-    },
-    data() {
-        return {
-            showBlockadder: true,
-            showContaineradder: false,
-            showClipboard: false,
-            searchInput: '',
-            currentFilterCategory: '',
-            filteredBlockTypes: [],
-            categorizedBlocks: [],
-            selectedContainerStyle: 'full',
-            showDeleteClipboardDialog: false,
-            deleteClipboardType: null
-        };
-    },
-    computed: {
-        ...mapGetters({
-            adderStorage: 'blockAdder',
-            containerAdder: 'containerAdder',
-            unorderedBlockTypes: 'blockTypes',
-            containerTypes: 'containerTypes',
-            favoriteBlockTypes: 'favoriteBlockTypes',
-            showToolbar: 'showToolbar',
-            usersClipboards: 'courseware-clipboards/all',
-            userId: 'userId'
-        }),
-        blockTypes() {
-            let blockTypes = JSON.parse(JSON.stringify(this.unorderedBlockTypes));
-            blockTypes.sort((a, b) => {
-                return a.title > b.title ? 1 : b.title > a.title ? -1 : 0;
-            });
-            return blockTypes;
-        },
-        containerStyles() {
-            return [
-                { key: 0, title: this.$gettext('Volle Breite'), colspan: 'full'},
-                { key: 1, title: this.$gettext('Halbe Breite'), colspan: 'half' },
-                { key: 2, title: this.$gettext('Halbe Breite (zentriert)'), colspan: 'half-center' },
-            ];
-        },
-        blockCategories() {
-            return [
-                { title: this.$gettext('Favoriten'), type: 'favorite' },
-                { title: this.$gettext('Texte'), type: 'text' },
-                { title: this.$gettext('Multimedia'), type: 'multimedia' },
-                { title: this.$gettext('Interaktion'), type: 'interaction' },
-                { title: this.$gettext('Gestaltung'), type: 'layout' },
-                { title: this.$gettext('Externe Inhalte'), type: 'external' },
-                { title: this.$gettext('Biografie'), type: 'biography' },
-            ];
-        },
-        maxHeight() {
-            if (this.stickyRibbon) {
-                return parseInt(window.innerHeight * 0.75) - 120;
-            } else {
-                return parseInt(Math.min(window.innerHeight * 0.75, window.innerHeight - 197)) - 120;
-            }
-        },
-        clipboardBlocks() {
-            return this.usersClipboards
-                .filter(clipboard => clipboard.attributes['object-type'] === 'courseware-blocks')
-                .sort((a, b) => b.attributes.mkdate - a.attributes.mkdate);
-        },
-        clipboardContainers() {
-            return this.usersClipboards
-                .filter(clipboard => clipboard.attributes['object-type'] === 'courseware-containers')
-                .sort((a, b) => b.attributes.mkdate < a.attributes.mkdate);
-        },
-        textDeleteClipboardTitle() {
-            if (this.deleteClipboardType === 'courseware-blocks') {
-                return this.$gettext('Merkliste für Blöcke leeren');
-            }
-            if (this.deleteClipboardType === 'courseware-containers') {
-                return this.$gettext('Merkliste für Abschnitte leeren');
-            }
-            return '';
-        },
-        textDeleteClipboardAlert() {
-            if (this.deleteClipboardType === 'courseware-blocks') {
-                return this.$gettext('Möchten Sie die Merkliste für Blöcke unwiderruflich leeren?');
-            }
-            if (this.deleteClipboardType === 'courseware-containers') {
-                return this.$gettext('Möchten Sie die Merkliste für Abschnitte unwiderruflich leeren?');
-            }
-            return '';
-        }
-    },
-    methods: {
-        ...mapActions({
-            removeFavoriteBlockType: 'removeFavoriteBlockType',
-            addFavoriteBlockType: 'addFavoriteBlockType',
-            coursewareContainerAdder: 'coursewareContainerAdder',
-            companionWarning: 'companionWarning',
-            deleteUserClipboards: 'deleteUserClipboards'
-        }),
-        displayContainerAdder() {
-            this.showContaineradder = true;
-            this.showBlockadder = false;
-            this.showClipboard = false;
-        },
-        displayBlockAdder() {
-            this.showContaineradder = false;
-            this.showClipboard = false;
-            this.showBlockadder = true;
-            this.disableContainerAdder();
-        },
-        displayClipboard() {
-            this.showClipboard = true;
-        },
-        isBlockFav(block) {
-            let isFav = false;
-            this.favoriteBlockTypes.forEach((type) => {
-                if (type.type === block.type) {
-                    isFav = true;
-                }
-            });
-
-            return isFav;
-        },
-        disableContainerAdder() {
-            this.coursewareContainerAdder(false);
-        },
-        loadSearch() {
-            let searchTerms = this.searchInput.trim();
-            if (searchTerms.length < 3 && !this.currentFilterCategory) {
-                this.companionWarning({info: this.$gettext('Leider ist Ihr Suchbegriff zu kurz. Der Suchbegriff muss mindestens 3 Zeichen lang sein.')});
-                return;
-            }
-            this.filteredBlockTypes = this.blockTypes;
-
-            // filter results by given filter first so only these results are searched if an additional search term is given
-            if (this.currentFilterCategory) {
-                this.filterBlockTypesByCategory();
-                this.categorizedBlocks = this.filteredBlockTypes;
-            } else {
-                this.categorizedBlocks = this.blockTypes;
-            }
-
-            searchTerms = searchTerms.toLowerCase().split(' ');
-
-            // sort out block types that don't contain all search words
-            searchTerms.forEach(term => {
-                this.filteredBlockTypes = this.filteredBlockTypes.filter(block => (
-                    block.title.toLowerCase().includes(term)
-                    || block.description.toLowerCase().includes(term)
-                ));
-            });
-
-            // add block types to the search if a search term matches a tag even if they aren't in the given category
-            if (this.searchInput.trim().length > 0) {
-                this.filteredBlockTypes.push(...this.getBlockTypesByTags(searchTerms));
-                // remove possible duplicates
-                this.filteredBlockTypes = [...new Map(this.filteredBlockTypes.map(item => [item['title'], item])).values()];
-            }
-        },
-        filterBlockTypesByCategory() {
-            if (this.currentFilterCategory !== 'favorite') {
-                this.filteredBlockTypes = this.filteredBlockTypes.filter(block => block.categories.includes(this.currentFilterCategory));
-            } else {
-                this.filteredBlockTypes = this.favoriteBlockTypes;
-            }
-            
-        },
-        getBlockTypesByTags(searchTags) {
-            return this.categorizedBlocks.filter(block => {
-                const lowercaseTags = block.tags.map(blockTag => blockTag.toLowerCase());
-                for (const tag of searchTags) {
-                    if (lowercaseTags.filter(blockTag => blockTag.includes(tag.toLowerCase())).length > 0) {
-                        return true;
-                    }
-                }
-                return false;
-            });
-        },
-        selectCategory(type) {
-            if (this.currentFilterCategory !== type) {
-                this.currentFilterCategory = type;
-            } else {
-                this.resetCategory();
-            }
-        },
-        resetCategory() {
-            this.currentFilterCategory = '';
-            if (!this.searchInput) {
-                this.filteredBlockTypes = this.blockTypes;
-            } else {
-                this.loadSearch();
-            }
-        },
-        resetSearch() {
-            this.filteredBlockTypes = this.blockTypes;
-            this.searchInput = '';
-            this.currentFilterCategory = '';
-        },
-        clearClipboard(type) {
-            this.deleteClipboardType = type;
-            this.showDeleteClipboardDialog = true;
-        },
-        executeDeleteClipboard() {
-            if (this.deleteClipboardType) {
-                this.deleteUserClipboards({uid: this.userId, type: this.deleteClipboardType});
-            }
-            this.closeDeleteClipboardDialog();
-        },
-        closeDeleteClipboardDialog() {
-            this.showDeleteClipboardDialog = false;
-            this.deleteClipboardType = null;
-        }
-    },
-    mounted() {
-        if (this.containerAdder === true) {
-            this.displayContainerAdder();
-        }
-        this.filteredBlockTypes = this.blockTypes;
-        setTimeout(() => this.$refs.searchBox.focus(), 800);
-    },
-    watch: {
-        adderStorage(newValue) {
-            if (Object.keys(newValue).length !== 0) {
-                this.displayBlockAdder();
-            }
-        },
-        containerAdder(newValue) {
-            if (newValue === true) {
-                this.displayContainerAdder();
-            }
-        },
-        showToolbar(newValue, oldValue) {
-            if (oldValue === true && newValue === false) {
-                this.disableContainerAdder();
-            }
-        },
-        searchInput(newValue, oldValue) {
-            if (newValue.length >= 3 && newValue !== oldValue) {
-                this.loadSearch();
-            }
-            if (newValue.length < oldValue.length && newValue.length < 3) {
-                if (!this.currentFilterCategory) {
-                    this.filteredBlockTypes = this.blockTypes;
-                } else {
-                    this.loadSearch();
-                }
-            }
-        },
-        currentFilterCategory(newValue) {
-            if (newValue) {
-                this.loadSearch();
-            }
-        }
-    }
-};
-</script>
diff --git a/resources/vue/components/courseware/structural-element/structural-element-components.js b/resources/vue/components/courseware/structural-element/structural-element-components.js
index fdba4b41e1d..40586c2e683 100644
--- a/resources/vue/components/courseware/structural-element/structural-element-components.js
+++ b/resources/vue/components/courseware/structural-element/structural-element-components.js
@@ -1,3 +1,4 @@
+import CoursewareToolbar from './../toolbar/CoursewareToolbar.vue';
 // contentbar
 import CoursewareRibbon from './CoursewareRibbon.vue';
 import CoursewareTabs from '../layouts/CoursewareTabs.vue';
@@ -13,6 +14,7 @@ import CoursewareListContainer from '../containers/CoursewareListContainer.vue';
 import CoursewareTabsContainer from '../containers/CoursewareTabsContainer.vue';
 
 const StructuralElementComponents = {
+    CoursewareToolbar,
     //contentbar
     CoursewareRibbon,
     CoursewareTabs,
diff --git a/resources/vue/components/courseware/toolbar/CoursewareBlockadderItem.vue b/resources/vue/components/courseware/toolbar/CoursewareBlockadderItem.vue
new file mode 100644
index 00000000000..f7fe3030d69
--- /dev/null
+++ b/resources/vue/components/courseware/toolbar/CoursewareBlockadderItem.vue
@@ -0,0 +1,115 @@
+<template>
+    <div class="cw-blockadder-item-wrapper">
+        <a href="#" @click.prevent="addBlock" class="cw-blockadder-item" :class="['cw-blockadder-item-' + type]">
+            <header class="cw-blockadder-item-title">
+                {{ title }}
+            </header>
+            <p class="cw-blockadder-item-description">
+                {{ description }}
+            </p>
+        </a>
+        <button
+            class="cw-blockadder-item-fav"
+            :title="favButtonTitle"
+            @click="toggleFavItem()"
+        >
+            <studip-icon :shape="blockTypeIsFav ? 'star' : 'star-empty'" :size="20" />
+        </button>
+    </div>
+    
+</template>
+
+<script>
+import { mapActions, mapGetters } from 'vuex';
+
+export default {
+    name: 'courseware-blockadder-item',
+    components: {},
+    props: {
+        title: String,
+        description: String,
+        type: String,
+    },
+    data() {
+        return {
+            showInfo: false,
+        };
+    },
+    computed: {
+        ...mapGetters({
+            blockAdder: 'blockAdder',
+            blockById: 'courseware-blocks/byId',
+            lastCreatedBlock: 'courseware-blocks/lastCreated',
+            favoriteBlockTypes: 'favoriteBlockTypes',
+        }),
+        blockTypeIsFav() {
+            return this.favoriteBlockTypes.some((type) => type.type === this.type);
+        },
+        favButtonTitle() {
+            if (this.blockTypeIsFav) {
+                return this.$gettextInterpolate(
+                    this.$gettext('%{ blockName } Block aus den Favoriten entfernen'),
+                    { blockName: this.title }
+                );
+            }
+
+            return this.$gettextInterpolate(
+                this.$gettext('%{ blockName } Block zu Favoriten hinzufügen'),
+                { blockName: this.title }
+            );   
+        }
+    },
+    methods: {
+        ...mapActions({
+            companionInfo: 'companionInfo',
+            companionSuccess: 'companionSuccess',
+            companionWarning: 'companionWarning',
+            createBlock: 'createBlockInContainer',
+            lockObject: 'lockObject',
+            unlockObject: 'unlockObject',
+            loadBlock: 'courseware-blocks/loadById',
+            updateContainer: 'updateContainer',
+            removeFavoriteBlockType: 'removeFavoriteBlockType',
+            addFavoriteBlockType: 'addFavoriteBlockType',
+        }),
+        async addBlock() {
+            if (Object.keys(this.blockAdder).length !== 0) {
+                // lock parent container
+                await this.lockObject({ id: this.blockAdder.container.id, type: 'courseware-containers' });
+                // create new block
+                await this.createBlock({
+                    container: this.blockAdder.container,
+                    section: this.blockAdder.section,
+                    blockType: this.type,
+                });
+                //get new Block
+                const newBlock = this.lastCreatedBlock;
+                // update container information -> new block id in sections
+                let container = this.blockAdder.container;
+                container.attributes.payload.sections[this.blockAdder.section].blocks.push(newBlock.id);
+                const structuralElementId = container.relationships['structural-element'].data.id;
+                // update container
+                await this.updateContainer({ container, structuralElementId });
+                // unlock container
+                await this.unlockObject({ id: this.blockAdder.container.id, type: 'courseware-containers' });
+                this.companionSuccess({
+                    info: this.$gettext('Der Block wurde erfolgreich eingefügt.'),
+                });
+                this.$emit('blockAdded');
+            } else {
+                // companion action
+                this.companionWarning({
+                    info: this.$gettext('Bitte wählen Sie einen Ort aus, an dem der Block eingefügt werden soll.'),
+                });
+            }
+        },
+        toggleFavItem() {
+            if (this.blockTypeIsFav) {
+                this.removeFavoriteBlockType(this.type);
+            } else {
+                this.addFavoriteBlockType(this.type);
+            }
+        },
+    },
+};
+</script>
diff --git a/resources/vue/components/courseware/toolbar/CoursewareClipboardItem.vue b/resources/vue/components/courseware/toolbar/CoursewareClipboardItem.vue
new file mode 100644
index 00000000000..27d9e58c5f1
--- /dev/null
+++ b/resources/vue/components/courseware/toolbar/CoursewareClipboardItem.vue
@@ -0,0 +1,246 @@
+<template>
+    <div class="cw-clipboard-item-wrapper">
+        <button class="cw-clipboard-item" :class="['cw-clipboard-item-' + kind]" @click.prevent="insertItem">
+            <header class="sr-only">
+                {{ srTitle }}
+            </header>
+            <header class="cw-clipboard-item-title" aria-hidden="true">
+                {{ name }}
+            </header>
+            <p class="cw-clipboard-item-description">
+                {{ description }}
+            </p>
+        </button>
+        <div class="cw-clipboard-item-action-menu-wrapper">
+            <studip-action-menu
+                class="cw-clipboard-item-action-menu"
+                :items="menuItems"
+                :context="name"
+                @insertItemCopy="insertItemCopy"
+                @editItem="showEditItem"
+                @deleteItem="deleteItem"
+            />
+        </div>
+        <studip-dialog
+            v-if="showEditDialog"
+            :title="$gettext('Umbenennen')"
+            :confirmText="$gettext('Speichern')"
+            confirmClass="accept"
+            :closeText="$gettext('Abbrechen')"
+            closeClass="cancel"
+            height="360"
+            width="500"
+            @close="closeEditItem"
+            @confirm="storeItem"
+        >
+            <template v-slot:dialogContent>
+                <form class="default" @submit.prevent="">
+                    <label>
+                        {{ $gettext('Titel') }}
+                        <input type="text" v-model="currentClipboard.attributes.name" />
+                    </label>
+                    <label>
+                        {{ $gettext('Beschreibung') }}
+                        <textarea v-model="currentClipboard.attributes.description"></textarea>
+                    </label>
+                </form>
+            </template>
+        </studip-dialog>
+    </div>
+</template>
+
+<script>
+import { mapActions, mapGetters } from 'vuex';
+
+export default {
+    name: 'courseware-clipboard-item',
+    components: {},
+    props: {
+        clipboard: Object,
+    },
+    data() {
+        return {
+            showEditDialog: false,
+            currentClipboard: null,
+
+            text: {
+                errorMessage: this.$gettext('Es ist ein Fehler aufgetreten.'),
+                positionWarning: this.$gettext(
+                    'Bitte wählen Sie einen Ort aus, an dem der Block eingefügt werden soll.'
+                ),
+                blockSuccess: this.$gettext('Der Block wurde erfolgreich eingefügt.'),
+                containerSuccess: this.$gettext('Der Abschnitt wurde erfolgreich eingefügt.'),
+            },
+        };
+    },
+    computed: {
+        ...mapGetters({
+            blockAdder: 'blockAdder',
+            currentElement: 'currentElement',
+        }),
+        name() {
+            return this.clipboard.attributes.name;
+        },
+        description() {
+            return this.clipboard.attributes.description;
+        },
+        isBlock() {
+            return this.clipboard.attributes['object-type'] === 'courseware-blocks';
+        },
+        kind() {
+            return this.clipboard.attributes['object-kind'];
+        },
+        blockId() {
+            return this.clipboard.attributes['block-id'];
+        },
+        blockNotFound() {
+            return this.clipboard.relationships.block.data === null;
+        },
+        containerId() {
+            return this.clipboard.attributes['container-id'];
+        },
+        containerNotFound() {
+            return this.clipboard.relationships.container.data === null;
+        },
+        itemNotFound() {
+            if (this.isBlock) {
+                return this.blockNotFound;
+            }
+
+            return this.containerNotFound;
+        },
+        menuItems() {
+            let menuItems = [];
+            if (!this.itemNotFound) {
+                menuItems.push({
+                    id: 1,
+                    label: this.$gettext('Aktuellen Stand einfügen'),
+                    icon: 'copy',
+                    emit: 'insertItemCopy',
+                });
+            }
+            menuItems.push({ id: 2, label: this.$gettext('Umbenennen'), icon: 'edit', emit: 'editItem' });
+            menuItems.push({ id: 3, label: this.$gettext('Löschen'), icon: 'trash', emit: 'deleteItem' });
+
+            menuItems.sort((a, b) => a.id - b.id);
+            return menuItems;
+        },
+        blockAdderActive() {
+            return Object.keys(this.blockAdder).length !== 0;
+        },
+        srTitle() {
+            return this.isBlock ? 
+                this.$gettextInterpolate(this.$gettext(`Block %{name} einfügen`), { name: this.name }) :
+                this.$gettextInterpolate(this.$gettext(`Abschnitt %{name} einfügen`), { name: this.name });
+        }
+    },
+    methods: {
+        ...mapActions({
+            companionInfo: 'companionInfo',
+            companionSuccess: 'companionSuccess',
+            companionWarning: 'companionWarning',
+            copyContainer: 'copyContainer',
+            copyBlock: 'copyBlock',
+            clipboardInsertBlock: 'clipboardInsertBlock',
+            clipboardInsertContainer: 'clipboardInsertContainer',
+            loadStructuralElement: 'loadStructuralElement',
+            loadContainer: 'loadContainer',
+            deleteClipboard: 'courseware-clipboards/delete',
+            updateClipboard: 'courseware-clipboards/update',
+            loadClipboard: 'courseware-clipboards/loadById',
+        }),
+
+        async insertItem() {
+            let insertError = false;
+
+            if (this.isBlock) {
+                if (!this.blockAdderActive) {
+                    this.companionWarning({ info: this.text.positionWarning });
+                    return;
+                }
+                try {
+                    await this.clipboardInsertBlock({
+                        parentId: this.blockAdder.container.id,
+                        section: this.blockAdder.section,
+                        clipboard: this.clipboard,
+                    });
+                } catch (error) {
+                    insertError = true;
+                    this.companionWarning({ info: this.text.errorMessage });
+                }
+                if (!insertError) {
+                    await this.loadContainer(this.blockAdder.container.id);
+                    this.companionSuccess({ info: this.text.blockSuccess });
+                }
+            } else {
+                try {
+                    await this.clipboardInsertContainer({
+                        parentId: this.currentElement,
+                        clipboard: this.clipboard,
+                    });
+                } catch (error) {
+                    insertError = true;
+                    this.companionWarning({ info: this.text.errorMessage });
+                }
+                if (!insertError) {
+                    this.loadStructuralElement(this.currentElement);
+                    this.companionSuccess({ info: this.text.containerSuccess });
+                }
+            }
+        },
+
+        async insertItemCopy() {
+            let insertError = false;
+
+            if (this.isBlock) {
+                if (!this.blockAdderActive) {
+                    this.companionWarning({ info: this.text.positionWarning });
+                    return;
+                }
+                try {
+                    await this.copyBlock({
+                        parentId: this.blockAdder.container.id,
+                        section: this.blockAdder.section,
+                        block: { id: this.blockId },
+                    });
+                } catch (error) {
+                    insertError = true;
+                    this.companionWarning({ info: this.text.errorMessage });
+                }
+                if (!insertError) {
+                    await this.loadContainer(this.blockAdder.container.id);
+                    this.companionSuccess({ info: this.text.blockSuccess });
+                }
+            } else {
+                try {
+                    await this.copyContainer({ parentId: this.currentElement, container: { id: this.containerId } });
+                } catch (error) {
+                    insertError = true;
+                    this.companionWarning({ info: this.text.errorMessage });
+                }
+                if (!insertError) {
+                    this.loadStructuralElement(this.currentElement);
+                    this.companionSuccess({ info: this.text.containerSuccess });
+                }
+            }
+        },
+        deleteItem() {
+            this.deleteClipboard({ id: this.clipboard.id });
+        },
+        showEditItem() {
+            this.showEditDialog = true;
+        },
+        closeEditItem() {
+            this.showEditDialog = false;
+        },
+        async storeItem() {
+            this.closeEditItem();
+            await this.updateClipboard(this.currentClipboard);
+            this.loadClipboard({ id: this.currentClipboard.id });
+        },
+    },
+    mounted() {
+        this.currentClipboard = _.cloneDeep(this.clipboard);
+    },
+};
+</script>
diff --git a/resources/vue/components/courseware/toolbar/CoursewareContainerAdderItem.vue b/resources/vue/components/courseware/toolbar/CoursewareContainerAdderItem.vue
new file mode 100644
index 00000000000..78cc4eda3bc
--- /dev/null
+++ b/resources/vue/components/courseware/toolbar/CoursewareContainerAdderItem.vue
@@ -0,0 +1,52 @@
+<template>
+    <a href="#" @click.prevent="addContainer">
+        <div class="cw-containeradder-item" :class="['cw-containeradder-item-' + type]">
+            <header class="cw-containeradder-item-title">
+                {{ title }}
+            </header>
+            <p class="cw-containeradder-item-description">
+                {{ description }}
+            </p>
+        </div>
+    </a>
+</template>
+<script>
+import { mapActions } from 'vuex';
+export default {
+    name: 'courseware-container-adder-item',
+    components: {},
+    props: {
+        title: String,
+        description: String,
+        type: String,
+        colspan: String,
+        firstSection: String,
+        secondSection: String,
+    },
+    methods: {
+        ...mapActions({
+            createContainer: 'createContainer',
+            companionSuccess: 'companionSuccess',
+        }),
+        async addContainer() {
+            let attributes = {};
+            attributes["container-type"] = this.type;
+            let sections = [];
+            if (this.type === 'list') {
+                sections = [{ name: this.firstSection, icon: '', blocks: [] }];
+            } else {
+                sections = [{ name: this.firstSection, icon: '', blocks: [] },{ name: this.secondSection, icon: '', blocks: [] }];
+            }
+            attributes.payload = {
+                colspan: this.colspan,
+                sections: sections,
+            };
+            await this.createContainer({ structuralElementId: this.$route.params.id, attributes: attributes });
+            this.companionSuccess({
+                info: this.$gettext('Der Abschnitt wurde erfolgreich eingefügt.'),
+            });
+        },
+    },
+    mounted() {},
+};
+</script>
diff --git a/resources/vue/components/courseware/toolbar/CoursewareToolbar.vue b/resources/vue/components/courseware/toolbar/CoursewareToolbar.vue
new file mode 100644
index 00000000000..252f3fe6ce6
--- /dev/null
+++ b/resources/vue/components/courseware/toolbar/CoursewareToolbar.vue
@@ -0,0 +1,170 @@
+<template>
+    <div class="cw-toolbar-wrapper">
+        <div id="cw-toolbar" class="cw-toolbar" :style="toolbarStyle">
+            <div v-if="showTools" class="cw-toolbar-tools" :class="{ unfold: unfold, hd: isHd, wqhd: isWqhd}">
+                <div class="cw-toolbar-button-wrapper">
+                    <button
+                        class="cw-toolbar-button"
+                        :class="{active: activeTool === 'blockAdder'}"
+                        :title="$gettext('Blöcke hinzufügen')"
+                        @click="activateTool('blockAdder')"
+                    >
+                        {{ $gettext('Blöcke') }}
+                    </button>
+                    <button
+                        class="cw-toolbar-button"
+                        :class="{active: activeTool === 'containerAdder'}"
+                        :title="$gettext('Abschnitte hinzufügen')"
+                        @click="activateTool('containerAdder')"
+                    >
+                        {{ $gettext('Abschnitte') }}
+                    </button>
+                    <button
+                        class="cw-toolbar-button"
+                        :class="{active: activeTool === 'clipboard'}"
+                        :title="$gettext('Block Merkliste')"
+                        @click="activateTool('clipboard')"
+                    >
+                        {{ $gettext('Merkliste') }}
+                    </button>
+                    <button
+                        class="cw-toolbar-button cw-toolbar-button-toggle cw-toolbar-button-toggle-out"
+                        :title="$gettext('Werkzeugleiste einklappen')"
+                        @click="toggleToolbar"
+                    ></button>
+                </div>
+                <courseware-toolbar-blocks v-if="activeTool === 'blockAdder'" />
+                <courseware-toolbar-containers v-if="activeTool === 'containerAdder'" />
+                <courseware-toolbar-clipboard v-if="activeTool === 'clipboard'" />
+            </div>
+            <button
+                v-else
+                class="cw-toolbar-button cw-toolbar-button-toggle cw-toolbar-button-toggle-in"
+                :title="$gettext('Werkzeugleiste ausklappen')"
+                @click="toggleToolbar"
+            ></button>
+            <div class="cw-toolbar-spacer-right"></div>
+        </div>
+    </div>
+</template>
+
+<script>
+import CoursewareToolbarBlocks from './CoursewareToolbarBlocks.vue';
+import CoursewareToolbarContainers from './CoursewareToolbarContainers.vue';
+import CoursewareToolbarClipboard from './CoursewareToolbarClipboard.vue';
+
+export default {
+    name: 'courseware-toolbar',
+    components: {
+        CoursewareToolbarBlocks,
+        CoursewareToolbarContainers,
+        CoursewareToolbarClipboard
+    },
+    data() {
+        return {
+            toolsActive: true,
+            unfold: true,
+            showTools: true,
+            toolbarTop: 0,
+            activeTool: 'blockAdder',
+
+            windowWidth: window.outerWidth,
+            windowInnerHeight: window.innerHeight
+        };
+    },
+    computed: {
+        toolbarStyle() {
+            const footerHeight = document.getElementById('main-footer').getBoundingClientRect().height;
+            const scrollTopStyles = window.getComputedStyle(document.getElementById('scroll-to-top'));
+            const scrollTopHeight = parseInt(scrollTopStyles['height']) + parseInt(scrollTopStyles['padding-top']) + parseInt(scrollTopStyles['padding-bottom']);
+            let height = parseInt(
+                Math.min(this.windowInnerHeight * 0.9, this.windowInnerHeight - this.toolbarTop - scrollTopHeight - footerHeight)
+            );
+
+            return {
+                height: height + 'px',
+                minHeight: height + 'px',
+                top: this.toolbarTop + 'px',
+            };
+        },
+        toolbarHeader() {
+            let header = '';
+            if (this.activeTool === 'blockAdder') {
+                header = this.$gettext('Block hinzufügen');
+            }
+            if (this.activeTool === 'containerAdder') {
+                header = this.$gettext('Abschnitt hinzufügen');
+            }
+
+            return header;
+        },
+        isHd() {
+            return this.windowWidth >= 1920;
+        },
+        isWqhd() {
+            return this.windowWidth >= 2560;
+        },
+    },
+    methods: {
+        toggleToolbar() {
+            this.toolsActive = !this.toolsActive;
+        },
+        activateTool(tool) {
+            this.activeTool = tool;
+        },
+        updateToolbarTop() {
+            const responsiveContentbar = document.getElementById('responsive-contentbar');
+            if (responsiveContentbar) {
+                const contentbarRect = this.responsiveContentbar.getBoundingClientRect();
+                this.toolbarTop = contentbarRect.bottom + 35;
+                return;
+            }
+
+            const ribbon = document.getElementById('cw-ribbon') ?? document.getElementById('contentbar');
+            if (ribbon) {
+                const contentbarRect = ribbon.getBoundingClientRect();
+                if (ribbon.classList.contains("cw-ribbon-sticky")) {
+                    this.toolbarTop = contentbarRect.bottom + 16;
+                } else {
+                    this.toolbarTop = contentbarRect.bottom + 15;
+                }
+            }
+            
+        },
+        onResize() {
+            this.windowWidth = window.outerWidth;
+            this.windowInnerHeight = window.innerHeight;
+        }
+    },
+    mounted() {
+        this.updateToolbarTop();
+        this.$nextTick(() => {
+            window.addEventListener('scroll', this.updateToolbarTop);
+            window.addEventListener('resize', this.onResize);
+        });
+    },
+    beforeDestroy() { 
+        window.removeEventListener('scroll', this.updateToolbarTop);
+        window.removeEventListener('resize', this.onResize); 
+    },
+
+    watch: {
+        toolsActive(newState, oldState) {
+            let view = this;
+            if (newState) {
+                this.showTools = true;
+                setTimeout(() => {
+                    view.unfold = true;
+                }, 10);
+            } else {
+                this.unfold = false;
+                setTimeout(() => {
+                    if (!view.toolsActive) {
+                        view.showTools = false;
+                    }
+                }, 600);
+            }
+        },
+    },
+};
+</script>
diff --git a/resources/vue/components/courseware/toolbar/CoursewareToolbarBlocks.vue b/resources/vue/components/courseware/toolbar/CoursewareToolbarBlocks.vue
new file mode 100644
index 00000000000..3cd783c3fc6
--- /dev/null
+++ b/resources/vue/components/courseware/toolbar/CoursewareToolbarBlocks.vue
@@ -0,0 +1,212 @@
+<template>
+    <div class="cw-toolbar-blocks">
+        <form @submit.prevent="loadSearch">
+            <div class="input-group files-search search cw-block-search">
+                <input
+                    ref="searchBox"
+                    type="text"
+                    v-model="searchInput"
+                    @click.stop
+                    :label="$gettext('Geben Sie einen Suchbegriff mit mindestens 3 Zeichen ein.')"
+                />
+                <span class="input-group-append" @click.stop>
+                    <button v-if="searchInput"
+                        type="button"
+                        class="button reset-search"
+                        id="reset-search"
+                        :title="$gettext('Suche zurücksetzen')"
+                        @click="resetSearch"
+                    >
+                        <studip-icon shape="decline" :size="20"></studip-icon>
+                    </button>
+                    <button 
+                        type="submit"
+                        class="button"
+                        id="search-btn"
+                        :title="$gettext('Suche starten')"
+                        @click="loadSearch"
+                    >
+                        <studip-icon shape="search" :size="20"></studip-icon>
+                    </button>
+                </span>
+            </div>
+        </form>
+
+        <div class="filterpanel">
+            <span class="sr-only">{{ $gettext('Kategorien Filter') }}</span>
+            <button
+                v-for="category in blockCategories"
+                :key="category.type"
+                class="button"
+                :class="{'button-active': category.type ===  currentFilterCategory }"
+                :aria-pressed="category.type ===  currentFilterCategory ? 'true' : 'false'"
+                @click="selectCategory(category.type)"
+            >
+                {{ category.title }}
+            </button>
+        </div>
+
+        <div v-if="filteredBlockTypes.length > 0" class="cw-blockadder-item-list">
+            <courseware-blockadder-item
+                v-for="(block, index) in filteredBlockTypes"
+                :key="index"
+                :title="block.title"
+                :type="block.type"
+                :description="block.description"
+                @blockAdded="$emit('blockAdded')"
+            />
+        </div>
+        <courseware-companion-box
+            v-else
+            :msgCompanion="$gettext('Es wurden keine passenden Blöcke gefunden.')"
+            mood="pointing"
+        />
+    </div>
+</template>
+
+<script>
+import CoursewareBlockadderItem from './CoursewareBlockadderItem.vue';
+import CoursewareCompanionBox from '../layouts/CoursewareCompanionBox.vue';
+import { mapActions, mapGetters } from 'vuex';
+
+export default {
+    name: 'courseware-toolbar-blocks',
+    components: {
+        CoursewareBlockadderItem,
+        CoursewareCompanionBox
+    },
+    data() {
+        return {
+            searchInput: '',
+            currentFilterCategory: '',
+            filteredBlockTypes: [],
+            categorizedBlocks: []
+        };
+    },
+    computed: {
+        ...mapGetters({
+            adderStorage: 'blockAdder',
+            unorderedBlockTypes: 'blockTypes',
+            favoriteBlockTypes: 'favoriteBlockTypes',
+        }),
+        blockTypes() {
+            let blockTypes = JSON.parse(JSON.stringify(this.unorderedBlockTypes));
+            blockTypes.sort((a, b) => {
+                return a.title > b.title ? 1 : b.title > a.title ? -1 : 0;
+            });
+            return blockTypes;
+        },
+        blockCategories() {
+            return [
+                { title: this.$gettext('Favoriten'), type: 'favorite' },
+                { title: this.$gettext('Texte'), type: 'text' },
+                { title: this.$gettext('Multimedia'), type: 'multimedia' },
+                { title: this.$gettext('Interaktion'), type: 'interaction' },
+                { title: this.$gettext('Gestaltung'), type: 'layout' },
+                { title: this.$gettext('Externe Inhalte'), type: 'external' },
+                { title: this.$gettext('Biografie'), type: 'biography' }
+            ];
+        }
+    },
+    methods: {
+        ...mapActions({
+            companionWarning: 'companionWarning'
+        }),
+        loadSearch() {
+            let searchTerms = this.searchInput.trim();
+            if (searchTerms.length < 3 && !this.currentFilterCategory) {
+                this.companionWarning({info: this.$gettext('Leider ist Ihr Suchbegriff zu kurz. Der Suchbegriff muss mindestens 3 Zeichen lang sein.')});
+                return;
+            }
+            this.filteredBlockTypes = this.blockTypes;
+
+            // filter results by given filter first so only these results are searched if an additional search term is given
+            if (this.currentFilterCategory) {
+                this.filterBlockTypesByCategory();
+                this.categorizedBlocks = this.filteredBlockTypes;
+            } else {
+                this.categorizedBlocks = this.blockTypes;
+            }
+
+            searchTerms = searchTerms.toLowerCase().split(' ');
+
+            // sort out block types that don't contain all search words
+            searchTerms.forEach(term => {
+                this.filteredBlockTypes = this.filteredBlockTypes.filter(block => (
+                    block.title.toLowerCase().includes(term)
+                    || block.description.toLowerCase().includes(term)
+                ));
+            });
+
+            // add block types to the search if a search term matches a tag even if they aren't in the given category
+            if (this.searchInput.trim().length > 0) {
+                this.filteredBlockTypes.push(...this.getBlockTypesByTags(searchTerms));
+                // remove possible duplicates
+                this.filteredBlockTypes = [...new Map(this.filteredBlockTypes.map(item => [item['title'], item])).values()];
+            }
+        },
+        filterBlockTypesByCategory() {
+            if (this.currentFilterCategory !== 'favorite') {
+                this.filteredBlockTypes = this.filteredBlockTypes.filter(block => block.categories.includes(this.currentFilterCategory));
+            } else {
+                this.filteredBlockTypes = this.favoriteBlockTypes;
+            }
+            
+        },
+        getBlockTypesByTags(searchTags) {
+            return this.categorizedBlocks.filter(block => {
+                const lowercaseTags = block.tags.map(blockTag => blockTag.toLowerCase());
+                for (const tag of searchTags) {
+                    if (lowercaseTags.filter(blockTag => blockTag.includes(tag.toLowerCase())).length > 0) {
+                        return true;
+                    }
+                }
+                return false;
+            });
+        },
+        selectCategory(type) {
+            if (this.currentFilterCategory !== type) {
+                this.currentFilterCategory = type;
+            } else {
+                this.resetCategory();
+            }
+        },
+        resetCategory() {
+            this.currentFilterCategory = '';
+            if (!this.searchInput) {
+                this.filteredBlockTypes = this.blockTypes;
+            } else {
+                this.loadSearch();
+            }
+        },
+        resetSearch() {
+            this.filteredBlockTypes = this.blockTypes;
+            this.searchInput = '';
+            this.currentFilterCategory = '';
+        }
+    },
+    mounted() {
+        this.filteredBlockTypes = this.blockTypes;
+        setTimeout(() => this.$refs.searchBox.focus(), 800);
+    },
+    watch: {
+        searchInput(newValue, oldValue) {
+            if (newValue.length >= 3 && newValue !== oldValue) {
+                this.loadSearch();
+            }
+            if (newValue.length < oldValue.length && newValue.length < 3) {
+                if (!this.currentFilterCategory) {
+                    this.filteredBlockTypes = this.blockTypes;
+                } else {
+                    this.loadSearch();
+                }
+            }
+        },
+        currentFilterCategory(newValue) {
+            if (newValue) {
+                this.loadSearch();
+            }
+        }
+    }
+}
+</script>
\ No newline at end of file
diff --git a/resources/vue/components/courseware/toolbar/CoursewareToolbarClipboard.vue b/resources/vue/components/courseware/toolbar/CoursewareToolbarClipboard.vue
new file mode 100644
index 00000000000..e7898cd03c1
--- /dev/null
+++ b/resources/vue/components/courseware/toolbar/CoursewareToolbarClipboard.vue
@@ -0,0 +1,134 @@
+<template>
+    <div class="cw-toolbar-clipboard">
+        <courseware-collapsible-box :title="$gettext('Blöcke')" :open="clipboardBlocks.length > 0">
+            <template v-if="clipboardBlocks.length > 0">
+                <div class="cw-element-inserter-wrapper">
+                    <courseware-clipboard-item
+                        v-for="(clipboard, index) in clipboardBlocks"
+                        :key="index"
+                        :clipboard="clipboard"
+                        @inserted="$emit('blockAdded')"
+                    />
+                </div>
+                <button class="button trash" @click="clearClipboard('courseware-blocks')">
+                    {{ $gettext('Alle Blöcke aus Merkliste entfernen') }}
+                </button>
+            </template>
+            <courseware-companion-box
+                v-else
+                mood="pointing"
+                :msgCompanion="$gettext('Die Merkliste enthält keine Blöcke.')"
+            />
+        </courseware-collapsible-box>
+        <courseware-collapsible-box :title="$gettext('Abschnitte')" :open="clipboardContainers.length > 0">
+            <template v-if="clipboardContainers.length > 0">
+                <div class="cw-element-inserter-wrapper">
+                    <courseware-clipboard-item
+                        v-for="(clipboard, index) in clipboardContainers"
+                        :key="index"
+                        :clipboard="clipboard"
+                    />
+                </div>
+                <button class="button trash" @click="clearClipboard('courseware-containers')">
+                    {{ $gettext('Alle Abschnitte aus Merkliste entfernen') }}
+                </button>
+            </template>
+            <courseware-companion-box
+                v-else
+                mood="pointing"
+                :msgCompanion="$gettext('Die Merkliste enthält keine Abschnitte.')"
+            />
+        </courseware-collapsible-box>
+        <studip-dialog
+            v-if="showDeleteClipboardDialog"
+            :title="textDeleteClipboardTitle"
+            :question="textDeleteClipboardAlert"
+            height="200"
+            width="500"
+            @confirm="executeDeleteClipboard"
+            @close="closeDeleteClipboardDialog"
+        ></studip-dialog>
+
+    </div>
+</template>
+
+<script>
+import CoursewareClipboardItem from './CoursewareClipboardItem.vue';
+import CoursewareCompanionBox from '../layouts/CoursewareCompanionBox.vue';
+import CoursewareCollapsibleBox from '../layouts/CoursewareCollapsibleBox.vue';
+import StudipDialog from '../../StudipDialog.vue';
+
+import { mapActions, mapGetters } from 'vuex';
+
+export default {
+    name: 'cw-tools-blockadder',
+    components: {
+        CoursewareClipboardItem,
+        CoursewareCompanionBox,
+        CoursewareCollapsibleBox,
+        StudipDialog,
+    },
+
+    data() {
+        return {
+            showDeleteClipboardDialog: false,
+            deleteClipboardType: null
+        };
+    },
+    computed: {
+        ...mapGetters({
+            usersClipboards: 'courseware-clipboards/all',
+            userId: 'userId'
+        }),
+        clipboardBlocks() {
+            return this.usersClipboards
+                .filter(clipboard => clipboard.attributes['object-type'] === 'courseware-blocks')
+                .sort((a, b) => b.attributes.mkdate - a.attributes.mkdate);
+        },
+        clipboardContainers() {
+            return this.usersClipboards
+                .filter(clipboard => clipboard.attributes['object-type'] === 'courseware-containers')
+                .sort((a, b) => b.attributes.mkdate < a.attributes.mkdate);
+        },
+        textDeleteClipboardTitle() {
+            if (this.deleteClipboardType === 'courseware-blocks') {
+                return this.$gettext('Merkliste für Blöcke leeren');
+            }
+            if (this.deleteClipboardType === 'courseware-containers') {
+                return this.$gettext('Merkliste für Abschnitte leeren');
+            }
+            return '';
+        },
+        textDeleteClipboardAlert() {
+            if (this.deleteClipboardType === 'courseware-blocks') {
+                return this.$gettext('Möchten Sie die Merkliste für Blöcke unwiderruflich leeren?');
+            }
+            if (this.deleteClipboardType === 'courseware-containers') {
+                return this.$gettext('Möchten Sie die Merkliste für Abschnitte unwiderruflich leeren?');
+            }
+            return '';
+        }
+    },
+    methods: {
+        ...mapActions({
+            companionWarning: 'companionWarning',
+            deleteUserClipboards: 'deleteUserClipboards'
+        }),
+        clearClipboard(type) {
+            this.deleteClipboardType = type;
+            this.showDeleteClipboardDialog = true;
+        },
+        executeDeleteClipboard() {
+            if (this.deleteClipboardType) {
+                this.deleteUserClipboards({uid: this.userId, type: this.deleteClipboardType});
+            }
+            this.closeDeleteClipboardDialog();
+        },
+        closeDeleteClipboardDialog() {
+            this.showDeleteClipboardDialog = false;
+            this.deleteClipboardType = null;
+        }
+    }
+};
+
+</script>
\ No newline at end of file
diff --git a/resources/vue/components/courseware/toolbar/CoursewareToolbarContainers.vue b/resources/vue/components/courseware/toolbar/CoursewareToolbarContainers.vue
new file mode 100644
index 00000000000..12fea1bbd05
--- /dev/null
+++ b/resources/vue/components/courseware/toolbar/CoursewareToolbarContainers.vue
@@ -0,0 +1,66 @@
+<template>
+    <div class="cw-toolbar-containers">
+        <div class="cw-container-style-selector" role="group" aria-labelledby="cw-containeradder-style">
+                    <p class="sr-only" id="cw-containeradder-style">{{ $gettext('Abschnitt-Stil') }}</p>
+                    <template
+                        v-for="style in containerStyles"
+                    >
+                        <input
+                            :key="style.key  + '-input'"
+                            type="radio"
+                            name="container-style"
+                            :id="'style-' + style.colspan"
+                            v-model="selectedContainerStyle"
+                            :value="style.colspan"
+                        />
+                        <label
+                            :key="style.key + '-label'"
+                            :for="'style-' + style.colspan"
+                            :class="[selectedContainerStyle === style.colspan ? 'cw-container-style-selector-active' : '', style.colspan]"
+                        >
+                            {{ style.title }}
+                        </label>
+                        
+                    </template>
+                </div>
+                <courseware-container-adder-item
+                    v-for="container in containerTypes"
+                    :key="container.type"
+                    :title="container.title"
+                    :type="container.type"
+                    :colspan="selectedContainerStyle"
+                    :description="container.description"
+                    :firstSection="$gettext('erstes Element')"
+                    :secondSection="$gettext('zweites Element')"
+                ></courseware-container-adder-item>
+    </div>
+</template>
+
+<script>
+import CoursewareContainerAdderItem from './CoursewareContainerAdderItem.vue';
+import { mapGetters } from 'vuex';
+
+export default {
+    name: 'courseware-toolbar-containers',
+    components: {
+        CoursewareContainerAdderItem
+    },
+    data() {
+        return {
+            selectedContainerStyle: 'full'
+        };
+    },
+    computed: {
+        ...mapGetters({
+            containerTypes: 'containerTypes'
+        }),
+        containerStyles() {
+            return [
+                { key: 0, title: this.$gettext('Volle Breite'), colspan: 'full'},
+                { key: 1, title: this.$gettext('Halbe Breite'), colspan: 'half' },
+                { key: 2, title: this.$gettext('Halbe Breite (zentriert)'), colspan: 'half-center' }
+            ];
+        },
+    }
+}
+</script>
\ No newline at end of file
-- 
GitLab