From 9bb68bf5d075ff0e127b9fe5309889f366ccf127 Mon Sep 17 00:00:00 2001 From: Murtaza Sultani Date: Fri, 5 Sep 2025 15:47:50 +0200 Subject: Resolve "Forum: Discussion-Type Index auf Vue umsetzen" Closes #5782 Merge request studip/studip!4406 --- app/controllers/course/forum/discussion_types.php | 24 ++- app/controllers/course/forum/discussions.php | 2 +- app/controllers/course/forum/search.php | 2 +- app/views/course/forum/discussion_types/index.php | 79 ---------- lib/classes/JsonApi/RouteMap.php | 2 + .../JsonApi/Routes/Forum/DiscussionTypeDelete.php | 29 ++++ .../JsonApi/Routes/Forum/DiscussionTypeIndex.php | 6 +- .../JsonApi/Routes/Forum/DiscussionTypeShow.php | 4 - .../JsonApi/Routes/Forum/DiscussionTypeStore.php | 46 ++++++ .../JsonApi/Schemas/Forum/DiscussionType.php | 32 ++-- lib/models/Forum/DiscussionType.php | 16 +- resources/assets/stylesheets/scss/buttons.scss | 2 +- resources/assets/stylesheets/scss/forum.scss | 9 +- resources/vue/apps/forum/discussions/Show.vue | 6 +- .../vue/apps/forum/discussions_types/Edit.vue | 27 ++-- .../vue/apps/forum/discussions_types/Index.vue | 163 +++++++++++++++++++++ resources/vue/components/Dropdown.vue | 2 +- resources/vue/components/UserAvatar.vue | 4 +- .../vue/components/forum/SubscriptionDropdown.vue | 4 + resources/vue/components/forum/helpers/urls.js | 4 + 20 files changed, 316 insertions(+), 147 deletions(-) delete mode 100644 app/views/course/forum/discussion_types/index.php create mode 100644 lib/classes/JsonApi/Routes/Forum/DiscussionTypeDelete.php create mode 100644 lib/classes/JsonApi/Routes/Forum/DiscussionTypeStore.php create mode 100644 resources/vue/apps/forum/discussions_types/Index.vue diff --git a/app/controllers/course/forum/discussion_types.php b/app/controllers/course/forum/discussion_types.php index 68005a5..71a4766 100644 --- a/app/controllers/course/forum/discussion_types.php +++ b/app/controllers/course/forum/discussion_types.php @@ -19,14 +19,16 @@ class Course_Forum_DiscussionTypesController extends AuthenticatedController _('Neuen Diskussionstyp anlegen'), $this->url_for('course/forum/discussion_types/edit'), Icon::create('add', Icon::ROLE_CLICKABLE, ['title' => _('Neuen Diskussionstyp anlegen')]) - )->asDialog('width=700'); + )->asDialog('width=700;height=650'); Sidebar::Get()->addWidget($actions); } public function index_action() { - $this->discussion_types = DiscussionType::findBySQL("TRUE ORDER BY mkdate DESC"); + $this->render_vue_app( + Studip\VueApp::create('forum/discussions_types/Index') + ); } public function edit_action(DiscussionType $discussion_type = null) @@ -51,10 +53,11 @@ class Course_Forum_DiscussionTypesController extends AuthenticatedController } $this->render_vue_app( - Studip\VueApp::create('forum/discussions_types/Edit')->withProps([ - 'icons' => array_unique($icons), - 'discussion_type' => $discussion_type->toRawArray() - ]) + Studip\VueApp::create('forum/discussions_types/Edit') + ->withProps([ + 'icons' => array_unique($icons), + 'discussionType' => $discussion_type->transformData() + ]) ); } @@ -71,13 +74,4 @@ class Course_Forum_DiscussionTypesController extends AuthenticatedController $this->relocate('course/forum/discussion_types/index'); } - - public function delete_action(DiscussionType $discussion_type) - { - $discussion_type->delete(); - - PageLayout::postSuccess(_('Der Diskussionstyp wurde gelöscht.')); - - $this->relocate('course/forum/discussion_types/index'); - } } diff --git a/app/controllers/course/forum/discussions.php b/app/controllers/course/forum/discussions.php index e6cec64..4a71519 100644 --- a/app/controllers/course/forum/discussions.php +++ b/app/controllers/course/forum/discussions.php @@ -136,7 +136,7 @@ class Course_Forum_DiscussionsController extends Forum\BaseController $all_tags = array_map(fn(TagDTO $tag) => $tag->toRawArray(), TagDTO::getForumTags()); $discussion_tags = array_map(fn(TagDTO $tag) => $tag->toRawArray(), $discussion->tags); - $discussion_types = array_map(fn(DiscussionType $discussion_type) => $discussion_type->toRawArray(), DiscussionType::getForumDiscussionType()); + $discussion_types = array_map(fn(DiscussionType $discussion_type) => $discussion_type->toRawArray(), DiscussionType::getAll()); $this->render_vue_app( Studip\VueApp::create('forum/discussions/Edit') diff --git a/app/controllers/course/forum/search.php b/app/controllers/course/forum/search.php index 706674c..988ca87 100644 --- a/app/controllers/course/forum/search.php +++ b/app/controllers/course/forum/search.php @@ -36,7 +36,7 @@ class Course_Forum_SearchController extends Forum\BaseController } $tags = array_map(fn(TagDTO $tag) => $tag->toRawArray(), TagDTO::getForumTags()); - $discussion_types = array_map(fn(DiscussionType $discussion_type) => $discussion_type->toRawArray(), DiscussionType::getForumDiscussionType()); + $discussion_types = array_map(fn(DiscussionType $discussion_type) => $discussion_type->toRawArray(), DiscussionType::getAll()); $this->render_vue_app( Studip\VueApp::create('forum/search/Index') diff --git a/app/views/course/forum/discussion_types/index.php b/app/views/course/forum/discussion_types/index.php deleted file mode 100644 index f6eb534..0000000 --- a/app/views/course/forum/discussion_types/index.php +++ /dev/null @@ -1,79 +0,0 @@ - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - _('Neuen Diskussionstyp anlegen')]) ?> - - -
- icon) : ?> - icon, ['title' => htmlReady($type->icon)])->asSvg(24) ?> - - - - name) ?> - - - addLink( - $controller->url_for('course/forum/discussion_types/edit', $type), - _('Bearbeiten'), - Icon::create('edit', 'clickable', ['title' => _('Diskussionstyp bearbeiten')]), - ['data-dialog' => 'width=700'] - ) - ->addLink( - $controller->url_for('course/forum/discussion_types/delete', $type), - _('Löschen'), - Icon::create('trash', 'clickable',['title' => _('Diskussionstyp löschen')]), - ['data-confirm' => sprintf( - _('Wollen Sie "%s" löschen?'), - $type->name - )] - ); - ?> -
- -
-
diff --git a/lib/classes/JsonApi/RouteMap.php b/lib/classes/JsonApi/RouteMap.php index 47bd666..ca53a4a 100644 --- a/lib/classes/JsonApi/RouteMap.php +++ b/lib/classes/JsonApi/RouteMap.php @@ -695,7 +695,9 @@ class RouteMap $group->group('/forum-discussion-types', function ($forum) { $forum->get('', Routes\Forum\DiscussionTypeIndex::class); + $forum->post('', Routes\Forum\DiscussionTypeStore::class); $forum->get('/{type_id}', Routes\Forum\DiscussionTypeShow::class); + $forum->delete('/{type_id}', Routes\Forum\DiscussionTypeDelete::class); }); $group->group('/forum-postings', function ($forum) { diff --git a/lib/classes/JsonApi/Routes/Forum/DiscussionTypeDelete.php b/lib/classes/JsonApi/Routes/Forum/DiscussionTypeDelete.php new file mode 100644 index 0000000..2ff19c4 --- /dev/null +++ b/lib/classes/JsonApi/Routes/Forum/DiscussionTypeDelete.php @@ -0,0 +1,29 @@ +getUser($request); + if (!$GLOBALS['perm']->have_perm('root', $user->id)) { + throw new AuthorizationFailedException(); + } + + $discussion_type = DiscussionType::find($args['type_id']); + if (!$discussion_type) { + throw new RecordNotFoundException(); + } + + $discussion_type->delete(); + + return $this->getCodeResponse(204); + } +} diff --git a/lib/classes/JsonApi/Routes/Forum/DiscussionTypeIndex.php b/lib/classes/JsonApi/Routes/Forum/DiscussionTypeIndex.php index cd32b90..2955c97 100644 --- a/lib/classes/JsonApi/Routes/Forum/DiscussionTypeIndex.php +++ b/lib/classes/JsonApi/Routes/Forum/DiscussionTypeIndex.php @@ -2,7 +2,6 @@ namespace JsonApi\Routes\Forum; use Forum\DiscussionType; -use JsonApi\Errors\BadRequestException; use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Message\ResponseInterface as Response; use JsonApi\JsonApiController; @@ -10,13 +9,10 @@ use JsonApi\JsonApiController; class DiscussionTypeIndex extends JsonApiController { protected $allowedPagingParameters = ['offset', 'limit']; - protected $allowedIncludePaths = [ - \JsonApi\Schemas\Forum\DiscussionType::REL_DISCUSSIONS - ]; public function __invoke(Request $request, Response $response, $args) { - $discussion_types = DiscussionType::findBySQL('1'); + $discussion_types = DiscussionType::getAll(); return $this->getPaginatedContentResponse( array_slice($discussion_types, ...$this->getOffsetAndLimit()), diff --git a/lib/classes/JsonApi/Routes/Forum/DiscussionTypeShow.php b/lib/classes/JsonApi/Routes/Forum/DiscussionTypeShow.php index 939835a..6bfdc0f 100644 --- a/lib/classes/JsonApi/Routes/Forum/DiscussionTypeShow.php +++ b/lib/classes/JsonApi/Routes/Forum/DiscussionTypeShow.php @@ -9,10 +9,6 @@ use JsonApi\JsonApiController; class DiscussionTypeShow extends JsonApiController { - protected $allowedIncludePaths = [ - \JsonApi\Schemas\Forum\DiscussionType::REL_DISCUSSIONS - ]; - public function __invoke(Request $request, Response $response, $args) { $discussion_type = DiscussionType::find($args['type_id']); diff --git a/lib/classes/JsonApi/Routes/Forum/DiscussionTypeStore.php b/lib/classes/JsonApi/Routes/Forum/DiscussionTypeStore.php new file mode 100644 index 0000000..20db2cc --- /dev/null +++ b/lib/classes/JsonApi/Routes/Forum/DiscussionTypeStore.php @@ -0,0 +1,46 @@ +getUser($request); + if (!$GLOBALS['perm']->have_perm('root', $user->id)) { + throw new AuthorizationFailedException(); + } + + $json = $this->validate($request); + $discussion_type = DiscussionType::create([ + 'name' => self::arrayGet($json, 'data.attributes.name'), + 'icon' => self::arrayGet($json, 'data.attributes.icon') + ]); + + return $this->getCreatedResponse($discussion_type); + } + + protected function validateResourceDocument($json, $data) + { + $required_keys = [ + 'data.attributes.name' => 'Missing `data.attributes.name`', + 'data.attributes.icon' => 'Missing `data.attributes.icon`', + ]; + + foreach ($required_keys as $key => $error_message) { + if (!self::arrayHas($json, $key)) { + return $error_message; + } + } + + return null; + } +} diff --git a/lib/classes/JsonApi/Schemas/Forum/DiscussionType.php b/lib/classes/JsonApi/Schemas/Forum/DiscussionType.php index 07ea34d..bac3686 100644 --- a/lib/classes/JsonApi/Schemas/Forum/DiscussionType.php +++ b/lib/classes/JsonApi/Schemas/Forum/DiscussionType.php @@ -11,34 +11,40 @@ class DiscussionType extends SchemaProvider const REL_DISCUSSIONS = 'discussions'; - public function getId($discussionType): ?string + /** + * @inheritDoc + * @param \Forum\DiscussionType $resource + */ + public function getId($resource): ?string { - return $discussionType->type_id; + return $resource->type_id; } - public function getAttributes($discussionType, ContextInterface $context): iterable + /** + * @inheritDoc + * @param \Forum\DiscussionType $resource + */ + public function getAttributes($resource, ContextInterface $context): iterable { - return [ - 'name' => $discussionType->name, - 'icon' => $discussionType->icon, - 'mkdate' => date('c', $discussionType->mkdate), - 'chdate' => date('c', $discussionType->chdate), - ]; + $attributes = $resource->transformData(); + unset($attributes['id']); + return $attributes; } /** - * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @inheritDoc + * @param \Forum\DiscussionType $resource */ - public function getRelationships($discussionType, ContextInterface $context): iterable + public function getRelationships($resource, ContextInterface $context): iterable { $relationships = []; - $relationships = $this->addDiscussionsRelationship($relationships, $discussionType, $this->shouldInclude($context, self::REL_DISCUSSIONS)); + $relationships = $this->addDiscussionsRelationship($relationships, $resource, $this->shouldInclude($context, self::REL_DISCUSSIONS)); return $relationships; } - private function addDiscussionsRelationship($relationships, $discussionType, $withDiscussions = false) + private function addDiscussionsRelationship(array $relationships, \Forum\DiscussionType $discussionType, $withDiscussions = false): array { if ($withDiscussions) { $relationships[self::REL_DISCUSSIONS] = [ diff --git a/lib/models/Forum/DiscussionType.php b/lib/models/Forum/DiscussionType.php index 0462388..127abd3 100644 --- a/lib/models/Forum/DiscussionType.php +++ b/lib/models/Forum/DiscussionType.php @@ -30,11 +30,25 @@ class DiscussionType extends SimpleORMap parent::configure($config); } - public static function getForumDiscussionType(): array + /** + * @return self[] + */ + public static function getAll(): array { return self::findBySQL("TRUE ORDER BY `mkdate` DESC"); } + public function transformData(): array + { + return [ + 'id' => $this->type_id, + 'icon' => $this->icon, + 'name' => $this->name, + 'chdate' => date('c', $this->chdate), + 'mkdate' => date('c', $this->mkdate) + ]; + } + /** * @return Discussion[] */ diff --git a/resources/assets/stylesheets/scss/buttons.scss b/resources/assets/stylesheets/scss/buttons.scss index bedd67c..e4197c0 100644 --- a/resources/assets/stylesheets/scss/buttons.scss +++ b/resources/assets/stylesheets/scss/buttons.scss @@ -99,7 +99,7 @@ button.button { } @mixin button-base() { - color: var(--base-color); + color: var(--color--brand-primary); transition: color var(--transition-duration); } diff --git a/resources/assets/stylesheets/scss/forum.scss b/resources/assets/stylesheets/scss/forum.scss index b0a02e9..ccfe15f 100644 --- a/resources/assets/stylesheets/scss/forum.scss +++ b/resources/assets/stylesheets/scss/forum.scss @@ -665,10 +665,6 @@ $card-max-width: 300px; .discussion { background-color: var(--color--main-navigation-background); - hr { - margin: 0; - } - &__status, &__body, &__form-container { @@ -1018,8 +1014,11 @@ $card-max-width: 300px; gap: 15px; padding: 15px; justify-content: space-between; - border: thin solid var(--color--button-inactive-border); + border: 1px solid var(--light-gray-color-40); max-width: calc(48em - 30px); + border-radius: var(--border-radius-default); + max-height: 300px; + overflow-y: scroll; .button { display: flex; diff --git a/resources/vue/apps/forum/discussions/Show.vue b/resources/vue/apps/forum/discussions/Show.vue index 9c0d1c0..7e52669 100644 --- a/resources/vue/apps/forum/discussions/Show.vue +++ b/resources/vue/apps/forum/discussions/Show.vue @@ -216,14 +216,14 @@ onMounted(async () => { {{ $gettext('Es sind noch keine Beiträge vorhanden.') }}

-
+
-
+
diff --git a/resources/vue/apps/forum/discussions_types/Edit.vue b/resources/vue/apps/forum/discussions_types/Edit.vue index 1d5b48b..40084ea 100644 --- a/resources/vue/apps/forum/discussions_types/Edit.vue +++ b/resources/vue/apps/forum/discussions_types/Edit.vue @@ -1,11 +1,12 @@