From ba52642f0634f211432e877d9dff0d2dec75d806 Mon Sep 17 00:00:00 2001
From: Ron Lucke <lucke@elan-ev.de>
Date: Mon, 18 Dec 2023 12:03:38 +0000
Subject: [PATCH] StEP #3255

Merge request studip/studip!2355
---
 ....5.11_courseware_add_optional_comments.php |  21 +++
 .../JsonApi/Routes/Courseware/Authority.php   |   4 +-
 .../Routes/Courseware/BlocksCreate.php        |   1 +
 .../Routes/Courseware/BlocksUpdate.php        |   4 +
 .../Courseware/StructuralElementsCreate.php   |   3 +-
 .../Courseware/StructuralElementsUpdate.php   |   4 +
 .../JsonApi/Routes/Courseware/UnitsCreate.php |   3 +-
 .../JsonApi/Schemas/Courseware/Block.php      |   1 +
 .../Schemas/Courseware/StructuralElement.php  |   1 +
 lib/models/Courseware/Block.php               |   4 +
 lib/models/Courseware/StructuralElement.php   |   9 +-
 .../assets/stylesheets/scss/courseware.scss   |   3 +-
 .../scss/courseware/blocks/default-block.scss |  37 +----
 .../stylesheets/scss/courseware/comments.scss |  26 ++-
 .../containers/default-container.scss         |  18 --
 .../courseware/layouts/call-to-action.scss    |  19 +++
 .../scss/courseware/layouts/talk-bubble.scss  | 133 ++++++++++-----
 .../scss/courseware/structural-element.scss   |  41 +----
 .../blocks/CoursewareBlockActions.vue         |  33 ++++
 .../blocks/CoursewareBlockComments.vue        |  10 +-
 .../blocks/CoursewareBlockDiscussion.vue      | 116 +++++++++----
 .../blocks/CoursewareBlockFeedback.vue        |  33 ++--
 .../blocks/CoursewareDefaultBlock.vue         |  61 +++++--
 .../layouts/CoursewareCallToActionBox.vue     |  70 ++++++++
 .../layouts/CoursewareTalkBubble.vue          |  58 +++++--
 .../CoursewareStructuralElement.vue           | 157 ++++++++++++++++--
 .../CoursewareStructuralElementComments.vue   |  10 +-
 .../CoursewareStructuralElementFeedback.vue   |  52 +++---
 .../widgets/CoursewareViewWidget.vue          |  14 --
 .../vue/store/courseware/courseware.module.js |  52 ++++++
 30 files changed, 713 insertions(+), 285 deletions(-)
 create mode 100644 db/migrations/5.5.11_courseware_add_optional_comments.php
 create mode 100644 resources/assets/stylesheets/scss/courseware/layouts/call-to-action.scss
 create mode 100644 resources/vue/components/courseware/layouts/CoursewareCallToActionBox.vue

diff --git a/db/migrations/5.5.11_courseware_add_optional_comments.php b/db/migrations/5.5.11_courseware_add_optional_comments.php
new file mode 100644
index 00000000000..7c12a2ef892
--- /dev/null
+++ b/db/migrations/5.5.11_courseware_add_optional_comments.php
@@ -0,0 +1,21 @@
+<?php
+
+final class CoursewareAddOptionalComments extends Migration
+{
+    public function description()
+    {
+        return 'Add column commentable to cw_blocks and cw_structural_elements';
+    }
+
+    protected function up()
+    {
+        DBManager::get()->exec("ALTER TABLE `cw_blocks` ADD `commentable` TINYINT(1) NOT NULL AFTER `visible`"); 
+        DBManager::get()->exec("ALTER TABLE `cw_structural_elements` ADD `commentable` TINYINT(1) NOT NULL AFTER `public`"); 
+    }
+
+    protected function down()
+    {
+        DBManager::get()->exec("ALTER TABLE `cw_blocks` DROP `commentable`"); 
+        DBManager::get()->exec("ALTER TABLE `cw_structural_elements` DROP `commentable`"); 
+    }
+}
diff --git a/lib/classes/JsonApi/Routes/Courseware/Authority.php b/lib/classes/JsonApi/Routes/Courseware/Authority.php
index 3df103d81a2..88eb3df36b8 100644
--- a/lib/classes/JsonApi/Routes/Courseware/Authority.php
+++ b/lib/classes/JsonApi/Routes/Courseware/Authority.php
@@ -400,7 +400,7 @@ class Authority
         return $perm;
     }
 
-    public static function canUpdateStructuralElementFeedback(User $user, StructuralElementComment $resource)
+    public static function canUpdateStructuralElementFeedback(User $user, StructuralElementFeedback $resource)
     {
         return self::canCreateStructuralElementFeedback($user, $resource->structural_element);
     }
@@ -410,7 +410,7 @@ class Authority
         return $resource->user_id === $user->id || self::canUpdateStructuralElement($user, $resource->structural_element);
     }
 
-    public static function canDeleteStructuralElementFeedback(User $user, StructuralElementComment $resource)
+    public static function canDeleteStructuralElementFeedback(User $user, StructuralElementFeedback $resource)
     {
         return self::canUpdateStructuralElementFeedback($user, $resource);
     }
diff --git a/lib/classes/JsonApi/Routes/Courseware/BlocksCreate.php b/lib/classes/JsonApi/Routes/Courseware/BlocksCreate.php
index 04c7d928b1f..d1d3afbe105 100644
--- a/lib/classes/JsonApi/Routes/Courseware/BlocksCreate.php
+++ b/lib/classes/JsonApi/Routes/Courseware/BlocksCreate.php
@@ -102,6 +102,7 @@ class BlocksCreate extends JsonApiController
             'block_type'      => $get('data.attributes.block-type'),
             'payload'         => '',
             'visible'         => 1,
+            'commentable'     => 0
         ]);
 
         $payload = $get('data.attributes.payload');
diff --git a/lib/classes/JsonApi/Routes/Courseware/BlocksUpdate.php b/lib/classes/JsonApi/Routes/Courseware/BlocksUpdate.php
index fcec2bb3697..6cf06ff9b7b 100644
--- a/lib/classes/JsonApi/Routes/Courseware/BlocksUpdate.php
+++ b/lib/classes/JsonApi/Routes/Courseware/BlocksUpdate.php
@@ -84,6 +84,10 @@ class BlocksUpdate extends JsonApiController
                 $resource->visible = $get('data.attributes.visible');
             }
 
+            if (is_bool($get('data.attributes.commentable'))) {
+                $resource->commentable = $get('data.attributes.commentable');
+            }
+
             if ($get('data.relationships.container.data.id')) {
                 $resource->container_id = $get('data.relationships.container.data.id');
             }
diff --git a/lib/classes/JsonApi/Routes/Courseware/StructuralElementsCreate.php b/lib/classes/JsonApi/Routes/Courseware/StructuralElementsCreate.php
index 496a8f7001f..c038c45f847 100644
--- a/lib/classes/JsonApi/Routes/Courseware/StructuralElementsCreate.php
+++ b/lib/classes/JsonApi/Routes/Courseware/StructuralElementsCreate.php
@@ -83,7 +83,8 @@ class StructuralElementsCreate extends JsonApiController
             'payload' => self::arrayGet($json, 'data.attributes.payload', ''),
             'read_approval' => $parent->read_approval,
             'write_approval' => $parent->write_approval,
-            'position' => $parent->countChildren()
+            'position' => $parent->countChildren(),
+            'commentable' => 0
         ]);
 
         $struct->store();
diff --git a/lib/classes/JsonApi/Routes/Courseware/StructuralElementsUpdate.php b/lib/classes/JsonApi/Routes/Courseware/StructuralElementsUpdate.php
index 6bf0e79b7af..455aacc3c06 100644
--- a/lib/classes/JsonApi/Routes/Courseware/StructuralElementsUpdate.php
+++ b/lib/classes/JsonApi/Routes/Courseware/StructuralElementsUpdate.php
@@ -140,6 +140,10 @@ class StructuralElementsUpdate extends JsonApiController
                 $resource->withdraw_date = $json['data']['attributes']['withdraw-date'];
             }
 
