diff options
| author | Murtaza Sultani <sultani@data-quest.de> | 2025-07-25 12:25:37 +0200 |
|---|---|---|
| committer | Murtaza Sultani <sultani@data-quest.de> | 2025-07-25 12:25:37 +0200 |
| commit | d83a8347ed60b06b360827dc8a1026a70815a483 (patch) | |
| tree | 358db42c7c3f35dc6c599c36b91baee0b8039a79 | |
| parent | 1d51d3baf430da6b4573b42aae5f0db9cea838c1 (diff) | |
Resolve "Forumsuche ohne Reload"
Closes #5747
Merge request studip/studip!4388
| -rw-r--r-- | app/controllers/course/forum/discussions.php | 2 | ||||
| -rw-r--r-- | app/controllers/course/forum/search.php | 185 | ||||
| -rw-r--r-- | app/controllers/course/forum/topics.php | 2 | ||||
| -rw-r--r-- | lib/classes/JsonApi/Routes/Forum/ForumDiscussionIndex.php | 65 | ||||
| -rw-r--r-- | lib/models/Forum/ForumDiscussion.php | 54 | ||||
| -rw-r--r-- | resources/vue/apps/forum/discussions/Edit.vue | 2 | ||||
| -rw-r--r-- | resources/vue/apps/forum/search/Index.vue | 142 | ||||
| -rw-r--r-- | resources/vue/components/forum/topics/SelectTopicInput.vue | 2 |
8 files changed, 251 insertions, 203 deletions
diff --git a/app/controllers/course/forum/discussions.php b/app/controllers/course/forum/discussions.php index 7e7aca7..51f2c0f 100644 --- a/app/controllers/course/forum/discussions.php +++ b/app/controllers/course/forum/discussions.php @@ -105,7 +105,7 @@ class Course_Forum_DiscussionsController extends Forum\ForumBaseController 'category' => $category ? $category->toRawArray() : [], 'read_index' => (int) ($posting_read ? $posting_read->read_index : 0), 'redirect' => Request::option('redirect'), - 'search_keyword' => $_SESSION['forum'][$this->range_id]['search']['keyword'] ?? '' + 'search_keyword' => $_SESSION['forum'][$this->range_id]['search_filter']['keyword'] ?? '' ]) ); } diff --git a/app/controllers/course/forum/search.php b/app/controllers/course/forum/search.php index c9f4588..1a028c1 100644 --- a/app/controllers/course/forum/search.php +++ b/app/controllers/course/forum/search.php @@ -1,9 +1,7 @@ <?php require_once 'ForumBaseController.php'; -use Forum\ForumDiscussion; use Forum\ForumDiscussionType; -use Forum\DTO\ForumMember; use Forum\DTO\ForumTag; class Course_Forum_SearchController extends Forum\ForumBaseController @@ -38,178 +36,75 @@ class Course_Forum_SearchController extends Forum\ForumBaseController ]; } - $search_object = $this->buildSearchObject(); - $all_tags = array_map(fn(ForumTag $tag) => $tag->toRawArray(), ForumTag::getForumTags()); + $tags = array_map(fn(ForumTag $tag) => $tag->toRawArray(), ForumTag::getForumTags()); $discussion_types = array_map(fn(ForumDiscussionType $discussion_type) => $discussion_type->toRawArray(), ForumDiscussionType::getForumDiscussionType()); $this->render_vue_app( Studip\VueApp::create('forum/search/Index') ->withProps([ - 'search' => $search_object, - 'discussions' => $this->getResult($search_object), + 'filter' => $this->getForumFilter(), 'topics' => $topics, 'discussion_types' => $discussion_types, - 'tags' => $all_tags, + 'tags' => $tags, 'course_members' => $course_members, ]) ); } - private function getResult($search_object): array + private function getForumFilter(): array { - if ($this->isSearchObjectEmpty($search_object)) { - unset($_SESSION['forum'][$this->range_id]['search']); - return []; - } + $request = Request::getInstance(); + $filter = []; + $session_filter = $_SESSION['forum'][$this->range_id]['search_filter'] ?? []; - $query = [ - "SELECT - discussions.*, - MAX(postings.mkdate) AS latest_post_date - FROM forum_discussions AS discussions - LEFT JOIN forum_postings AS postings USING(discussion_id) - LEFT JOIN tags_relations ON (tags_relations.range_id = discussions.discussion_id AND range_type = 'forum') - WHERE postings.range_id = :range_id ", - [ - 'range_id' => $this->range_id - ] - ]; - - $keyword = $search_object['keyword']; - if ($keyword) { - $query[0] .= " AND (discussions.title LIKE :keyword OR postings.content LIKE :keyword)"; - $query[1]["keyword"] = "%$keyword%"; + if ($request->offsetExists('keyword')) { + $filter['keyword'] = Request::get('keyword'); + } else if (isset($session_filter['keyword'])) { + $filter['keyword'] = $session_filter['keyword']; } - if ($search_object['begin']) { - $query[0] .= " AND postings.mkdate >= :begin"; - $query[1]['begin'] = $search_object['begin']; + if ($request->offsetExists('begin')) { + $filter['begin'] = Request::int('begin'); + } else if (isset($session_filter['begin'])) { + $filter['begin'] = (int) $session_filter['begin']; } - if ($search_object['end']) { - $query[0] .= " AND postings.mkdate <= :end"; - $query[1]['end'] = $search_object['end']; + if ($request->offsetExists('end')) { + $filter['end'] = Request::int('end'); + } else if (isset($session_filter['end'])) { + $filter['end'] = (int) $session_filter['end']; } - if ($search_object['topic_ids']) { - $query[0] .= " AND discussions.topic_id IN (:topic_ids)"; - $query[1]['topic_ids'] = $search_object['topic_ids']; + if ($request->offsetExists('status')) { + $filter['status'] = Request::int('status'); + } else if (isset($session_filter['status'])) { + $filter['status'] = (int) $session_filter['status']; } - if ($search_object['discussion_type_ids']) { - $query[0] .= " AND discussions.type_id IN (:type_ids)"; - $query[1]['type_ids'] = $search_object['discussion_type_ids']; + if ($request->offsetExists('type_ids')) { + $filter['type_ids'] = Request::getArray('type_ids'); + } else if (isset($session_filter['type_ids'])) { + $filter['type_ids'] = $session_filter['type_ids']; } - if ($search_object['tag_ids']) { - $query[0] .= " AND tags_relations.tag_id IN (:tag_ids)"; - $query[1]['tag_ids'] = $search_object['tag_ids']; + if ($request->offsetExists('tag_ids')) { + $filter['tag_ids'] = Request::getArray('tag_ids'); + } else if (isset($session_filter['tag_ids'])) { + $filter['tag_ids'] = $session_filter['tag_ids']; } - if ($search_object['user_ids']) { - $query[0] .= " AND postings.user_id IN (:user_ids)"; - $query[1]['user_ids'] = $search_object['user_ids']; + if ($request->offsetExists('topic_ids')) { + $filter['topic_ids'] = Request::getArray('topic_ids'); + } else if (isset($session_filter['topic_ids'])) { + $filter['topic_ids'] = $session_filter['topic_ids']; } - $query[0] .= match ($search_object['discussion_status']) { - 2 => " AND discussions.closed_at IS NULL", // opens - 3 => " AND discussions.closed_at IS NOT NULL", // closed - default => "" - }; - - $discussions = DBManager::get()->fetchAll( - $query[0]." GROUP BY discussions.discussion_id ORDER BY latest_post_date DESC", - $query[1], - ForumDiscussion::buildExisting(...) - ); - - return array_map(function (ForumDiscussion $discussion) { - $members = array_map(fn(ForumMember $member) => $member->toRawArray(), $discussion->members); - $tags = array_map(fn(ForumTag $tag) => $tag->toRawArray(), $discussion->tags); - $metadata = $discussion->getMetaData(); - - return [ - 'id' => $discussion->discussion_id, - 'title' => $discussion->title, - 'closed_at' => $discussion->closed_at ? date('c', $discussion->closed_at) : null, - 'view_count' => (int) $discussion->view_count, - 'sticky' => (bool) $discussion->sticky, - 'mkdate' => date('c', $discussion->mkdate), - 'chdate' => date('c', $discussion->chdate), - 'topic' => $discussion->topic->toRawArray(), - 'category' => $discussion->category ? [ - 'name' => $discussion->category->name, - 'color' => $discussion->category->color, - ] : [], - 'discussion_type' => $discussion->discussion_type ? [ - 'name' => $discussion->discussion_type->name, - 'icon' => $discussion->discussion_type->icon, - ] : [], - 'members' => $members, - 'tags' => $tags, - 'meta' => [ - 'postings_count' => (int) $metadata['postings_count'], - 'recent_activity' => $metadata['recent_activity'] ? date('c', $metadata['recent_activity']) : null, - ] - ]; - }, $discussions); - } - - private function isSearchObjectEmpty($search_object): bool { - if ( - $search_object['keyword'] || - $search_object['begin'] || - $search_object['end'] || - $search_object['discussion_status'] || - $search_object['discussion_type_ids'] || - $search_object['tag_ids'] || - $search_object['topic_ids'] || - $search_object['user_ids'] - ) { - return false; - } - - return true; - } - - private function buildSearchObject(): array - { - $request = Request::getInstance(); - if ( - $request->offsetExists('keyword') || - $request->offsetExists('begin') || - $request->offsetExists('end') || - $request->offsetExists('discussion_status') || - $request->offsetExists('discussion_type_ids') || - $request->offsetExists('tag_ids') || - $request->offsetExists('topic_ids') || - $request->offsetExists('user_ids') - ) { - $search_object = [ - 'keyword' => Request::get('keyword'), - 'begin' => Request::int('begin'), - 'end' => Request::int('end'), - 'discussion_status' => Request::int('discussion_status'), - 'discussion_type_ids' => Request::getArray('discussion_type_ids'), - 'tag_ids' => Request::getArray('tag_ids'), - 'topic_ids' => Request::getArray('topic_ids'), - 'user_ids' => Request::getArray('user_ids') - ]; - - $_SESSION['forum'][$this->range_id]['search'] = $search_object; - return $search_object; + if ($request->offsetExists('user_ids')) { + $filter['user_ids'] = Request::getArray('user_ids'); + } else if (isset($session_filter['user_ids'])) { + $filter['user_ids'] = $session_filter['user_ids']; } - $session_search = $_SESSION['forum'][$this->range_id]['search'] ?? []; - return [ - 'keyword' => $session_search['keyword'] ?? '', - 'begin' => $session_search['begin'] ?? 0, - 'end' => $session_search['end'] ?? 0, - 'discussion_status' => $session_search['discussion_status'] ?? 0, - 'discussion_type_ids' => $session_search['discussion_type_ids'] ?? [], - 'tag_ids' => $session_search['tag_ids'] ?? [], - 'topic_ids' => $session_search['topic_ids'] ?? [], - 'user_ids' => $session_search['user_ids'] ?? [] - ]; + return $filter; } } diff --git a/app/controllers/course/forum/topics.php b/app/controllers/course/forum/topics.php index 652ecf9..a72da84 100644 --- a/app/controllers/course/forum/topics.php +++ b/app/controllers/course/forum/topics.php @@ -11,7 +11,7 @@ class Course_Forum_TopicsController extends Forum\ForumBaseController { parent::before_filter($action, $args); - unset($_SESSION['forum'][$this->range_id]['search']); + unset($_SESSION['forum'][$this->range_id]['search_filter']); Navigation::activateItem('course/forum/topics'); } diff --git a/lib/classes/JsonApi/Routes/Forum/ForumDiscussionIndex.php b/lib/classes/JsonApi/Routes/Forum/ForumDiscussionIndex.php index 140a502..83af8ac 100644 --- a/lib/classes/JsonApi/Routes/Forum/ForumDiscussionIndex.php +++ b/lib/classes/JsonApi/Routes/Forum/ForumDiscussionIndex.php @@ -12,7 +12,17 @@ use Forum\ForumDiscussion; class ForumDiscussionIndex extends JsonApiController { protected $allowedPagingParameters = ['offset', 'limit']; - protected $allowedFilteringParameters = ['last-visit']; + protected $allowedFilteringParameters = [ + 'last-visit', + 'keyword', + 'begin', + 'end', + 'topic-ids', + 'type-ids', + 'tag-ids', + 'user-ids', + 'status' + ]; protected $allowedIncludePaths = [ \JsonApi\Schemas\Forum\ForumCategory::REL_TOPICS, \JsonApi\Schemas\Forum\ForumDiscussion::REL_CATEGORY, @@ -35,14 +45,61 @@ class ForumDiscussionIndex extends JsonApiController throw new AuthorizationFailedException(); } - $filtering = $this->getQueryParameters()->getFilteringParameters() ?: []; - $last_visit = $filtering['last-visit'] ?? 0; + $filters = $this->getFilter(); + if ($filters) { + $_SESSION['forum'][$range->id]['search_filter'] = $filters; + } - $discussions = ForumDiscussion::getCourseDiscussions($range->id, $last_visit); + $discussions = ForumDiscussion::getCourseDiscussions($range->id, $filters); return $this->getPaginatedContentResponse( array_slice($discussions, ...$this->getOffsetAndLimit()), count($discussions) ); } + + private function getFilter(): array + { + $filtering = $this->getQueryParameters()->getFilteringParameters() ?: []; + + $discussion_filter = []; + + if (isset($filtering['last-visit'])) { + $discussion_filter['last_visit'] = (int) $filtering['last-visit']; + } + + if (isset($filtering['keyword'])) { + $discussion_filter['keyword'] = $filtering['keyword']; + } + + if (isset($filtering['status'])) { + $discussion_filter['status'] = (int) $filtering['status']; + } + + if (isset($filtering['begin'])) { + $discussion_filter['begin'] = (int) $filtering['begin']; + } + + if (isset($filtering['end'])) { + $discussion_filter['end'] = (int) $filtering['end']; + } + + if (isset($filtering['topic-ids'])) { + $discussion_filter['topic_ids'] = explode(',', $filtering['topic-ids']); + } + + if (isset($filtering['type-ids'])) { + $discussion_filter['type_ids'] = explode(',', $filtering['type-ids']); + } + + if (isset($filtering['tag-ids'])) { + $discussion_filter['tag_ids'] = explode(',', $filtering['tag-ids']); + } + + if (isset($filtering['user-ids'])) { + $discussion_filter['user_ids'] = explode(',', $filtering['user-ids']); + } + + return $discussion_filter; + } } diff --git a/lib/models/Forum/ForumDiscussion.php b/lib/models/Forum/ForumDiscussion.php index ce648fa..0dae82e 100644 --- a/lib/models/Forum/ForumDiscussion.php +++ b/lib/models/Forum/ForumDiscussion.php @@ -80,9 +80,12 @@ class ForumDiscussion extends SimpleORMap } /** + * @param string $range_id course_id or institute_id. + * @param array $filter Optional: filters to apply. + * * @return self[] */ - public static function getCourseDiscussions($range_id, $last_visit = 0): array + public static function getCourseDiscussions(string $range_id, array $filter = []): array { $query = [ "SELECT @@ -91,13 +94,58 @@ class ForumDiscussion extends SimpleORMap FROM forum_discussions AS discussions JOIN forum_postings as postings USING (discussion_id) JOIN forum_topics AS topics USING (topic_id) + LEFT JOIN tags_relations ON (tags_relations.range_id = discussions.discussion_id AND range_type = 'forum') WHERE topics.range_id = :range_id", ['range_id' => $range_id] ]; - if ($last_visit) { + if (isset($filter['last_visit'])) { $query[0] .= " AND postings.mkdate > :last_visit"; - $query[1]["last_visit"] = $last_visit; + $query[1]["last_visit"] = $filter['last_visit']; + } + + if (isset($filter['keyword'])) { + $keyword = $filter['keyword']; + $query[0] .= " AND (discussions.title LIKE :keyword OR postings.content LIKE :keyword)"; + $query[1]["keyword"] = "%$keyword%"; + } + + if (isset($filter['begin'])) { + $query[0] .= " AND postings.mkdate >= :begin"; + $query[1]['begin'] = $filter['begin']; + } + + if (isset($filter['end'])) { + $query[0] .= " AND postings.mkdate <= :end"; + $query[1]['end'] = $filter['end']; + } + + if (isset($filter['topic_ids'])) { + $query[0] .= " AND discussions.topic_id IN (:topic_ids)"; + $query[1]['topic_ids'] = $filter['topic_ids']; + } + + if (isset($filter['type_ids'])) { + $query[0] .= " AND discussions.type_id IN (:type_ids)"; + $query[1]['type_ids'] = $filter['type_ids']; + } + + if (isset($filter['tag_ids'])) { + $query[0] .= " AND tags_relations.tag_id IN (:tag_ids)"; + $query[1]['tag_ids'] = $filter['tag_ids']; + } + + if (isset($filter['user_ids'])) { + $query[0] .= " AND postings.user_id IN (:user_ids)"; + $query[1]['user_ids'] = $filter['user_ids']; + } + + if (isset($filter['status'])) { + $query[0] .= match ($filter['status']) { + 2 => " AND discussions.closed_at IS NULL", // opens + 3 => " AND discussions.closed_at IS NOT NULL", // closed + default => "" + }; } return \DBManager::get()->fetchAll( diff --git a/resources/vue/apps/forum/discussions/Edit.vue b/resources/vue/apps/forum/discussions/Edit.vue index ed8ab6b..26960b1 100644 --- a/resources/vue/apps/forum/discussions/Edit.vue +++ b/resources/vue/apps/forum/discussions/Edit.vue @@ -121,7 +121,7 @@ onMounted(() => { <label class="flex-1"> <span class="sr-only">{{ $gettext('Thema') }}</span> <input type="hidden" name="topic" :value="JSON.stringify(discussionForm.topic)"> - <SelectTopicInput :options="topics" v-model="discussionForm.topic" :taggable="true" /> + <SelectTopicInput :options="topics" v-model="discussionForm.topic" :taggable="true" :required="true" /> </label> <label class="flex-1"> <span class="sr-only">{{ $gettext('Diskussionstyp') }}</span> diff --git a/resources/vue/apps/forum/search/Index.vue b/resources/vue/apps/forum/search/Index.vue index 2b5c4bb..d828e73 100644 --- a/resources/vue/apps/forum/search/Index.vue +++ b/resources/vue/apps/forum/search/Index.vue @@ -11,6 +11,8 @@ import DiscussionIndex from "@/vue/components/forum/discussions/DiscussionIndex. import StudipIcon from "../../../components/StudipIcon.vue"; import StudipSelect from "../../../components/StudipSelect.vue"; import {highlightText, removeHighlight} from "@/vue/components/forum/helpers"; +import {deserializeJSONAPIResponse} from "../../../../assets/javascripts/lib/jsonapiUtils"; +import StudipPagination from "../../../components/StudipPagination.vue"; const discussionStatuses = [ { @@ -30,14 +32,10 @@ const discussionStatuses = [ const CSRF = STUDIP.CSRF_TOKEN; const props = defineProps({ - search: { + filter: { type: Object, required: true }, - discussions: { - type: Array, - required: true - }, topics: { type: Array, required: true @@ -56,17 +54,20 @@ const props = defineProps({ } }); +const discussions = ref([]); +const pagination = ref({}); +const isLoading = ref(false); const isFilterVisible = ref(true); const searchForm = reactive({ - ...props.search, - begin: toDateString(props.search.begin), - end: toDateString(props.search.end), - discussion_status: discussionStatuses.find(status => status.value === props.search.discussion_status), - topics: props.topics.filter(({ topic_id }) => props.search.topic_ids.includes(topic_id)), - tags: props.tags.filter(({ id }) => props.search.tag_ids.includes(id.toString())), - types: props.discussion_types.filter(({ type_id }) => props.search.discussion_type_ids.includes(type_id.toString())), - authors: props.course_members.filter(({ user_id }) => props.search.user_ids.includes(user_id)) + ...(props.filter.keyword && { keyword: props.filter.keyword }), + ...(props.filter.begin && { begin: parseToDateString(props.filter.begin) }), + ...(props.filter.end && { end: parseToDateString(props.filter.end) }), + ...(props.filter.status && { status: discussionStatuses.find(status => status.value === props.filter.status) }), + ...(props.filter.topic_ids && { topics: props.topics.filter(({ topic_id }) => props.filter.topic_ids.includes(topic_id)) }), + ...(props.filter.tag_ids && { tags: props.tags.filter(({ id }) => props.filter.tag_ids.includes(id.toString())) }), + ...(props.filter.type_ids && { types: props.discussion_types.filter(({ type_id }) => props.filter.type_ids.includes(type_id.toString())) }), + ...(props.filter.user_ids && { authors: props.course_members.filter(({ user_id }) => props.filter.user_ids.includes(user_id)) }), }); const availableTags = computed(() => { @@ -96,12 +97,10 @@ const availableTypes = computed(() => { return props.discussion_types; }); -const actionURL = STUDIP.URLHelper.getURL(`dispatch.php/course/forum/search`); - const resetSearchForm = () => { Object.assign(searchForm, { keyword: '', - discussion_status: null, + status: null, begin: null, end: null, topics: [], @@ -109,40 +108,91 @@ const resetSearchForm = () => { types: [], authors: [] }); + + discussions.value = []; } -function toUnixTimestamp(date) { - return (new Date(date)).getTime() / 1000; +function parseToDateString(timestamp) { + if (!timestamp) { + return ''; + } + + return STUDIP.Dates.unixTimestampToISO(timestamp).split('T')[0]; } -function toDateString(unixTimestamp) { - if (!unixTimestamp) { +const filterQueryParams = computed(() => { + const filter = { + ...(searchForm.keyword && { 'keyword': searchForm.keyword }), + ...(searchForm.status && { 'status': parseInt(searchForm.status.value) }), + ...(searchForm.begin && { 'begin': STUDIP.Dates.stringToUnixTimestamp(searchForm.begin) }), + ...(searchForm.end && { 'end': STUDIP.Dates.stringToUnixTimestamp(searchForm.end) }), + ...(searchForm.types?.length && { 'type-ids': searchForm.types.map(({ type_id }) => type_id).join(',') }), + ...(searchForm.topics?.length && { 'topic-ids': searchForm.topics.map(({ topic_id }) => topic_id).join(',') }), + ...(searchForm.authors?.length && { 'user-ids': searchForm.authors.map(({ user_id }) => user_id).join(',') }), + ...(searchForm.tags?.length && { 'tag-ids': searchForm.tags.map(({ id }) => id).join(',') }), + }; + + if (Object.keys(filter).length === 0) { return ''; } - return (new Date(parseInt(unixTimestamp) * 1000)).toISOString().split('T')[0]; + return Object.entries(filter) + .map(([key, value]) => `filter[${encodeURIComponent(key)}]=${encodeURIComponent(value)}`) + .join('&'); +}); + +const fetchDiscussions = async (_, offset = 0) => { + try { + isLoading.value = true; + + const response = await STUDIP.jsonapi.withPromises().GET( + `courses/${STUDIP.URLHelper.parameters.cid}/forum-discussions`, + { + data: { + include: `category,discussion-type,members,tags,user&fields[users]=id&${filterQueryParams.value}`, + page: { offset } + } + } + ); + + pagination.value = { + ...response.meta.page, + currentPage: response.meta.page.offset / response.meta.page.limit, + links: response.links + }; + + discussions.value = await deserializeJSONAPIResponse(response); + } catch (error) { + STUDIP.Report.error(error); + } finally { + isLoading.value = false; + } } -onMounted(() => { - if(searchForm.keyword.length > 1 && props.discussions.length) { - highlightText(searchForm.keyword, '.title'); +onMounted(async () => { + if (filterQueryParams.value) { + await fetchDiscussions(); + } + + if(searchForm.keyword.length > 1 && discussions.value.length) { + highlightText(searchForm.keyword, '.discussion-title'); // remove highlights document.getElementById("forum-search").addEventListener("click", function() { - removeHighlight('.title mark'); + removeHighlight('.discussion-title mark'); }); } -}) +}); </script> <template> <ForumApp id="forum-search"> - <form :action="actionURL" method="post" class="default search-container use-utility-classes"> + <form action="#" @submit.prevent="fetchDiscussions" method="post" class="default search-container use-utility-classes"> <input type="hidden" :name="CSRF.name" :value="CSRF.value"> <h1>{{ $gettext('Suche') }}</h1> <div class="search-controls"> <div class="search-input-container"> - <input name="keyword" type="text" :value="searchForm.keyword" :placeholder="$gettext('Diskussionen oder Beiträge')"/> + <input name="keyword" type="text" v-model="searchForm.keyword" :placeholder="$gettext('Diskussionen oder Beiträge')"/> </div> <button type="submit" @@ -217,32 +267,22 @@ onMounted(() => { <div v-if="isFilterVisible" class="filter-controls"> <label> <span class="sr-only">{{ $gettext('Thema') }}</span> - <template v-for="topic in searchForm.topics" :key="topic.topic_id"> - <input type="hidden" name="topic_ids[]" :value="topic.topic_id"> - </template> - <SelectTopicInput id="" :options="availableTopics" v-model="searchForm.topics" multiple /> + <SelectTopicInput id="" :options="availableTopics" v-model="searchForm.topics" :required="false" multiple /> </label> <label> <span class="sr-only">{{ $gettext('Diskussionstyp') }}</span> - <template v-for="type in searchForm.types" :key="type.type_id"> - <input type="hidden" name="discussion_type_ids[]" :value="type.type_id"> - </template> <SelectDiscussionType :options="availableTypes" v-model="searchForm.types" multiple /> </label> <label> <span class="sr-only">{{ $gettext('Schlagworte') }}</span> - <template v-for="tag in searchForm.tags" :key="tag.id"> - <input type="hidden" name="tag_ids[]" :value="tag.id"> - </template> <SelectTagsInput :options="availableTags" v-model="searchForm.tags" multiple /> </label> <label> <span class="sr-only">{{ $gettext('Status der Diskussion') }}</span> - <input v-if="searchForm.discussion_status" type="hidden" name="discussion_status" :value="searchForm.discussion_status.value"> <StudipSelect :options="discussionStatuses" :placeholder="$gettext('Status der Diskussion')" - v-model="searchForm.discussion_status" + v-model="searchForm.status" > <template #no-options> <div> @@ -254,15 +294,9 @@ onMounted(() => { <div class="date-inputs-container"> <input type="date" v-model="searchForm.begin" :placeholder="$gettext('Von')" :aria-label="$gettext('Von')" autocomplete="off" /> <input type="date" v-model="searchForm.end" :placeholder="$gettext('Bis')" :aria-label="$gettext('Bis')" autocomplete="off" /> - - <input type="hidden" name="begin" :value="toUnixTimestamp(searchForm.begin)" /> - <input type="hidden" name="end" :value="toUnixTimestamp(searchForm.end)" /> </div> <label> <span class="sr-only">{{ $gettext('Autor/-in') }}</span> - <template v-for="user in searchForm.authors" :key="user.user_id"> - <input type="hidden" name="user_ids[]" :value="user.user_id"> - </template> <SelectUserInput :options="course_members" multiple @@ -275,7 +309,21 @@ onMounted(() => { <div class="search-result-container"> <h2>{{ $gettext('Suchergebnisse') }}</h2> - <DiscussionIndex :discussions="discussions" :withActions="false" redirect="search" /> + <DiscussionIndex :discussions="discussions" :isLoading="isLoading" redirect="search"> + <template #pagination> + <tfoot v-if="pagination && pagination.total > pagination.limit"> + <tr> + <td colspan="7"> + <StudipPagination + :currentPage="pagination.currentPage" + :totalItems="pagination.total" + :itemsPerPage="pagination.limit" + @pageUpdated="fetchDiscussions" /> + </td> + </tr> + </tfoot> + </template> + </DiscussionIndex> </div> </ForumApp> </template> diff --git a/resources/vue/components/forum/topics/SelectTopicInput.vue b/resources/vue/components/forum/topics/SelectTopicInput.vue index 7dabc95..24c0e00 100644 --- a/resources/vue/components/forum/topics/SelectTopicInput.vue +++ b/resources/vue/components/forum/topics/SelectTopicInput.vue @@ -24,7 +24,7 @@ const selectedTopics = defineModel(); <template #search="{attributes, events}"> <input class="vs__search" - :required="!selectedTopics" + :required="!selectedTopics && $attrs.required" v-bind="attributes" v-on="events" /> |
