mirror of
https://github.com/chenasraf/nextcloud-forum.git
synced 2026-05-17 17:28:02 +00:00
fix: directly link to replies from profile+search pages
This commit is contained in:
@@ -283,10 +283,23 @@ class PostController extends OCSController {
|
||||
// For posts by a single author, we can optimize by fetching author data once
|
||||
$author = $this->userService->enrichUserData($authorId);
|
||||
|
||||
// Enrich posts with content, reactions, and pre-fetched author data
|
||||
return new DataResponse(array_map(function ($p) use ($bbcodes, $reactionsByPostId, $currentUserId, $author) {
|
||||
// Enrich posts with content, reactions, pre-fetched author data, and page number
|
||||
$perPage = 20;
|
||||
return new DataResponse(array_map(function ($p) use ($bbcodes, $reactionsByPostId, $currentUserId, $author, $perPage) {
|
||||
$postReactions = $reactionsByPostId[$p->getId()] ?? [];
|
||||
return $this->postEnrichmentService->enrichPost($p, $bbcodes, $postReactions, $currentUserId, $author);
|
||||
$enriched = $this->postEnrichmentService->enrichPost($p, $bbcodes, $postReactions, $currentUserId, $author);
|
||||
|
||||
// Calculate the page number for direct linking
|
||||
if (!$p->getIsFirstPost()) {
|
||||
try {
|
||||
$position = $this->postMapper->getReplyPosition($p->getThreadId(), $p->getId());
|
||||
$enriched['page'] = (int)floor($position / $perPage) + 1;
|
||||
} catch (\Exception $e) {
|
||||
// Fallback - page unknown
|
||||
}
|
||||
}
|
||||
|
||||
return $enriched;
|
||||
}, $posts));
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->error('Error fetching posts by author: ' . $e->getMessage());
|
||||
|
||||
@@ -8,6 +8,7 @@ declare(strict_types=1);
|
||||
namespace OCA\Forum\Controller;
|
||||
|
||||
use OCA\Forum\Db\Post;
|
||||
use OCA\Forum\Db\PostMapper;
|
||||
use OCA\Forum\Db\Thread;
|
||||
use OCA\Forum\Db\ThreadMapper;
|
||||
use OCA\Forum\Service\PostEnrichmentService;
|
||||
@@ -29,6 +30,7 @@ class SearchController extends OCSController {
|
||||
string $appName,
|
||||
IRequest $request,
|
||||
private SearchService $searchService,
|
||||
private PostMapper $postMapper,
|
||||
private ThreadMapper $threadMapper,
|
||||
private PostEnrichmentService $postEnrichmentService,
|
||||
private ThreadEnrichmentService $threadEnrichmentService,
|
||||
@@ -124,7 +126,8 @@ class SearchController extends OCSController {
|
||||
}, $results['threads']);
|
||||
|
||||
// Enrich posts with pre-fetched author data and thread context
|
||||
$enrichedPosts = array_map(function ($post) use ($authors) {
|
||||
$perPage = 20;
|
||||
$enrichedPosts = array_map(function ($post) use ($authors, $perPage) {
|
||||
$enriched = $this->postEnrichmentService->enrichPost($post, [], [], null, $authors[$post->getAuthorId()]);
|
||||
// Add thread info for context
|
||||
try {
|
||||
@@ -137,6 +140,16 @@ class SearchController extends OCSController {
|
||||
$enriched['threadSlug'] = null;
|
||||
}
|
||||
|
||||
// Calculate the page number for direct linking
|
||||
if (!$post->getIsFirstPost()) {
|
||||
try {
|
||||
$position = $this->postMapper->getReplyPosition($post->getThreadId(), $post->getId());
|
||||
$enriched['page'] = (int)floor($position / $perPage) + 1;
|
||||
} catch (\Exception $e) {
|
||||
// Fallback - page unknown
|
||||
}
|
||||
}
|
||||
|
||||
return $enriched;
|
||||
}, $results['posts']);
|
||||
|
||||
|
||||
@@ -157,7 +157,7 @@ describe('SearchPostResult', () => {
|
||||
},
|
||||
})
|
||||
await wrapper.find('.search-post-result').trigger('click')
|
||||
expect(mockPush).toHaveBeenCalledWith('/t/my-thread#post-42')
|
||||
expect(mockPush).toHaveBeenCalledWith({ path: '/t/my-thread', query: { post: '42' } })
|
||||
})
|
||||
|
||||
it('should not navigate when thread slug is missing', async () => {
|
||||
|
||||
@@ -3,12 +3,7 @@
|
||||
<div class="result-header">
|
||||
<div class="thread-context">
|
||||
<span class="meta-label">{{ strings.inThread }}:</span>
|
||||
<router-link
|
||||
v-if="post.threadSlug"
|
||||
:to="`/t/${post.threadSlug}#post-${post.id}`"
|
||||
class="thread-link"
|
||||
@click.stop
|
||||
>
|
||||
<router-link v-if="post.threadSlug" :to="postLink" class="thread-link" @click.stop>
|
||||
{{ post.threadTitle }}
|
||||
</router-link>
|
||||
<span v-else class="thread-missing">{{ strings.threadUnavailable }}</span>
|
||||
@@ -74,6 +69,13 @@ export default defineComponent({
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
postLink(): { path: string; query: Record<string, string> } {
|
||||
const query: Record<string, string> = { post: String(this.post.id) }
|
||||
if ((this.post as any).page) {
|
||||
query.page = String((this.post as any).page)
|
||||
}
|
||||
return { path: `/t/${this.post.threadSlug}`, query }
|
||||
},
|
||||
highlightedContent(): string {
|
||||
// Strip HTML tags first, then highlight query terms, then truncate
|
||||
const text = this.stripHtml(this.post.content)
|
||||
@@ -84,7 +86,7 @@ export default defineComponent({
|
||||
methods: {
|
||||
navigateToPost(): void {
|
||||
if (this.post.threadSlug) {
|
||||
this.$router.push(`/t/${this.post.threadSlug}#post-${this.post.id}`)
|
||||
this.$router.push(this.postLink)
|
||||
}
|
||||
},
|
||||
stripHtml(html: string): string {
|
||||
|
||||
@@ -364,12 +364,14 @@ export default defineComponent({
|
||||
this.$router.push({ path: `/t/${thread.slug}`, query })
|
||||
},
|
||||
|
||||
navigateToPost(post: Post) {
|
||||
if (post.threadSlug) {
|
||||
this.$router.push(`/t/${post.threadSlug}#post-${post.id}`)
|
||||
} else {
|
||||
this.$router.push(`/thread/${post.threadId}#post-${post.id}`)
|
||||
navigateToPost(post: Post & { page?: number }) {
|
||||
const slug = post.threadSlug || `thread/${post.threadId}`
|
||||
const path = `/t/${slug}`
|
||||
const query: Record<string, string> = { post: String(post.id) }
|
||||
if (post.page) {
|
||||
query.page = String(post.page)
|
||||
}
|
||||
this.$router.push({ path, query })
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace OCA\Forum\Tests\Controller;
|
||||
use OCA\Forum\AppInfo\Application;
|
||||
use OCA\Forum\Controller\SearchController;
|
||||
use OCA\Forum\Db\Post;
|
||||
use OCA\Forum\Db\PostMapper;
|
||||
use OCA\Forum\Db\Thread;
|
||||
use OCA\Forum\Db\ThreadMapper;
|
||||
use OCA\Forum\Service\PostEnrichmentService;
|
||||
@@ -26,6 +27,8 @@ class SearchControllerTest extends TestCase {
|
||||
private SearchController $controller;
|
||||
/** @var SearchService&MockObject */
|
||||
private SearchService $searchService;
|
||||
/** @var PostMapper&MockObject */
|
||||
private PostMapper $postMapper;
|
||||
/** @var ThreadMapper&MockObject */
|
||||
private ThreadMapper $threadMapper;
|
||||
/** @var PostEnrichmentService&MockObject */
|
||||
@@ -44,6 +47,7 @@ class SearchControllerTest extends TestCase {
|
||||
protected function setUp(): void {
|
||||
$this->request = $this->createMock(IRequest::class);
|
||||
$this->searchService = $this->createMock(SearchService::class);
|
||||
$this->postMapper = $this->createMock(PostMapper::class);
|
||||
$this->threadMapper = $this->createMock(ThreadMapper::class);
|
||||
$this->postEnrichmentService = $this->createMock(PostEnrichmentService::class);
|
||||
$this->threadEnrichmentService = $this->createMock(ThreadEnrichmentService::class);
|
||||
@@ -75,6 +79,7 @@ class SearchControllerTest extends TestCase {
|
||||
Application::APP_ID,
|
||||
$this->request,
|
||||
$this->searchService,
|
||||
$this->postMapper,
|
||||
$this->threadMapper,
|
||||
$this->postEnrichmentService,
|
||||
$this->threadEnrichmentService,
|
||||
|
||||
Reference in New Issue
Block a user