+            if (isset($json['data']['attributes']['commentable'])) {
+                $resource->commentable = $json['data']['attributes']['commentable'];
+            }
+
             // update parent
             if (self::arrayHas($json, 'data.relationships.parent')) {
                 $parent = $this->getParentFromJson($json);
diff --git a/lib/classes/JsonApi/Routes/Courseware/UnitsCreate.php b/lib/classes/JsonApi/Routes/Courseware/UnitsCreate.php
index a6159e3f416..5909f2aa473 100644
--- a/lib/classes/JsonApi/Routes/Courseware/UnitsCreate.php
+++ b/lib/classes/JsonApi/Routes/Courseware/UnitsCreate.php
@@ -96,7 +96,8 @@ class UnitsCreate extends JsonApiController
             'title' => self::arrayGet($json, 'data.attributes.title', ''),
             'purpose' => self::arrayGet($json, 'data.attributes.purpose', ''),
             'payload' => self::arrayGet($json, 'data.attributes.payload', ''),
-            'position' => 0
+            'position' => 0,
+            'commentable' => 0
         ]);
 
         $unit = \Courseware\Unit::create([
diff --git a/lib/classes/JsonApi/Schemas/Courseware/Block.php b/lib/classes/JsonApi/Schemas/Courseware/Block.php
index 03eb56b8a4f..e608188f86b 100644
--- a/lib/classes/JsonApi/Schemas/Courseware/Block.php
+++ b/lib/classes/JsonApi/Schemas/Courseware/Block.php
@@ -40,6 +40,7 @@ class Block extends SchemaProvider
             'block-type' => (string) $resource->getBlockType(),
             'title' => (string) $resource->type->getTitle(),
             'visible' => (bool) $resource['visible'],
+            'commentable' => (bool) $resource['commentable'],
             'payload' => $resource->type->getPayload(),
             'mkdate' => date('c', $resource['mkdate']),
             'chdate' => date('c', $resource['chdate']),
diff --git a/lib/classes/JsonApi/Schemas/Courseware/StructuralElement.php b/lib/classes/JsonApi/Schemas/Courseware/StructuralElement.php
index 4335e892417..ab1dd0f5047 100644
--- a/lib/classes/JsonApi/Schemas/Courseware/StructuralElement.php
+++ b/lib/classes/JsonApi/Schemas/Courseware/StructuralElement.php
@@ -54,6 +54,7 @@ class StructuralElement extends SchemaProvider
             'can-edit' => $resource->canEdit($user),
             'can-visit' => $resource->canVisit($user),
             'is-link' => (int) $resource['is_link'],
+            'commentable' => (bool) $resource['commentable'],
             'target-id' => (int)  $resource['target_id'],
             'external-relations' => $resource['external_relations']->getIterator(),
             'mkdate' => date('c', $resource['mkdate']),
diff --git a/lib/models/Courseware/Block.php b/lib/models/Courseware/Block.php
index b7948323438..8b3d6f43349 100644
--- a/lib/models/Courseware/Block.php
+++ b/lib/models/Courseware/Block.php
@@ -22,6 +22,7 @@ use User;
  * @property int $position database column
  * @property string|null $block_type database column
  * @property int $visible database column
+ * @property int $commentable database column
  * @property string $payload database column
  * @property int $mkdate database column
  * @property int $chdate database column
@@ -172,6 +173,7 @@ class Block extends \SimpleORMap implements \PrivacyObject
                 'block-type'=> $this->type->getType(),
                 'title'=> $this->type->getTitle(),
                 'visible'=> $this->visible,
+                'commentable' => $this->commentable,
                 'payload'=> $this->type->getPayload(),
                 'mkdate'=> $this->mkdate,
                 'chdate'=> $this->chdate
@@ -204,6 +206,7 @@ class Block extends \SimpleORMap implements \PrivacyObject
             'block_type' => $this->type->getType(),
             'payload' => json_encode($this->type->copyPayload($rangeId)),
             'visible' => 1,
+            'commentable' => 0
         ]);
 
         //update Container payload
@@ -227,6 +230,7 @@ class Block extends \SimpleORMap implements \PrivacyObject
             'block_type' => $data->attributes->{'block-type'},
             'payload' => json_encode($data->attributes->payload),
             'visible' => 1,
+            'commentable' => 0
         ]);
 
         $block->payload = json_encode($block->type->copyPayload($rangeId));
diff --git a/lib/models/Courseware/StructuralElement.php b/lib/models/Courseware/StructuralElement.php
index 34cef8067af..a63c73f7f9f 100644
--- a/lib/models/Courseware/StructuralElement.php
+++ b/lib/models/Courseware/StructuralElement.php
@@ -31,6 +31,7 @@ use User;
  * @property string|null $purpose database column
  * @property \JSONArrayObject $payload database column
  * @property int $public database column
+ * @property int $commentable database column
  * @property int $release_date database column
  * @property int $withdraw_date database column
  * @property \JSONArrayObject $read_approval database column
@@ -758,6 +759,7 @@ class StructuralElement extends \SimpleORMap implements \PrivacyObject
             'owner_id' => $user->id,
             'editor_id' => $user->id,
             'title' => _('neue Seite'),
+            'commentable' => 0
         ]);
 
         $struct->store();
@@ -841,6 +843,7 @@ SQL;
             'purpose' => $purpose ?: $this->purpose,
             'position' => 0,
             'payload' => $this->payload,
+            'commentable' => 0
         ]);
 
         $element->store();
@@ -892,7 +895,8 @@ SQL;
             'image_id' => $image_id,
             'image_type' => $this->image_type,
             'read_approval' => $parent->read_approval,
-            'write_approval' => $parent->write_approval
+            'write_approval' => $parent->write_approval,
+            'commentable' => 0
         ]);
 
         $element->store();
@@ -1032,7 +1036,8 @@ SQL;
             'position' => $parent->countChildren(),
             'payload' => $this->payload,
             'read_approval' => $parent->read_approval,
-            'write_approval' => $parent->write_approval
+            'write_approval' => $parent->write_approval,
+            'commentable' => 0
         ]);
 
         $element->store();
diff --git a/resources/assets/stylesheets/scss/courseware.scss b/resources/assets/stylesheets/scss/courseware.scss
index 6c3d7ea620e..d83a23e0313 100644
--- a/resources/assets/stylesheets/scss/courseware.scss
+++ b/resources/assets/stylesheets/scss/courseware.scss
@@ -20,7 +20,8 @@
 @import './courseware/containers/tabs.scss';
 @import './courseware/blocks/default-block.scss';
 
-@import './courseware/layouts/collapsible.scss';
+
+@import './courseware/layouts/call-to-action.scss';
 @import './courseware/layouts/companion.scss';
 @import './courseware/layouts/import-zip.scss';
 @import './courseware/layouts/input-file.scss';
diff --git a/resources/assets/stylesheets/scss/courseware/blocks/default-block.scss b/resources/assets/stylesheets/scss/courseware/blocks/default-block.scss
index 21e583e50a8..d65927bb06d 100644
--- a/resources/assets/stylesheets/scss/courseware/blocks/default-block.scss
+++ b/resources/assets/stylesheets/scss/courseware/blocks/default-block.scss
@@ -1,6 +1,7 @@
 .cw-default-block {
     display: flex;
-    flex-flow: row;
+    flex-flow: column nowrap;
+
     .cw-default-block-invisible-info {
         img {
             vertical-align: text-bottom;
@@ -95,21 +96,6 @@
     }
 }
 
-
-.cw-container-wrapper-discuss {
-    .cw-container-colspan-full {
-        .cw-content-wrapper {
-            max-width: $max-content-width;
-        }
-    }
-    .cw-container-colspan-half,
-    .cw-container-colspan-half-center {
-        .cw-content-wrapper {
-            max-width: 540px;
-        }
-    }
-}
-
 .cw-block-title {
     padding: 4px;
     background-color: var(--content-color-20);
@@ -132,22 +118,3 @@
         padding-top: 106px;
     }
 }
-
-.cw-call-to-action {
-    border: solid thin var(--content-color-40);
-    border-top: none;
-
-    button {
-        width: 100%;
-        background-color: var(--activity-color-20);
-        border: none;
-        text-align: left;
-        padding: 1em;
-        cursor: pointer;
-
-        img {
-            margin: 0 1em;
-            vertical-align: middle;
-        }
-    }
-}
\ No newline at end of file
diff --git a/resources/assets/stylesheets/scss/courseware/comments.scss b/resources/assets/stylesheets/scss/courseware/comments.scss
index f953e86d2ad..cec94b03233 100644
--- a/resources/assets/stylesheets/scss/courseware/comments.scss
+++ b/resources/assets/stylesheets/scss/courseware/comments.scss
@@ -1,17 +1,11 @@
-.cw-structural-element-feedback,
-.cw-structural-element-comments {
-    padding: 0 1em;
-}
-
 .cw-structural-element-feedback-items,
 .cw-structural-element-comments-items,
 .cw-block-feedback-items,
 .cw-block-comments-items {
     min-height: 1em;
-    max-height: 225px;
+    max-height: 270px;
     overflow-y: auto;
     overflow-x: hidden;
-    margin: -1em -1em 0em 0em;
     padding: 0em 1em 0em 0em;
     scroll-behavior: smooth;
 }
