diff --git a/open-isle-cli/src/views/PostPageView.vue b/open-isle-cli/src/views/PostPageView.vue index eaf2e551c..b6e313861 100644 --- a/open-isle-cli/src/views/PostPageView.vue +++ b/open-isle-cli/src/views/PostPageView.vue @@ -69,7 +69,7 @@
Sort by:
- +
@@ -154,6 +154,7 @@ export default { const mainContainer = ref(null) const currentIndex = ref(1) const subscribed = ref(false) + const commentSort = ref('NEWEST') // record default metadata from the main document const defaultTitle = document.title @@ -287,6 +288,7 @@ export default { const onCommentDeleted = (id) => { removeCommentFromList(Number(id), comments.value) + fetchComments() } const fetchPost = async () => { @@ -310,13 +312,12 @@ export default { category.value = data.category tags.value = data.tags || [] postReactions.value = data.reactions || [] - comments.value = (data.comments || []).map(mapComment) + await fetchComments() subscribed.value = !!data.subscribed status.value = data.status pinnedAt.value = data.pinnedAt postTime.value = TimeManager.format(data.createdAt) await nextTick() - gatherPostItems() } catch (e) { console.error(e) } @@ -379,9 +380,7 @@ export default { }) if (res.ok) { const data = await res.json() - comments.value.push(mapComment(data)) - await nextTick() - gatherPostItems() + await fetchComments() if (data.reward && data.reward > 0) { toast.success(`评论成功,获得 ${data.reward} 经验值`) } else { @@ -524,6 +523,33 @@ export default { } } + const fetchCommentSorts = () => { + return Promise.resolve([ + { id: 'NEWEST', name: '最新', icon: 'fas fa-clock' }, + { id: 'OLDEST', name: '最旧', icon: 'fas fa-hourglass-start' }, + { id: 'MOST_INTERACTIONS', name: '最多互动', icon: 'fas fa-fire' } + ]) + } + + const fetchComments = async () => { + try { + const token = getToken() + const res = await fetch(`${API_BASE_URL}/api/posts/${postId}/comments?sort=${commentSort.value}`, { + headers: { Authorization: token ? `Bearer ${token}` : '' } + }) + if (res.ok) { + const data = await res.json() + comments.value = data.map(mapComment) + await nextTick() + gatherPostItems() + } + } catch { + // ignore + } + } + + watch(commentSort, fetchComments) + const jumpToHashComment = async () => { const hash = location.hash if (hash.startsWith('#comment-')) { @@ -546,7 +572,6 @@ export default { const hash = location.hash const id = hash.startsWith('#comment-') ? hash.substring('#comment-'.length) : null await fetchPost() - gatherPostItems() if (id) expandCommentPath(id) updateCurrentIndex() await jumpToHashComment() @@ -595,7 +620,9 @@ export default { lightboxImgs, handleContentClick, isMobile, - pinnedAt + pinnedAt, + commentSort, + fetchCommentSorts } } } diff --git a/src/main/java/com/openisle/controller/CommentController.java b/src/main/java/com/openisle/controller/CommentController.java index c3aab14d1..b6dccfdfd 100644 --- a/src/main/java/com/openisle/controller/CommentController.java +++ b/src/main/java/com/openisle/controller/CommentController.java @@ -4,6 +4,7 @@ import com.openisle.model.Comment; import com.openisle.service.CommentService; import com.openisle.service.CaptchaService; import com.openisle.service.LevelService; +import com.openisle.model.CommentSort; import lombok.Data; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; @@ -56,8 +57,9 @@ public class CommentController { } @GetMapping("/posts/{postId}/comments") - public List listComments(@PathVariable Long postId) { - return commentService.getCommentsForPost(postId).stream() + public List listComments(@PathVariable Long postId, + @RequestParam(value = "sort", required = false, defaultValue = "OLDEST") CommentSort sort) { + return commentService.getCommentsForPost(postId, sort).stream() .map(this::toDtoWithReplies) .collect(Collectors.toList()); } diff --git a/src/main/java/com/openisle/controller/PostController.java b/src/main/java/com/openisle/controller/PostController.java index c839c2173..9b49ddc5c 100644 --- a/src/main/java/com/openisle/controller/PostController.java +++ b/src/main/java/com/openisle/controller/PostController.java @@ -4,6 +4,7 @@ import com.openisle.model.Comment; import com.openisle.model.Post; import com.openisle.model.Reaction; import com.openisle.service.CommentService; +import com.openisle.model.CommentSort; import com.openisle.service.PostService; import com.openisle.service.ReactionService; import com.openisle.service.CaptchaService; @@ -180,7 +181,7 @@ public class PostController { .collect(Collectors.toList()); dto.setReactions(reactions); - List comments = commentService.getCommentsForPost(post.getId()) + List comments = commentService.getCommentsForPost(post.getId(), CommentSort.OLDEST) .stream() .map(this::toCommentDtoWithReplies) .collect(Collectors.toList()); diff --git a/src/main/java/com/openisle/model/CommentSort.java b/src/main/java/com/openisle/model/CommentSort.java new file mode 100644 index 000000000..515a5465e --- /dev/null +++ b/src/main/java/com/openisle/model/CommentSort.java @@ -0,0 +1,10 @@ +package com.openisle.model; + +/** + * Sort options for comments. + */ +public enum CommentSort { + NEWEST, + OLDEST, + MOST_INTERACTIONS +} diff --git a/src/main/java/com/openisle/service/CommentService.java b/src/main/java/com/openisle/service/CommentService.java index a2cca58ff..49d11c037 100644 --- a/src/main/java/com/openisle/service/CommentService.java +++ b/src/main/java/com/openisle/service/CommentService.java @@ -4,6 +4,7 @@ import com.openisle.model.Comment; import com.openisle.model.Post; import com.openisle.model.User; import com.openisle.model.NotificationType; +import com.openisle.model.CommentSort; import com.openisle.repository.CommentRepository; import com.openisle.repository.PostRepository; import com.openisle.repository.UserRepository; @@ -104,10 +105,16 @@ public class CommentService { return comment; } - public List getCommentsForPost(Long postId) { + public List getCommentsForPost(Long postId, CommentSort sort) { Post post = postRepository.findById(postId) .orElseThrow(() -> new com.openisle.exception.NotFoundException("Post not found")); - return commentRepository.findByPostAndParentIsNullOrderByCreatedAtAsc(post); + List list = commentRepository.findByPostAndParentIsNullOrderByCreatedAtAsc(post); + if (sort == CommentSort.NEWEST) { + list.sort(java.util.Comparator.comparing(Comment::getCreatedAt).reversed()); + } else if (sort == CommentSort.MOST_INTERACTIONS) { + list.sort((a, b) -> Integer.compare(interactionCount(b), interactionCount(a))); + } + return list; } public List getReplies(Long parentId) { @@ -167,4 +174,10 @@ public class CommentService { imageUploader.removeReferences(imageUploader.extractUrls(comment.getContent())); commentRepository.delete(comment); } + + private int interactionCount(Comment comment) { + int reactions = reactionRepository.findByComment(comment).size(); + int replies = commentRepository.findByParentOrderByCreatedAtAsc(comment).size(); + return reactions + replies; + } } diff --git a/src/test/java/com/openisle/controller/CommentControllerTest.java b/src/test/java/com/openisle/controller/CommentControllerTest.java index 959e8b90e..8eeabbef9 100644 --- a/src/test/java/com/openisle/controller/CommentControllerTest.java +++ b/src/test/java/com/openisle/controller/CommentControllerTest.java @@ -19,6 +19,7 @@ import java.time.LocalDateTime; import java.util.List; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.any; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; @@ -53,7 +54,7 @@ class CommentControllerTest { void createAndListComments() throws Exception { Comment comment = createComment(1L, "hi", "bob"); Mockito.when(commentService.addComment(eq("bob"), eq(1L), eq("hi"))).thenReturn(comment); - Mockito.when(commentService.getCommentsForPost(1L)).thenReturn(List.of(comment)); + Mockito.when(commentService.getCommentsForPost(eq(1L), any())).thenReturn(List.of(comment)); Mockito.when(commentService.getReplies(1L)).thenReturn(List.of()); mockMvc.perform(post("/api/posts/1/comments") diff --git a/src/test/java/com/openisle/controller/PostControllerTest.java b/src/test/java/com/openisle/controller/PostControllerTest.java index 6a46af268..7da8cdb25 100644 --- a/src/test/java/com/openisle/controller/PostControllerTest.java +++ b/src/test/java/com/openisle/controller/PostControllerTest.java @@ -184,7 +184,7 @@ class PostControllerTest { cr.setType(com.openisle.model.ReactionType.LIKE); Mockito.when(postService.viewPost(eq(1L), Mockito.isNull())).thenReturn(post); - Mockito.when(commentService.getCommentsForPost(1L)).thenReturn(List.of(comment)); + Mockito.when(commentService.getCommentsForPost(eq(1L), any())).thenReturn(List.of(comment)); Mockito.when(commentService.getReplies(2L)).thenReturn(List.of(reply)); Mockito.when(commentService.getReplies(3L)).thenReturn(List.of()); Mockito.when(commentService.getParticipants(Mockito.anyLong(), Mockito.anyInt())).thenReturn(java.util.List.of());