aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMurtaza Sultani <sultani@data-quest.de>2025-07-25 12:25:37 +0200
committerMurtaza Sultani <sultani@data-quest.de>2025-07-25 12:25:37 +0200
commitd83a8347ed60b06b360827dc8a1026a70815a483 (patch)
tree358db42c7c3f35dc6c599c36b91baee0b8039a79
parent1d51d3baf430da6b4573b42aae5f0db9cea838c1 (diff)
Resolve "Forumsuche ohne Reload"
Closes #5747 Merge request studip/studip!4388
-rw-r--r--app/controllers/course/forum/discussions.php2
-rw-r--r--app/controllers/course/forum/search.php185
-rw-r--r--app/controllers/course/forum/topics.php2
-rw-r--r--lib/classes/JsonApi/Routes/Forum/ForumDiscussionIndex.php65
-rw-r--r--lib/models/Forum/ForumDiscussion.php54
-rw-r--r--resources/vue/apps/forum/discussions/Edit.vue2
-rw-r--r--resources/vue/apps/forum/search/Index.vue142
-rw-r--r--resources/vue/components/forum/topics/SelectTopicInput.vue2
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"
/>