@@ -30,7 +24,7 @@
 .cw-block-feedback-create,
 .cw-block-comment-create {
     border-top: solid thin var(--content-color-40);
-    padding-top: 1em;
+    padding: 8px 1em 0 1em;
     textarea {
         width: calc(100% - 6px);
         resize: none;
@@ -54,4 +48,20 @@
 
 .cw-comments-overview-dialog-comments-context {
     margin: 0 0 1.5em 0;
+}
+
+.cw-block-discussion {
+    .cw-call-to-action:not(:first-child) {
+        border-top: none;
+    }
+}
+
+.cw-default-block-active {
+    .cw-block-discussion {
+        margin: 0 -2px 0 0px;
+        .cw-call-to-action {
+            border-top: none;
+        }
+    }
+    
 }
\ No newline at end of file
diff --git a/resources/assets/stylesheets/scss/courseware/containers/default-container.scss b/resources/assets/stylesheets/scss/courseware/containers/default-container.scss
index 7798d502dd9..a959d86b95c 100644
--- a/resources/assets/stylesheets/scss/courseware/containers/default-container.scss
+++ b/resources/assets/stylesheets/scss/courseware/containers/default-container.scss
@@ -104,24 +104,6 @@ form.cw-container-dialog-edit-form {
     }
 }
 
-.cw-container-wrapper-discuss {
-    flex-direction: column;
-
-    .cw-container-colspan-full {
-        max-width: unset;
-    }
-    .cw-container-colspan-half-center,
-    .cw-container-colspan-half {
-        max-width: 1050px;
-    }
-    .cw-container-colspan-half-center {
-        width: 100%;
-        .cw-container-content {
-            width: 1050px;
-        }
-    }
-}
-
 .cw-radioset {
     display: flex;
     flex-direction: row;
diff --git a/resources/assets/stylesheets/scss/courseware/layouts/call-to-action.scss b/resources/assets/stylesheets/scss/courseware/layouts/call-to-action.scss
new file mode 100644
index 00000000000..2e5e3b9cc32
--- /dev/null
+++ b/resources/assets/stylesheets/scss/courseware/layouts/call-to-action.scss
@@ -0,0 +1,19 @@
+@use '../../../mixins.scss' as *;
+
+.cw-call-to-action {
+    border: solid thin var(--content-color-40);
+
+    .action-button {
+        width: 100%;
+        background-color: var(--activity-color-20);
+        border: none;
+        text-align: left;
+        padding: 1em;
+        cursor: pointer;
+
+        img {
+            margin: 0 1em;
+            vertical-align: middle;
+        }
+    }
+}
\ No newline at end of file
diff --git a/resources/assets/stylesheets/scss/courseware/layouts/talk-bubble.scss b/resources/assets/stylesheets/scss/courseware/layouts/talk-bubble.scss
index 06062b692ee..c3d30c2f84f 100644
--- a/resources/assets/stylesheets/scss/courseware/layouts/talk-bubble.scss
+++ b/resources/assets/stylesheets/scss/courseware/layouts/talk-bubble.scss
@@ -1,58 +1,99 @@
-.cw-talk-bubble {
-    margin: 10px 20px;
-    position: relative;
-    width: 85%;
-    height: auto;
-    background-color: var(--dark-gray-color-10);
-    float: left;
-
-    .cw-talk-bubble-talktext {
-        padding: 1em;
-        text-align: left;
-        line-height: 1.5em;
-
-        .cw-talk-bubble-talktext-time {
-            color: var(--dark-gray-color-80);
-            text-align: right;
-            font-size: 0.8em;
-            margin-bottom: -0.5em;
-        }
-    }
+$color: var(--base-color-20);
+$ownColor: var(--petrol-40);
 
-    &.cw-talk-bubble-own-post {
-        float: right;
-    }
+.cw-talk-bubble-wrapper {
+    display: flex;
+    flex-direction: row;
+    justify-content: start;
 
-    &:after {
-        content: ' ';
-        position: absolute;
-        width: 0;
-        height: 0;
-        top: 0px;
-        bottom: auto;
-        border: 22px solid;
-        border-color: var(--dark-gray-color-10) transparent transparent transparent;
-        left: -20px;
-        right: auto;
+    .cw-talk-bubble-avatar {
+        padding: 8px;
     }
 
-    &.cw-talk-bubble-own-post:after {
-        left: auto;
-        right: -20px;
+    .cw-talk-bubble {
+        margin: 10px 20px;
+        position: relative;
+        max-width: 80%;
+        height: auto;
+        background-color: $color;
+        border-radius: 10px;
+
+        .cw-talk-bubble-content {
+            padding: 8px 1em;
+
+            .cw-talk-bubble-header {
+                margin-bottom: 8px;
+
+                a {
+                    font-weight: 700;
+                }
+            }
+
+            .cw-talk-bubble-talktext {
+                margin-bottom: 4px;
+                text-align: left;
+                line-height: 1.5em;
+
+                .cw-talk-bubble-footer {
+                    float: right;
+                    margin-top: 4px;
+                    padding-bottom: 4px;
+
+                    &:before {
+                        content: " ";
+                        width: 1em;
+                        display: inline-block;
+                    }
+
+                    .cw-talk-bubble-talktext-time {
+                        text-align: right;
+                        font-size: 0.8em;
+                        margin-bottom: -0.5em;
+                    }
+
+                    button {
+                        border: none;
+                        background-color: transparent;
+                        vertical-align: middle;
+                        cursor: pointer;
+                    }
+                }
+
+            }
+        }
+
+        &:after {
+            content: ' ';
+            position: absolute;
+            width: 0;
+            height: 0;
+            top: 0px;
+            bottom: auto;
+            border: 16px solid;
+            border-color: $color transparent transparent transparent;
+            border-radius: 4px;
+            left: -14px;
+            right: auto;
+        }
     }
 
-    .cw-talk-bubble-user {
-        padding: 1em 1em 0 1em;
+    &.cw-talk-bubble-own-post {
+        justify-content: end;
+
+        .cw-talk-bubble {
+            flex-direction: row-reverse;
+            background-color: $ownColor;
 
-        .cw-talk-bubble-avatar {
-            display: inline-block;
+            &:after {
+                border-color: $ownColor transparent transparent transparent;
+                left: auto;
+                right: -14px;
+            }
         }
 
-        span {
-            padding-left: 4px;
-            color: var(--dark-gray-color-45);
-            font-weight: 600;
-            vertical-align: top;
+
+        .cw-talk-bubble-header {
+            flex-direction: row-reverse;
         }
     }
 }
\ No newline at end of file
diff --git a/resources/assets/stylesheets/scss/courseware/structural-element.scss b/resources/assets/stylesheets/scss/courseware/structural-element.scss
index ed1ba385749..4609f8cebf0 100644
--- a/resources/assets/stylesheets/scss/courseware/structural-element.scss
+++ b/resources/assets/stylesheets/scss/courseware/structural-element.scss
@@ -49,8 +49,9 @@
         }
     }
 
-    .cw-structural-element-discussion {
-        max-width: 1606px;
+    .cw-structural-element-feedback-wrapper,
+    .cw-structural-element-comments-wrapper {
+        max-width: calc(1095px - 2px);
         width: 100%;
         margin-bottom: 1em;
     }
@@ -68,10 +69,6 @@
             margin: 0 auto;
             padding: 91px 15px 15px 15px;
         }
-
-        &.cw-container-wrapper-discuss {
-            max-width: 1606px;
-        }
     }
 
     .cw-structural-element-description {
@@ -238,36 +235,4 @@
             }
         }
     }
-}
-
-@media only screen and (max-width: 1820px) {
-    .cw-structural-element .cw-container-wrapper.cw-container-wrapper-discuss {
-        max-width: $max-content-width;
-        .cw-container.cw-container-list.cw-container-item.cw-container-colspan-full {
-            .cw-default-block {
-                flex-flow: column;
-                .cw-discuss-wrapper {
-                    margin-left: 0;
-                    margin-top: 8px;
-                }
-            }
-        }
-    }
-}
-
-@media only screen and (max-width: 1200px) {
-    .cw-structural-element .cw-container-wrapper.cw-container-wrapper-discuss {
-        max-width: $max-content-width;
-        .cw-container.cw-container-list.cw-container-item.cw-container-colspan-half,
-        .cw-container.cw-container-list.cw-container-item.cw-container-colspan-half-center {
-            .cw-default-block {
-                flex-flow: column;
-                .cw-discuss-wrapper {
-                    margin-left: 0;
-                    margin-top: 8px;
-                    max-width: 540px;
-                }
-            }
-        }
-    }
 }
\ No newline at end of file
diff --git a/resources/vue/components/courseware/blocks/CoursewareBlockActions.vue b/resources/vue/components/courseware/blocks/CoursewareBlockActions.vue
index 3ffff182f0c..89beb0a0886 100644
--- a/resources/vue/components/courseware/blocks/CoursewareBlockActions.vue
+++ b/resources/vue/components/courseware/blocks/CoursewareBlockActions.vue
@@ -9,6 +9,9 @@
             @deleteBlock="deleteBlock"
             @removeLock="removeLock"
             @copyToClipboard="copyToClipboard"
+            @deactivateComments="deactivateComments"
+            @activateComments="activateComments"
+            @showFeedback="showFeedback"
         />
     </div>
 </template>
@@ -34,6 +37,7 @@ export default {
         ...mapGetters({
             userId: 'userId',
             userIsTeacher: 'userIsTeacher',
+            getRelatedFeedback: 'courseware-block-feedback/related',
         }),
         blocked() {
             return this.block?.relationships?.['edit-blocker']?.data !== null;
@@ -47,6 +51,16 @@ export default {
         blockedByAnotherUser() {
             return this.blocked && this.userId !== this.blockerId;
         },
+        hasFeedback() {
+            const { id, type } = this.block;
+            const feedback = this.getRelatedFeedback({ parent: { id, type }, relationship: 'feedback' });
+
+            if (feedback === null || feedback.length === 0) {
+                return false;
+            }
+
+            return true;
+        },
         menuItems() {
             let menuItems = [];
             if (this.canEdit) {
@@ -61,6 +75,16 @@ export default {
                             icon: this.block.attributes.visible ? 'visibility-visible' : 'visibility-invisible', // do we change the icons ?
                             emit: 'setVisibility',
                         });
+                        if (this.userIsTeacher) {
+                            menuItems.push({
+                                id: 4,
+                                label: this.block.attributes.commentable
+                                    ? this.$gettext('Kommentare abschalten')
+                                    : this.$gettext('Kommentare aktivieren'),
+                                icon: 'comment2',
+                                emit: this.block.attributes.commentable ? 'deactivateComments' : 'activateComments',
+                            });
+                        }
                     }
                     if (this.blocked && this.blockedByAnotherUser && this.userIsTeacher) {
                         menuItems.push({
@@ -145,6 +169,15 @@ export default {
         },
         copyToClipboard() {
             this.$emit('copyToClipboard');
+        },
+        activateComments() {
+            this.$emit('activateComments')
+        },
+        deactivateComments() {
+            this.$emit('deactivateComments')
+        },
+        showFeedback() {
+            this.$emit('showFeedback');
         }
     },
 };
diff --git a/resources/vue/components/courseware/blocks/CoursewareBlockComments.vue b/resources/vue/components/courseware/blocks/CoursewareBlockComments.vue
index 68dc648b1c0..9b1cc44623c 100644
--- a/resources/vue/components/courseware/blocks/CoursewareBlockComments.vue
+++ b/resources/vue/components/courseware/blocks/CoursewareBlockComments.vue
@@ -7,6 +7,7 @@
                     v-for="comment in comments"
                     :key="comment.id"
                     :payload="buildPayload(comment)"
+                    @delete="deleteComment(comment)"
                 />
             </div>
             <div class="cw-block-comment-create">
@@ -61,7 +62,8 @@ export default {
     methods: {
         ...mapActions({
             createComments: 'courseware-block-comments/create',
-            loadRelatedComments: 'courseware-block-comments/loadRelated'
+            loadRelatedComments: 'courseware-block-comments/loadRelated',
+            deleteBlockComment: 'courseware-block-comments/delete'
         }),
         async loadComments() {
             const parent = {
@@ -96,6 +98,9 @@ export default {
             this.loadComments();
             this.createComment = '';
         },
+        deleteComment(comment) {
+            this.deleteBlockComment({id: comment.id, type: comment.type });
+        },
         buildPayload(comment) {
             const commenter = this.relatedUser({
                 parent: { id: comment.id, type: comment.type },
@@ -109,7 +114,8 @@ export default {
                 chdate: comment.attributes.chdate,
                 mkdate: comment.attributes.mkdate,
                 user_id: commenter.id,
-                user_name: commenter.attributes['formatted-name'],
+                user_formatted_name: commenter.attributes['formatted-name'],
+                username: commenter?.attributes?.username ?? '',
                 user_avatar: commenter.meta.avatar.small,
             };
 
diff --git a/resources/vue/components/courseware/blocks/CoursewareBlockDiscussion.vue b/resources/vue/components/courseware/blocks/CoursewareBlockDiscussion.vue
index 716e6ee0b09..e29e15a1407 100644
--- a/resources/vue/components/courseware/blocks/CoursewareBlockDiscussion.vue
+++ b/resources/vue/components/courseware/blocks/CoursewareBlockDiscussion.vue
@@ -1,31 +1,37 @@
 <template>
     <div class="cw-block-discussion">
-        <courseware-collapsible-box
-            :title="text.comments"
-            :open="hasComments"
+        <courseware-call-to-action-box
+            v-if="commentable"
+            iconShape="chat"
+            :actionTitle="callToActionTitleComments"
+            :titleClosed="text.comments.titleClosed"
+            :titleOpen="text.comments.titleOpen"
+            :foldable="true"
+            :open="false"
         >
-            <courseware-block-comments
-            :block="block"
-            @hasComments="hasComments = true"
-            />
-        </courseware-collapsible-box>
+            <template #content>
+                <courseware-block-comments :block="block" />
+            </template>
+        </courseware-call-to-action-box>
 
-        <courseware-collapsible-box
-            v-if="canEdit || userIsTeacher"
-            :title="text.feedback"
-            :open="hasFeedback"
+        <courseware-call-to-action-box
+            v-if="showFeedback"
+            iconShape="exclaim-circle"
+            :actionTitle="callToActionTitleFeedback"
+            :titleClosed="text.feedback.titleClosed"
+            :titleOpen="text.feedback.titleOpen"
+            :foldable="true"
+            :open="displayFeedback"
         >
-            <courseware-block-feedback
-                :block="block"
-                :canEdit="canEdit"
-                @hasFeedback="hasFeedback = true"
-            />
-        </courseware-collapsible-box>
+            <template #content>
+                <courseware-block-feedback :block="block" :canEdit="canEdit" />
+            </template>
+        </courseware-call-to-action-box>
     </div>
 </template>
 
 <script>
-import CoursewareCollapsibleBox from '../layouts/CoursewareCollapsibleBox.vue';
+import CoursewareCallToActionBox from '../layouts/CoursewareCallToActionBox.vue';
 import CoursewareBlockComments from './CoursewareBlockComments.vue';
 import CoursewareBlockFeedback from './CoursewareBlockFeedback.vue';
 import { mapGetters } from 'vuex';
@@ -33,28 +39,80 @@ import { mapGetters } from 'vuex';
 export default {
     name: 'courseware-block-discussion',
     components: {
-        CoursewareCollapsibleBox,
+        CoursewareCallToActionBox,
         CoursewareBlockComments,
         CoursewareBlockFeedback,
     },
     props: {
         block: Object,
-        canEdit: Boolean
+        canEdit: Boolean,
+        commentable: Boolean,
+        displayFeedback: Boolean
     },
     data() {
         return {
-            hasComments: false,
-            hasFeedback: false,
             text: {
-                comments: this.$gettext('Kommentare'),
-                feedback: this.$gettext('Feedback')
-            }
-        }
+                comments: {
+                    titleClosed: this.$gettext('Kommentare anzeigen'),
+                    titleOpen: this.$gettext('Kommentare ausblenden'),
+                },
+                feedback: {
+                    titleClosed: this.$gettext('Anmerkungen anzeigen'),
+                    titleOpen: this.$gettext('Anmerkungen ausblenden'),
+                },
+            },
+        };
     },
     computed: {
         ...mapGetters({
+            getRelatedFeedback: 'courseware-block-feedback/related',
+            getRelatedComments: 'courseware-block-comments/related',
             userIsTeacher: 'userIsTeacher',
         }),
-    }
-}
+        feedback() {
+            const { id, type } = this.block;
+
+            return this.getRelatedFeedback({ parent: { id, type }, relationship: 'feedback' });
+        },
+        feedbackCounter() {
+            return this.feedback?.length ?? 0;
+        },
+        hasFeedback() {
+            if (this.feedback === null ||  this.feedbackCounter === 0) {
+                return false;
+            }
+
+            return true;
+        },
+        showFeedback() {
+            return ((this.canEdit || this.userIsTeacher) && this.hasFeedback) || this.displayFeedback;
+        },
+        callToActionTitleFeedback() {
+            return this.$gettextInterpolate(
+                this.$ngettext(
+                    '%{length} Anmerkung (Nur für Nutzende mit Schreibrechten sichtbar)',
+                    '%{length} Anmerkungen (Nur für Nutzende mit Schreibrechten sichtbar)',
+                    this.feedbackCounter
+                ),
+            { length: this.feedbackCounter });
+        },
+        comments() {
+            const { id, type } = this.block;
+
+            return this.getRelatedComments({ parent: { id, type }, relationship: 'comments' });
+        },
+        commentsCounter() {
+            return this.comments?.length ?? 0;
+        },
+        callToActionTitleComments() {
+            return this.$gettextInterpolate(
+                this.$ngettext(
+                    '%{length} Kommentar',
+                    '%{length} Kommentare',
+                    this.commentsCounter
+                ),
+            { length: this.commentsCounter });
+        },
+    },
+};
 </script>
diff --git a/resources/vue/components/courseware/blocks/CoursewareBlockFeedback.vue b/resources/vue/components/courseware/blocks/CoursewareBlockFeedback.vue
index dfc95dc4515..c13d500de7f 100644
--- a/resources/vue/components/courseware/blocks/CoursewareBlockFeedback.vue
+++ b/resources/vue/components/courseware/blocks/CoursewareBlockFeedback.vue
@@ -11,16 +11,19 @@
                     v-for="feedback in feedback"
                     :key="feedback.id"
                     :payload="buildPayload(feedback)"
+                    @delete="deleteFeedback(feedback)"
                 />
             </div>
             <courseware-companion-box
                 v-if="!userIsTeacher && feedback.length === 0"
-                :msgCompanion="$gettext('Es wurde noch kein Feedback abgegeben.')"
+                :msgCompanion="$gettext('Es wurde noch keine Anmerkungen abgegeben.')"
                 mood="pointing"
             />
             <div v-if="userIsTeacher" class="cw-block-feedback-create">
                 <textarea v-model="feedbackText" :placeholder="placeHolder" spellcheck="true"></textarea>
-                <button class="button" @click="postFeedback">{{ $gettext('Senden') }}</button>
+                <button class="button" @click="postFeedback">
+                    {{ $gettext('Senden') }}
+                </button>
             </div>
         </div>
     </section>
@@ -31,7 +34,6 @@ import CoursewareCompanionBox from '../layouts/CoursewareCompanionBox.vue';
 import CoursewareTalkBubble from '../layouts/CoursewareTalkBubble.vue';
 import { mapActions, mapGetters } from 'vuex';
 
-
 export default {
     name: 'courseware-block-feedback',
     components: {
@@ -45,8 +47,8 @@ export default {
     data() {
         return {
             feedbackText: '',
-            placeHolder: this.$gettext('Schreiben Sie ein Feedback...'),
-            srMessage: ''
+            placeHolder: this.$gettext('Schreiben Sie eine Anmerkung...'),
+            srMessage: '',
         };
     },
     computed: {
@@ -67,12 +69,13 @@ export default {
             }
 
             return false;
-        }
+        },
     },
     methods: {
         ...mapActions({
             createFeedback: 'courseware-block-feedback/create',
             loadRelatedFeedback: 'courseware-block-feedback/loadRelated',
+            deleteBlockFeedback: 'courseware-block-feedback/delete',
         }),
         buildPayload(feedback) {
             const { id, type } = feedback;
@@ -83,7 +86,8 @@ export default {
                 content: feedback.attributes.feedback,
                 chdate: feedback.attributes.chdate,
                 mkdate: feedback.attributes.mkdate,
-                user_name: user?.attributes?.['formatted-name'] ?? '',
+                user_formatted_name: user?.attributes?.['formatted-name'] ?? '',
+                username: user?.attributes?.username ?? '',
                 user_avatar: user?.meta?.avatar.small,
             };
         },
@@ -119,23 +123,16 @@ export default {
             this.feedbackText = '';
             this.loadFeedback();
         },
+        deleteFeedback(feedback) {
+            this.deleteBlockFeedback({ id: feedback.id, type: feedback.type });
+        },
         updateSrMessage(message) {
             this.srMessage = '';
             this.srMessage = message;
-        }
-    },
-    async mounted() {
-        await this.loadFeedback(this.block.id);
+        },
     },
     updated() {
         this.$refs.feedbacks.scrollTop = this.$refs.feedbacks.scrollHeight;
     },
-    watch: {
-        feedback() {
-            if (this.feedback && this.feedback.length > 0) {
-                this.$emit('hasFeedback');
-            }
-        }
-    }
 };
 </script>
diff --git a/resources/vue/components/courseware/blocks/CoursewareDefaultBlock.vue b/resources/vue/components/courseware/blocks/CoursewareDefaultBlock.vue
index d11a53834dc..2fabdda16f5 100644
--- a/resources/vue/components/courseware/blocks/CoursewareDefaultBlock.vue
+++ b/resources/vue/components/courseware/blocks/CoursewareDefaultBlock.vue
@@ -1,5 +1,5 @@
 <template>
-    <div v-if="block.attributes.visible || canEdit" class="cw-default-block">
+    <div v-if="block.attributes.visible || canEdit" class="cw-default-block" :class="[showEditMode ? 'cw-default-block-active' : '']">
         <div class="cw-content-wrapper" :class="[showEditMode ? 'cw-content-wrapper-active' : '']">
             <header v-if="showEditMode" class="cw-block-header">
                 <a href="#" class="cw-block-header-toggle" :aria-expanded="isOpen" @click.prevent="isOpen = !isOpen">
@@ -24,6 +24,9 @@
                     @deleteBlock="displayDeleteDialog()"
                     @removeLock="displayRemoveLockDialog()"
                     @copyToClipboard="copyToClipboard"
+                    @deactivateComments="deactivateComments"
+                    @activateComments="activateComments"
+                    @showFeedback="showFeedback"
                 />
             </header>
             <div v-show="isOpen">
@@ -55,12 +58,12 @@
                 </div>
             </div>
         </div>
-        <div v-if="discussView" class="cw-discuss-wrapper">
-            <courseware-block-discussion
-                :block="block"
-                :canEdit="canEdit"
-            />
-        </div>
+        <courseware-block-discussion
+            :block="block"
+            :canEdit="canEdit"
+            :commentable="commentable"
+            :displayFeedback="displayFeedback"
+        />
         <studip-dialog
             v-if="showDeleteDialog"
             :title="textDeleteTitle"
@@ -138,6 +141,7 @@ export default {
             textRemoveLockTitle: this.$gettext('Sperre aufheben'),
             textRemoveLockAlert: this.$gettext('Möchten Sie die Sperre dieses Blocks wirklich aufheben?'),
             isOpen: true,
+            displayFeedback: false,
         };
     },
     computed: {
@@ -148,7 +152,7 @@ export default {
             userId: 'userId',
             userById: 'users/byId',
             viewMode: 'viewMode',
-            currentElementisLink: 'currentElementisLink'
+            currentElementisLink: 'currentElementisLink',
         }),
         showEditMode() {
             let show = (this.viewMode === 'edit' || this.blockedByThisUser) && !this.currentElementisLink;
@@ -157,9 +161,6 @@ export default {
             }
             return show;
         },
-        discussView() {
-            return this.viewMode === 'discuss';
-        },
         blocked() {
             return this.block?.relationships?.['edit-blocker']?.data !== null;
         },
@@ -189,7 +190,10 @@ export default {
         },
         public() {
             return this.context.type === 'public';
-        }
+        },
+        commentable() {
+            return this.block?.attributes?.commentable ?? false;
+        },
     },
     mounted() {
         if (this.blocked) {
@@ -200,6 +204,9 @@ export default {
         if (!this.public && this.userProgress && this.userProgress.attributes.grade === 0 && this.defaultGrade) {
             this.userProgress = 1;
         }
+        if (this.canEdit) {
+            this.loadFeedback(this.block.id);
+        }
     },
     methods: {
         ...mapActions({
@@ -212,7 +219,10 @@ export default {
             loadContainer: 'loadContainer',
             loadBlock: 'courseware-blocks/loadById',
             updateContainer: 'updateContainer',
-            createClipboard: 'courseware-clipboards/create'
+            createClipboard: 'courseware-clipboards/create',
+            activateBlockComments: 'activateBlockComments',
+            deactivateBlockComments: 'deactivateBlockComments',
+            loadRelatedFeedback: 'courseware-block-feedback/loadRelated',
         }),
         async displayFeature(element) {
             if (this.showEdit && element === 'Edit') {
@@ -377,7 +387,30 @@ export default {
 
             await this.createClipboard(clipboard, { root: true });
             this.companionSuccess({ info: this.$gettext('Block wurde in Merkliste abgelegt.') });
-        }
+        },
+        activateComments() {
+            this.activateBlockComments({ block: this.block });
+        },
+        deactivateComments() {
+            this.deactivateBlockComments({ block: this.block });
+        },
+        showFeedback() {
+            console.log('displayFeedback');
+            this.displayFeedback = true;
+        },
+        async loadFeedback() {
+            const parent = {
+                type: this.block.type,
+                id: this.block.id,
+            };
+            await this.loadRelatedFeedback({
+                parent,
+                relationship: 'feedback',
+                options: {
+                    include: 'user',
+                },
+            });
+        },
 
     },
     watch: {
diff --git a/resources/vue/components/courseware/layouts/CoursewareCallToActionBox.vue b/resources/vue/components/courseware/layouts/CoursewareCallToActionBox.vue
new file mode 100644
index 00000000000..601a339e1a5
--- /dev/null
+++ b/resources/vue/components/courseware/layouts/CoursewareCallToActionBox.vue
@@ -0,0 +1,70 @@
+<template>
+    <div class="cw-call-to-action">
+        <button class="action-button" :title="unfold ? titleOpen : titleClosed" @click="buttonAction">
+            <studip-icon :shape="unfold ? 'arr_1down' : iconShape" :size="24"/>
+            {{ actionTitle }}
+        </button>
+        <div v-if="unfold" class="cw-call-to-action-content">
+            <slot name="content"></slot>
+        </div>
+    </div>
+
+</template>
+
+<script>
+import StudipIcon from '../../StudipIcon.vue';
+
+export default {
+    name: 'courseware-call-to-action-box',
+    components: {
+        StudipIcon
+    },
+    props: {
+        iconShape: {
+            type: String,
+            default: 'arr_1right'
+        },
+        titleClosed: {
+            type: String,
+            required: true
+        },
+        titleOpen: {
+            type: String,
+            required: true
+        },
+        actionTitle: {
+            type: String,
+            required: true
+        },
+        foldable: {
+            type: Boolean,
+            default: false
+        },
+        open: {
+            type: Boolean,
+            default: true
+        }
+    },
+    data() {
+        return {
+            unfold: true
+        }
+    },
+    methods: {
+        buttonAction() {
+            this.$emit('click');
+            if (this.foldable) {
+                this.unfold = !this.unfold;
+            }
+        }
+    },
+    mounted() {
+        this.unfold = this.open;
+    },
+    watch: {
+        open(newState) {
+            this.unfold = newState;
+        }
+    }
+}
+</script>
\ No newline at end of file
diff --git a/resources/vue/components/courseware/layouts/CoursewareTalkBubble.vue b/resources/vue/components/courseware/layouts/CoursewareTalkBubble.vue
index cd76aaa1a9b..007a72ef6e3 100644
--- a/resources/vue/components/courseware/layouts/CoursewareTalkBubble.vue
+++ b/resources/vue/components/courseware/layouts/CoursewareTalkBubble.vue
@@ -1,20 +1,35 @@
 <template>
-    <div :class="{ 'cw-talk-bubble-own-post': payload.own }" class="cw-talk-bubble">
-        <div class="cw-talk-bubble-user" v-if="!payload.own">
-            <div class="cw-talk-bubble-avatar">
-                <img :src="payload.user_avatar" />
-            </div>
-            <span>{{ payload.user_name }}</span>
+    <div :class="{ 'cw-talk-bubble-own-post': payload.own }" class="cw-talk-bubble-wrapper">
+        <div v-if="!payload.own" class="cw-talk-bubble-avatar">
+            <img :src="payload.user_avatar" />
         </div>
-        <div class="cw-talk-bubble-talktext">
-            <p>{{ payload.content }}</p>
-            <p class="cw-talk-bubble-talktext-time"><iso-date :date="payload.chdate" /></p>
+        <div class="cw-talk-bubble">
+            <div class="cw-talk-bubble-content">
+                <header v-if="!payload.own" class="cw-talk-bubble-header">
+                    <a :href="userProfileUrl">{{ payload.user_formatted_name }}</a>
+                </header>
+                <div class="cw-talk-bubble-talktext">
+                    <span>{{ payload.content }}</span>
+                    <div class="cw-talk-bubble-footer">
+                        <span class="cw-talk-bubble-talktext-time"><iso-date :date="payload.chdate" /></span>
+                        <button v-if="userIsTeacher || payload.own" :title="$gettext('Löschen')"
+                            @click="showDeleteDialog = true">
+                            <studip-icon shape="trash" />
+                        </button>
+                    </div>
+                </div>
+            </div>
         </div>
+        <studip-dialog v-if="showDeleteDialog" :title="$gettext('Eintrag löschen')"
+            :question="$gettext('Möchten Sie diesen Eintrag löschen?')" height="180" width="360" @confirm="deletePost"
+            @close="closeDeleteDialog">
+        </studip-dialog>
     </div>
 </template>
 
 <script>
 import IsoDate from './IsoDate.vue';
+import { mapGetters } from 'vuex';
 
 export default {
     name: 'courseware-talk-bubble',
@@ -22,5 +37,28 @@ export default {
     props: {
         payload: Object,
     },
+    data() {
+        return {
+            showDeleteDialog: false
+        }
+    },
+    computed: {
+        ...mapGetters({
+            userIsTeacher: 'userIsTeacher'
+        }),
+        userProfileUrl() {
+            const username = this.payload.username;
+            return STUDIP.URLHelper.getURL('dispatch.php/profile', { username });
+        }
+    },
+    methods: {
+        closeDeleteDialog() {
+            this.showDeleteDialog = false;
+        },
+        deletePost() {
+            this.closeDeleteDialog();
+            this.$emit('delete');
+        }
+    }
 };
-</script>
\ No newline at end of file
+</script>
diff --git a/resources/vue/components/courseware/structural-element/CoursewareStructuralElement.vue b/resources/vue/components/courseware/structural-element/CoursewareStructuralElement.vue
index dcb396984dd..4f70f4894fe 100644
--- a/resources/vue/components/courseware/structural-element/CoursewareStructuralElement.vue
+++ b/resources/vue/components/courseware/structural-element/CoursewareStructuralElement.vue
@@ -77,11 +77,30 @@
                                 @linkElement="menuAction('linkElement')"
                                 @removeLock="menuAction('removeLock')"
                                 @activateFullscreen="menuAction('activateFullscreen')"
+                                @activateComments="menuAction('activateComments')"
+                                @deactivateComments="menuAction('deactivateComments')"
+                                @showFeedback="menuAction('showFeedback')"
                             />
                         </template>
                     </courseware-ribbon>
                     <div class="cw-page-wrapper">
                         <div class="cw-page-content">
+                            <courseware-call-to-action-box
+                                v-if="canEdit && (hasFeedback || displayFeedback)"
+                                class="cw-structural-element-feedback-wrapper"
+                                iconShape="exclaim-circle"
+                                :actionTitle="callToActionTitleFeedback"
+                                :titleClosed="$gettext('Anmerkungen anzeigen')"
+                                :titleOpen="$gettext('Anmerkungen ausblenden')"
+                                :foldable="true"
+                            >
+                                <template #content>
+                                    <courseware-structural-element-feedback
+                                        :structuralElement="structuralElement"
+                                        :canEdit="canEdit"
+                                    />
+                                </template>
+                            </courseware-call-to-action-box>
                             <div v-if="structuralElementLoaded && !isLink" class="cw-companion-box-wrapper">
                                 <courseware-companion-box
                                     v-if="!canVisit"
@@ -113,14 +132,8 @@
                                 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"
@@ -138,14 +151,8 @@
                                 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 })"
@@ -211,6 +218,22 @@
                         </div>
                         <courseware-toolbar v-if="canVisit && canEdit && editView && !isLink" /> 
                     </div>
+                    <courseware-call-to-action-box
+                        v-if="commentable"
+                        class="cw-structural-element-comments-wrapper"
+                        iconShape="chat"
+                        :actionTitle="callToActionTitleComments"
+                        :titleClosed="$gettext('Kommentare anzeigen')"
+                        :titleOpen="$gettext('Kommentare ausblenden')"
+                        :foldable="true"
+                        :open="false"
+                    >
+                        <template #content>
+                            <courseware-structural-element-comments
+                                :structuralElement="structuralElement"
+                            />
+                        </template>
+                    </courseware-call-to-action-box>
                 </div>
                 <studip-dialog
                     v-if="showEditDialog"
@@ -593,6 +616,8 @@ import StructuralElementComponents from './structural-element-components.js';
 import CoursewarePluginComponents from '../plugin-components.js';
 import CoursewareRootContent from './CoursewareRootContent.vue';
 
+import CoursewareStructuralElementComments from './CoursewareStructuralElementComments.vue';
+import CoursewareStructuralElementFeedback from './CoursewareStructuralElementFeedback.vue';
 import CoursewareStructuralElementDialogAdd from './CoursewareStructuralElementDialogAdd.vue';
 import CoursewareStructuralElementDialogAddChooser from './CoursewareStructuralElementDialogAddChooser.vue';
 import CoursewareStructuralElementDialogCopy from './CoursewareStructuralElementDialogCopy.vue';
@@ -609,6 +634,7 @@ import CoursewareExport from '@/vue/mixins/courseware/export.js';
 import CoursewareOerMessage from '@/vue/mixins/courseware/oermessage.js';
 import colorMixin from '@/vue/mixins/courseware/colors.js';
 import wizardMixin from '@/vue/mixins/courseware/wizard.js';
+import CoursewareCallToActionBox from '../layouts/CoursewareCallToActionBox.vue';
 import CoursewareDateInput from '../layouts/CoursewareDateInput.vue';
 import StockImageSelector from '../../stock-images/SelectorDialog.vue';
 import StudipDialog from '../../StudipDialog.vue';
@@ -620,6 +646,8 @@ export default {
     name: 'courseware-structural-element',
     components: Object.assign(StructuralElementComponents, {
         CoursewareRootContent,
+        CoursewareStructuralElementComments,
+        CoursewareStructuralElementFeedback,
         CoursewareStructuralElementDialogAdd,
         CoursewareStructuralElementDialogAddChooser,
         CoursewareStructuralElementDialogCopy,
@@ -632,6 +660,7 @@ export default {
         CoursewareStructuralElementPermissions,
         CoursewareContentPermissions,
         CoursewareWelcomeScreen,
+        CoursewareCallToActionBox,
         CoursewareDateInput,
         StockImageSelector,
         StudipDialog,
@@ -699,6 +728,7 @@ export default {
             uploadImageURL: null,
             showStockImageSelector: false,
             selectedStockImage: null,
+            displayFeedback: false,
         };
     },
 
@@ -710,6 +740,8 @@ export default {
             containerById: 'courseware-containers/byId',
             relatedContainers: 'courseware-containers/related',
             relatedStructuralElements: 'courseware-structural-elements/related',
+            getRelatedFeedback: 'courseware-structural-element-feedback/related',
+            getRelatedComments: 'courseware-structural-element-comments/related',
             relatedTaskGroups: 'courseware-task-groups/related',
             relatedUsers: 'users/related',
             structuralElementById: 'courseware-structural-elements/byId',
@@ -1037,6 +1069,24 @@ export default {
                         icon: 'settings',
                         emit: 'editCurrentElement',
                     });
+                    if (this.userIsTeacher) {
+                        menu.push({
+                            id: 2,
+                            label: this.commentable
+                                    ? this.$gettext('Kommentare abschalten')
+                                    : this.$gettext('Kommentare aktivieren'),
+                                icon: 'comment2',
+                                emit: this.commentable ? 'deactivateComments' : 'activateComments',
+                        });
+                        if (!this.hasFeedback && !this.displayFeedback) {
+                            menu.push({
+                                id: 3,
+                                label: this.$gettext('Anmerkungen aktivieren'),
+                                icon: 'exclaim-circle',
+                                emit: 'showFeedback'
+                            });
+                        }
+                    }
                 }
                 if (this.blockedByAnotherUser && this.userIsTeacher) {
                     menu.push({
@@ -1085,9 +1135,6 @@ export default {
         blockingUserName() {
             return this.blockingUser ? this.blockingUser.attributes['formatted-name'] : '';
         },
-        discussView() {
-            return this.viewMode === 'discuss';
-        },
         editView() {
             return this.viewMode === 'edit';
         },
@@ -1211,7 +1258,57 @@ export default {
                 'dispatch.php/course/courseware/courseware/' + this.context.unit,
                 {cid: this.context.id}
             );
-        }
+        },
+        commentable() {
+            return this.currentElement?.attributes?.commentable ?? false;
+        },
+        feedback() {
+            const parent = {
+                type: this.currentElement.type,
+                id: this.currentElement.id,
+            };
+
+            return this.getRelatedFeedback({ parent, relationship: 'feedback' });
+        },
+        feedbackCounter() {
+            return this.feedback?.length ?? 0;
+        },
+        hasFeedback() {
+            if (this.feedback === null || this.feedbackCounter === 0) {
+                return false;
+            }
+
+            return true;
+        },
+        callToActionTitleFeedback() {
+            return this.$gettextInterpolate(
+                this.$ngettext(
+                    '%{length} Anmerkung zur Seite (Nur für Nutzende mit Schreibrechten sichtbar)',
+                    '%{length} Anmerkungen zur Seite (Nur für Nutzende mit Schreibrechten sichtbar)',
+                    this.feedbackCounter
+                ),
+            { length: this.feedbackCounter });
+        },
+        comments() {
+            const parent = {
+                type: this.currentElement.type,
+                id: this.currentElement.id,
+            };
+
+            return this.getRelatedComments({ parent, relationship: 'comments' });
+        },
+        commentsCounter() {
+            return this.comments?.length ?? 0;
+        },
+        callToActionTitleComments() {
+            return this.$gettextInterpolate(
+                this.$ngettext(
+                    '%{length} Kommentar zur Seite',
+                    '%{length} Kommentare zur Seite',
+                    this.commentsCounter
+                ),
+            { length: this.commentsCounter });
+        },
     },
 
     methods: {
@@ -1246,7 +1343,10 @@ export default {
             loadStructuralElement: 'loadStructuralElement',
             createLink: 'createLink',
             setCurrentElementId: 'coursewareCurrentElement',
-            loadProgresses: 'loadProgresses'
+            loadProgresses: 'loadProgresses',
+            activateStructuralElementComments: 'activateStructuralElementComments',
+            deactivateStructuralElementComments: 'deactivateStructuralElementComments',
+            loadRelatedFeedback: 'courseware-structural-element-feedback/loadRelated',
         }),
 
         initCurrent() {
@@ -1254,6 +1354,7 @@ export default {
             this.uploadFileError = '';
             this.deletingPreviewImage = false;
             this.uploadImageURL = null;
+            this.loadFeedback();
         },
         async menuAction(action) {
             switch (action) {
@@ -1315,6 +1416,15 @@ export default {
                 case 'activateFullscreen':
                     STUDIP.Fullscreen.activate();
                     break;
+                case 'activateComments':
+                    this.activateStructuralElementComments({ element: this.currentElement });
+                    break;
+                case 'deactivateComments':
+                    this.deactivateStructuralElementComments({ element: this.currentElement });
+                    break;
+                case 'showFeedback':
+                    this.displayFeedback = true;
+                    break;
             }
         },
         async closeEditDialog() {
@@ -1558,6 +1668,19 @@ export default {
                 ref.initCurrentData();
             }
         },
+        async loadFeedback() {
+            const parent = {
+                type: this.currentElement.type,
+                id: this.currentElement.id,
+            };
+            await this.loadRelatedFeedback({
+                parent,
+                relationship: 'feedback',
+                options: {
+                    include: 'user',
+                },
+            });
+        },
         keyHandler(e, containerId) {
             switch (e.keyCode) {
                 case 27: // esc
diff --git a/resources/vue/components/courseware/structural-element/CoursewareStructuralElementComments.vue b/resources/vue/components/courseware/structural-element/CoursewareStructuralElementComments.vue
index 42c7f5f6707..e78377f4f20 100644
--- a/resources/vue/components/courseware/structural-element/CoursewareStructuralElementComments.vue
+++ b/resources/vue/components/courseware/structural-element/CoursewareStructuralElementComments.vue
@@ -9,6 +9,7 @@
                 v-for="comment in comments"
                 :key="comment.id"
                 :payload="buildPayload(comment)"
+                @delete="deleteComment(comment)"
             />
         </div>
         <div class="cw-structural-element-comment-create">
@@ -62,7 +63,8 @@ export default {
     methods: {
         ...mapActions({
             createComments: 'courseware-structural-element-comments/create',
-            loadRelatedComments: 'courseware-structural-element-comments/loadRelated'
+            loadRelatedComments: 'courseware-structural-element-comments/loadRelated',
+            deleteElementComment: 'courseware-structural-element-comments/delete'
         }),
         async loadComments() {
             const parent = {
@@ -110,12 +112,16 @@ export default {
                 chdate: comment.attributes.chdate,
                 mkdate: comment.attributes.mkdate,
                 user_id: commenter.id,
-                user_name: commenter.attributes['formatted-name'],
+                user_formatted_name: commenter.attributes['formatted-name'],
+                username: commenter?.attributes?.username ?? '',
                 user_avatar: commenter.meta.avatar.small,
             };
 
             return payload;
         },
+        deleteComment(comment) {
+            this.deleteElementComment({ id: comment.id, type: comment.type });
+        },
         updateSrMessage(message) {
             this.srMessage = '';
             this.srMessage = message;
diff --git a/resources/vue/components/courseware/structural-element/CoursewareStructuralElementFeedback.vue b/resources/vue/components/courseware/structural-element/CoursewareStructuralElementFeedback.vue
index fd950bc1625..acecf630448 100644
--- a/resources/vue/components/courseware/structural-element/CoursewareStructuralElementFeedback.vue
+++ b/resources/vue/components/courseware/structural-element/CoursewareStructuralElementFeedback.vue
@@ -10,16 +10,19 @@
                 v-for="feedback in feedback"
                 :key="feedback.id"
                 :payload="buildPayload(feedback)"
+                @delete="deleteFeedback(feedback)"
             />
         </div>
         <courseware-companion-box
-                v-if="!userIsTeacher && feedback.length === 0"
-                :msgCompanion="$gettext('Es wurde noch kein Feedback abgegeben.')"
-                mood="pointing"
-            />
+            v-if="!userIsTeacher && feedback.length === 0"
+            :msgCompanion="$gettext('Es wurde noch keine Anmerkungen abgegeben.')"
+            mood="pointing"
+        />
         <div v-if="userIsTeacher" class="cw-structural-element-feedback-create">
             <textarea v-model="feedbackText" :placeholder="placeHolder" spellcheck="true"></textarea>
-            <button class="button" @click="postFeedback"><translate>Senden</translate></button>
+            <button class="button" @click="postFeedback">
+                {{ $gettext('Senden') }}
+            </button>
         </div>
     </section>
 </template>
@@ -42,8 +45,8 @@ export default {
     data() {
         return {
             feedbackText: '',
-            placeHolder: this.$gettext('Schreiben Sie ein Feedback...'),
-            srMessage: ''
+            placeHolder: this.$gettext('Schreiben Sie eine Anmerkung...'),
+            srMessage: '',
         };
     },
     computed: {
@@ -67,12 +70,13 @@ export default {
             }
 
             return false;
-        }
+        },
     },
     methods: {
         ...mapActions({
             createFeedback: 'courseware-structural-element-feedback/create',
             loadRelatedFeedback: 'courseware-structural-element-feedback/loadRelated',
+            deleteElementFeedback: 'courseware-structural-element-feedback/delete',
         }),
         buildPayload(feedback) {
             const { id, type } = feedback;
@@ -83,7 +87,8 @@ export default {
                 content: feedback.attributes.feedback,
                 chdate: feedback.attributes.chdate,
                 mkdate: feedback.attributes.mkdate,
-                user_name: user?.attributes?.['formatted-name'] ?? '',
+                user_formatted_name: user?.attributes?.['formatted-name'] ?? '',
+                username: user?.attributes?.username ?? '',
                 user_avatar: user?.meta?.avatar.small,
             };
         },
@@ -101,7 +106,7 @@ export default {
             });
         },
         async postFeedback() {
-            this.updateSrMessage(this.$gettext('Feedback gesendet'));
+            this.updateSrMessage(this.$gettext('Anmerkung gesendet'));
             const data = {
                 attributes: {
                     feedback: this.feedbackText,
@@ -110,32 +115,25 @@ export default {
                     'structural-element': {
                         data: {
                             id: this.structuralElement.id,
-                            type: this.structuralElement.type
-                        }
-                    }
+                            type: this.structuralElement.type,
+                        },
+                    },
                 },
             };
-            await this.createFeedback( data, { root: true });
+            await this.createFeedback(data, { root: true });
             this.feedbackText = '';
             this.loadFeedback();
         },
+        deleteFeedback(feedback) {
+            this.deleteElementFeedback({ id: feedback.id, type: feedback.type });
+        },
         updateSrMessage(message) {
             this.srMessage = '';
             this.srMessage = message;
-        }
-    },
-    async mounted() {
-        await this.loadFeedback();
+        },
     },
     updated() {
         this.$refs.feedbacks.scrollTop = this.$refs.feedbacks.scrollHeight;
     },
-    watch: {
-        feedback() {
-            if (this.feedback && this.feedback.length > 0) {
-                this.$emit('hasFeedback');
-            }
-        }
-    }
-}
-</script>
\ No newline at end of file
+};
+</script>
diff --git a/resources/vue/components/courseware/widgets/CoursewareViewWidget.vue b/resources/vue/components/courseware/widgets/CoursewareViewWidget.vue
index d8a8563ce1a..6f9d787f6bc 100644
--- a/resources/vue/components/courseware/widgets/CoursewareViewWidget.vue
+++ b/resources/vue/components/courseware/widgets/CoursewareViewWidget.vue
@@ -15,14 +15,6 @@
                         <translate>Bearbeiten</translate>
                     </button>
                 </li>
-                <li
-                    v-if="context.type === 'courses' && canVisit"
-                    :class="{ active: discussView }"
-                >
-                    <button @click="setDiscussView">
-                        <translate>Kommentieren</translate>
-                    </button>
-                </li>
             </ul>
         </template>
     </sidebar-widget>
@@ -50,9 +42,6 @@ export default {
         editView() {
             return this.viewMode === 'edit';
         },
-        discussView() {
-            return this.viewMode === 'discuss';
-        },
         canEdit() {
             if (!this.structuralElement) {
                 return false;
@@ -77,9 +66,6 @@ export default {
         setEditView() {
             this.coursewareViewMode('edit');
         },
-        setDiscussView() {
-            this.coursewareViewMode('discuss');
-        },
     },
 };
 </script>
diff --git a/resources/vue/store/courseware/courseware.module.js b/resources/vue/store/courseware/courseware.module.js
index 1f85d56b0e7..db13cf16de8 100644
--- a/resources/vue/store/courseware/courseware.module.js
+++ b/resources/vue/store/courseware/courseware.module.js
@@ -532,6 +532,32 @@ export const actions = {
 
     },
 
+    async activateStructuralElementComments({ dispatch }, { element }) {
+
+        element.attributes.commentable = true;
+
+        const updatedElement =  await dispatch('setStructuralElementComments', { element: element });
+        
+        return updatedElement;
+
+    },
+    async deactivateStructuralElementComments({ dispatch }, { element }) {
+
+        element.attributes.commentable = false;
+
+        const updatedElement =  await dispatch('setStructuralElementComments', { element: element });
+        
+        return updatedElement;
+    },
+
+    async setStructuralElementComments({ dispatch }, { element }) {
+        await dispatch('lockObject', { id: element.id, type: 'courseware-structural-elements' });
+        const updatedElement =  await dispatch('courseware-structural-elements/update', element, { root: true });
+        await dispatch('unlockObject', { id: element.id, type: 'courseware-structural-elements' });
+
+        return updatedElement;
+    },
+
     async createBlockInContainer({ dispatch }, { container, blockType }) {
         const block = {
             attributes: {
@@ -609,6 +635,32 @@ export const actions = {
         return dispatch('loadContainer', containerId);
     },
 
+    async activateBlockComments({ dispatch }, { block }) {
+
+        block.attributes.commentable = true;
+
+        const updatedBlock =  await dispatch('setBlockComments', { block: block });
+        
+        return updatedBlock;
+
+    },
+    async deactivateBlockComments({ dispatch }, { block }) {
+
+        block.attributes.commentable = false;
+
+        const updatedBlock =  await dispatch('setBlockComments', { block: block });
+        
+        return updatedBlock;
+    },
+
+    async setBlockComments({ dispatch }, { block }) {
+        await dispatch('lockObject', { id: block.id, type: 'courseware-blocks' });
+        const updatedBlock =  await dispatch('courseware-blocks/update', block, { root: true });
+        await dispatch('unlockObject', { id: block.id, type: 'courseware-blocks' });
+
+        return updatedBlock;
+    },
+
     async storeCoursewareSettings({ dispatch, getters },
                                   { permission, progression, certificateSettings, reminderSettings,
                                       resetProgressSettings }) {
-- 
GitLab