{{ formatType(item.type) }}
@@ -146,7 +168,9 @@ export default {
USER_ACTIVITY: 'fas fa-user',
FOLLOWED_POST: 'fas fa-feather-alt',
USER_FOLLOWED: 'fas fa-user-plus',
- USER_UNFOLLOWED: 'fas fa-user-minus'
+ USER_UNFOLLOWED: 'fas fa-user-minus',
+ POST_SUBSCRIBED: 'fas fa-bookmark',
+ POST_UNSUBSCRIBED: 'fas fa-bookmark'
}
const reactionEmojiMap = {
@@ -223,6 +247,17 @@ export default {
}
}
})
+ } else if (n.type === 'POST_SUBSCRIBED' || n.type === 'POST_UNSUBSCRIBED') {
+ notifications.value.push({
+ ...n,
+ icon: iconMap[n.type],
+ iconClick: () => {
+ if (n.post) {
+ markRead(n.id)
+ router.push(`/posts/${n.post.id}`)
+ }
+ }
+ })
} else {
notifications.value.push({
...n,
@@ -249,6 +284,10 @@ export default {
return '关注的帖子有新评论'
case 'FOLLOWED_POST':
return '关注的用户发布了新文章'
+ case 'POST_SUBSCRIBED':
+ return '有人订阅了你的文章'
+ case 'POST_UNSUBSCRIBED':
+ return '有人取消订阅你的文章'
case 'USER_FOLLOWED':
return '有人关注了你'
case 'USER_UNFOLLOWED':
diff --git a/open-isle-cli/src/views/PostPageView.vue b/open-isle-cli/src/views/PostPageView.vue
index 6f1148dca..0b92e73f9 100644
--- a/open-isle-cli/src/views/PostPageView.vue
+++ b/open-isle-cli/src/views/PostPageView.vue
@@ -19,11 +19,19 @@
BLOCK
-
+
订阅文章
-
+
取消订阅
@@ -106,7 +114,7 @@ import ReactionsGroup from '../components/ReactionsGroup.vue'
import DropdownMenu from '../components/DropdownMenu.vue'
import { renderMarkdown } from '../utils/markdown'
import { API_BASE_URL, toast } from '../main'
-import { getToken } from '../utils/auth'
+import { getToken, authState } from '../utils/auth'
import TimeManager from '../utils/time'
import { hatch } from 'ldrs'
import { useRouter } from 'vue-router'
@@ -133,6 +141,9 @@ export default {
const postItems = ref([])
const mainContainer = ref(null)
const currentIndex = ref(1)
+ const subscribed = ref(false)
+ const loggedIn = computed(() => authState.loggedIn)
+ const isAuthor = computed(() => authState.username === author.value.username)
const reviewMenuItems = [
{ text: '通过审核', onClick: () => {} },
{ text: '驳回', color: 'red', onClick: () => {} }
@@ -211,6 +222,7 @@ export default {
tags.value = data.tags || []
postReactions.value = data.reactions || []
comments.value = (data.comments || []).map(mapComment)
+ subscribed.value = !!data.subscribed
postTime.value = TimeManager.format(data.createdAt)
await nextTick()
gatherPostItems()
@@ -296,6 +308,42 @@ export default {
})
}
+ const subscribePost = async () => {
+ const token = getToken()
+ if (!token) {
+ toast.error('请先登录')
+ return
+ }
+ const res = await fetch(`${API_BASE_URL}/api/subscriptions/posts/${postId}`, {
+ method: 'POST',
+ headers: { Authorization: `Bearer ${token}` }
+ })
+ if (res.ok) {
+ subscribed.value = true
+ toast.success('已订阅')
+ } else {
+ toast.error('操作失败')
+ }
+ }
+
+ const unsubscribePost = async () => {
+ const token = getToken()
+ if (!token) {
+ toast.error('请先登录')
+ return
+ }
+ const res = await fetch(`${API_BASE_URL}/api/subscriptions/posts/${postId}`, {
+ method: 'DELETE',
+ headers: { Authorization: `Bearer ${token}` }
+ })
+ if (res.ok) {
+ subscribed.value = false
+ toast.success('已取消订阅')
+ } else {
+ toast.error('操作失败')
+ }
+ }
+
const jumpToHashComment = async () => {
const hash = location.hash
if (hash.startsWith('#comment-')) {
@@ -344,10 +392,15 @@ export default {
onSliderInput,
onScroll: updateCurrentIndex,
copyPostLink,
+ subscribePost,
+ unsubscribePost,
renderMarkdown,
isWaitingFetchingPost,
isWaitingPostingComment,
- gotoProfile
+ gotoProfile,
+ subscribed,
+ loggedIn,
+ isAuthor
}
}
}
diff --git a/src/main/java/com/openisle/controller/PostController.java b/src/main/java/com/openisle/controller/PostController.java
index 25304957e..84f53a1a4 100644
--- a/src/main/java/com/openisle/controller/PostController.java
+++ b/src/main/java/com/openisle/controller/PostController.java
@@ -8,6 +8,7 @@ import com.openisle.service.PostService;
import com.openisle.service.ReactionService;
import com.openisle.service.CaptchaService;
import com.openisle.service.DraftService;
+import com.openisle.service.SubscriptionService;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
@@ -26,6 +27,7 @@ public class PostController {
private final PostService postService;
private final CommentService commentService;
private final ReactionService reactionService;
+ private final SubscriptionService subscriptionService;
private final CaptchaService captchaService;
private final DraftService draftService;
@@ -50,7 +52,7 @@ public class PostController {
public ResponseEntity
getPost(@PathVariable Long id, Authentication auth) {
String viewer = auth != null ? auth.getName() : null;
Post post = postService.viewPost(id, viewer);
- return ResponseEntity.ok(toDto(post));
+ return ResponseEntity.ok(toDto(post, viewer));
}
@GetMapping
@@ -134,6 +136,16 @@ public class PostController {
return dto;
}
+ private PostDto toDto(Post post, String viewer) {
+ PostDto dto = toDto(post);
+ if (viewer != null) {
+ dto.setSubscribed(subscriptionService.isPostSubscribed(viewer, post.getId()));
+ } else {
+ dto.setSubscribed(false);
+ }
+ return dto;
+ }
+
private CommentDto toCommentDtoWithReplies(Comment comment) {
CommentDto dto = toCommentDto(comment);
List replies = commentService.getReplies(comment.getId()).stream()
@@ -223,6 +235,7 @@ public class PostController {
private List comments;
private List reactions;
private java.util.List participants;
+ private boolean subscribed;
}
@Data
diff --git a/src/main/java/com/openisle/model/NotificationType.java b/src/main/java/com/openisle/model/NotificationType.java
index 9a200d800..ac528e34b 100644
--- a/src/main/java/com/openisle/model/NotificationType.java
+++ b/src/main/java/com/openisle/model/NotificationType.java
@@ -14,6 +14,10 @@ public enum NotificationType {
POST_REVIEWED,
/** A subscribed post received a new comment */
POST_UPDATED,
+ /** Someone subscribed to your post */
+ POST_SUBSCRIBED,
+ /** Someone unsubscribed from your post */
+ POST_UNSUBSCRIBED,
/** Someone you follow published a new post */
FOLLOWED_POST,
/** Someone started following you */
diff --git a/src/main/java/com/openisle/service/SubscriptionService.java b/src/main/java/com/openisle/service/SubscriptionService.java
index bed33bae2..959f11777 100644
--- a/src/main/java/com/openisle/service/SubscriptionService.java
+++ b/src/main/java/com/openisle/service/SubscriptionService.java
@@ -26,6 +26,10 @@ public class SubscriptionService {
PostSubscription ps = new PostSubscription();
ps.setUser(user);
ps.setPost(post);
+ if (!user.getId().equals(post.getAuthor().getId())) {
+ notificationService.createNotification(post.getAuthor(),
+ NotificationType.POST_SUBSCRIBED, post, null, null, user, null);
+ }
return postSubRepo.save(ps);
});
}
@@ -33,7 +37,13 @@ public class SubscriptionService {
public void unsubscribePost(String username, Long postId) {
User user = userRepo.findByUsername(username).orElseThrow();
Post post = postRepo.findById(postId).orElseThrow();
- postSubRepo.findByUserAndPost(user, post).ifPresent(postSubRepo::delete);
+ postSubRepo.findByUserAndPost(user, post).ifPresent(ps -> {
+ postSubRepo.delete(ps);
+ if (!user.getId().equals(post.getAuthor().getId())) {
+ notificationService.createNotification(post.getAuthor(),
+ NotificationType.POST_UNSUBSCRIBED, post, null, null, user, null);
+ }
+ });
}
public void subscribeComment(String username, Long commentId) {
@@ -117,6 +127,15 @@ public class SubscriptionService {
return userSubRepo.findBySubscriberAndTarget(subscriber, target).isPresent();
}
+ public boolean isPostSubscribed(String username, Long postId) {
+ if (username == null || postId == null) {
+ return false;
+ }
+ User user = userRepo.findByUsername(username).orElseThrow();
+ Post post = postRepo.findById(postId).orElseThrow();
+ return postSubRepo.findByUserAndPost(user, post).isPresent();
+ }
+
private Optional findUser(String identifier) {
if (identifier.matches("\\d+")) {
return userRepo.findById(Long.parseLong(identifier));