diff --git a/backend/src/main/java/com/openisle/config/ChannelInitializer.java b/backend/src/main/java/com/openisle/config/ChannelInitializer.java new file mode 100644 index 000000000..ba034b49d --- /dev/null +++ b/backend/src/main/java/com/openisle/config/ChannelInitializer.java @@ -0,0 +1,32 @@ +package com.openisle.config; + +import com.openisle.model.MessageConversation; +import com.openisle.repository.MessageConversationRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class ChannelInitializer implements CommandLineRunner { + private final MessageConversationRepository conversationRepository; + + @Override + public void run(String... args) { + if (conversationRepository.countByChannelTrue() == 0) { + MessageConversation chat = new MessageConversation(); + chat.setChannel(true); + chat.setName("吹水群"); + chat.setDescription("吹水聊天"); + chat.setAvatar("https://openisle-1307107697.cos.accelerate.myqcloud.com/dynamic_assert/32647273e2334d14adfd4a6ce9db0643.jpeg"); + conversationRepository.save(chat); + + MessageConversation tech = new MessageConversation(); + tech.setChannel(true); + tech.setName("技术讨论群"); + tech.setDescription("讨论技术相关话题"); + tech.setAvatar("https://openisle-1307107697.cos.accelerate.myqcloud.com/dynamic_assert/5edde9a5864e471caa32491dbcdaa8b2.png"); + conversationRepository.save(tech); + } + } +} diff --git a/backend/src/main/java/com/openisle/config/SecurityConfig.java b/backend/src/main/java/com/openisle/config/SecurityConfig.java index 91fb45027..bb6081aac 100644 --- a/backend/src/main/java/com/openisle/config/SecurityConfig.java +++ b/backend/src/main/java/com/openisle/config/SecurityConfig.java @@ -121,6 +121,7 @@ public class SecurityConfig { .requestMatchers(HttpMethod.GET, "/api/reaction-types").permitAll() .requestMatchers(HttpMethod.GET, "/api/activities/**").permitAll() .requestMatchers(HttpMethod.GET, "/api/sitemap.xml").permitAll() + .requestMatchers(HttpMethod.GET, "/api/channels").permitAll() .requestMatchers(HttpMethod.GET, "/api/rss").permitAll() .requestMatchers(HttpMethod.GET, "/api/point-goods").permitAll() .requestMatchers(HttpMethod.POST, "/api/point-goods").permitAll() @@ -156,7 +157,7 @@ public class SecurityConfig { uri.startsWith("/api/search") || uri.startsWith("/api/users") || uri.startsWith("/api/reaction-types") || uri.startsWith("/api/config") || uri.startsWith("/api/activities") || uri.startsWith("/api/push/public-key") || - uri.startsWith("/api/point-goods") || + uri.startsWith("/api/point-goods") || uri.startsWith("/api/channels") || uri.startsWith("/api/sitemap.xml") || uri.startsWith("/api/medals") || uri.startsWith("/api/rss")); diff --git a/backend/src/main/java/com/openisle/controller/ChannelController.java b/backend/src/main/java/com/openisle/controller/ChannelController.java new file mode 100644 index 000000000..03b5a6952 --- /dev/null +++ b/backend/src/main/java/com/openisle/controller/ChannelController.java @@ -0,0 +1,42 @@ +package com.openisle.controller; + +import com.openisle.dto.ChannelDto; +import com.openisle.model.User; +import com.openisle.repository.UserRepository; +import com.openisle.service.ChannelService; +import com.openisle.service.MessageService; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/channels") +@RequiredArgsConstructor +public class ChannelController { + private final ChannelService channelService; + private final MessageService messageService; + private final UserRepository userRepository; + + private Long getCurrentUserId(Authentication auth) { + User user = userRepository.findByUsername(auth.getName()) + .orElseThrow(() -> new IllegalArgumentException("User not found")); + return user.getId(); + } + + @GetMapping + public List listChannels(Authentication auth) { + return channelService.listChannels(getCurrentUserId(auth)); + } + + @PostMapping("/{channelId}/join") + public ChannelDto joinChannel(@PathVariable Long channelId, Authentication auth) { + return channelService.joinChannel(channelId, getCurrentUserId(auth)); + } + + @GetMapping("/unread-count") + public long unreadCount(Authentication auth) { + return messageService.getUnreadChannelCount(getCurrentUserId(auth)); + } +} diff --git a/backend/src/main/java/com/openisle/controller/MessageController.java b/backend/src/main/java/com/openisle/controller/MessageController.java index c599fc785..bf2b2b1ad 100644 --- a/backend/src/main/java/com/openisle/controller/MessageController.java +++ b/backend/src/main/java/com/openisle/controller/MessageController.java @@ -59,6 +59,14 @@ public class MessageController { return ResponseEntity.ok(toDto(message)); } + @PostMapping("/conversations/{conversationId}/messages") + public ResponseEntity sendMessageToConversation(@PathVariable Long conversationId, + @RequestBody ChannelMessageRequest req, + Authentication auth) { + Message message = messageService.sendMessageToConversation(getCurrentUserId(auth), conversationId, req.getContent()); + return ResponseEntity.ok(toDto(message)); + } + @PostMapping("/conversations/{conversationId}/read") public ResponseEntity markAsRead(@PathVariable Long conversationId, Authentication auth) { messageService.markConversationAsRead(conversationId, getCurrentUserId(auth)); @@ -114,4 +122,16 @@ public class MessageController { this.content = content; } } + + static class ChannelMessageRequest { + private String content; + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + } } \ No newline at end of file diff --git a/backend/src/main/java/com/openisle/dto/ChannelDto.java b/backend/src/main/java/com/openisle/dto/ChannelDto.java new file mode 100644 index 000000000..5c3d20d7f --- /dev/null +++ b/backend/src/main/java/com/openisle/dto/ChannelDto.java @@ -0,0 +1,17 @@ +package com.openisle.dto; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class ChannelDto { + private Long id; + private String name; + private String description; + private String avatar; + private MessageDto lastMessage; + private long memberCount; + private boolean joined; + private long unreadCount; +} diff --git a/backend/src/main/java/com/openisle/dto/ConversationDetailDto.java b/backend/src/main/java/com/openisle/dto/ConversationDetailDto.java index 96f548c45..6b0c9e97c 100644 --- a/backend/src/main/java/com/openisle/dto/ConversationDetailDto.java +++ b/backend/src/main/java/com/openisle/dto/ConversationDetailDto.java @@ -8,6 +8,9 @@ import java.util.List; @Data public class ConversationDetailDto { private Long id; + private String name; + private boolean channel; + private String avatar; private List participants; private Page messages; } \ No newline at end of file diff --git a/backend/src/main/java/com/openisle/dto/ConversationDto.java b/backend/src/main/java/com/openisle/dto/ConversationDto.java index 17796f2c9..fdc83e639 100644 --- a/backend/src/main/java/com/openisle/dto/ConversationDto.java +++ b/backend/src/main/java/com/openisle/dto/ConversationDto.java @@ -10,6 +10,9 @@ import java.util.List; @Setter public class ConversationDto { private Long id; + private String name; + private boolean channel; + private String avatar; private MessageDto lastMessage; private List participants; private LocalDateTime createdAt; diff --git a/backend/src/main/java/com/openisle/model/MessageConversation.java b/backend/src/main/java/com/openisle/model/MessageConversation.java index 9f9c94971..dfcda4e0c 100644 --- a/backend/src/main/java/com/openisle/model/MessageConversation.java +++ b/backend/src/main/java/com/openisle/model/MessageConversation.java @@ -20,6 +20,18 @@ public class MessageConversation { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + // Indicates whether this conversation represents a public channel + @Column(nullable = false) + private boolean channel = false; + + // Channel metadata + private String name; + + @Column(columnDefinition = "TEXT") + private String description; + + private String avatar; + @CreationTimestamp @Column(nullable = false, updatable = false) private LocalDateTime createdAt; diff --git a/backend/src/main/java/com/openisle/repository/MessageConversationRepository.java b/backend/src/main/java/com/openisle/repository/MessageConversationRepository.java index ef8bacb6c..d260c4f38 100644 --- a/backend/src/main/java/com/openisle/repository/MessageConversationRepository.java +++ b/backend/src/main/java/com/openisle/repository/MessageConversationRepository.java @@ -1,23 +1,22 @@ package com.openisle.repository; import com.openisle.model.MessageConversation; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - import com.openisle.model.User; -import java.util.List; - +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; -import java.util.Optional; -import com.openisle.model.User; import java.util.List; @Repository public interface MessageConversationRepository extends JpaRepository { - @Query("SELECT c FROM MessageConversation c JOIN c.participants p1 JOIN c.participants p2 WHERE p1.user = :user1 AND p2.user = :user2") - Optional findConversationByUsers(@Param("user1") User user1, @Param("user2") User user2); + @Query("SELECT c FROM MessageConversation c " + + "WHERE c.channel = false AND size(c.participants) = 2 " + + "AND EXISTS (SELECT 1 FROM c.participants p1 WHERE p1.user = :user1) " + + "AND EXISTS (SELECT 1 FROM c.participants p2 WHERE p2.user = :user2) " + + "ORDER BY c.createdAt DESC") + List findConversationsByUsers(@Param("user1") User user1, @Param("user2") User user2); @Query("SELECT DISTINCT c FROM MessageConversation c " + "JOIN c.participants p " + @@ -28,4 +27,8 @@ public interface MessageConversationRepository extends JpaRepository findConversationsByUserIdOrderByLastMessageDesc(@Param("userId") Long userId); -} \ No newline at end of file + + List findByChannelTrue(); + + long countByChannelTrue(); +} diff --git a/backend/src/main/java/com/openisle/service/ChannelService.java b/backend/src/main/java/com/openisle/service/ChannelService.java new file mode 100644 index 000000000..62b1c392b --- /dev/null +++ b/backend/src/main/java/com/openisle/service/ChannelService.java @@ -0,0 +1,98 @@ +package com.openisle.service; + +import com.openisle.dto.ChannelDto; +import com.openisle.dto.MessageDto; +import com.openisle.dto.UserSummaryDto; +import com.openisle.model.Message; +import com.openisle.model.MessageConversation; +import com.openisle.model.MessageParticipant; +import com.openisle.model.User; +import com.openisle.repository.MessageConversationRepository; +import com.openisle.repository.MessageParticipantRepository; +import com.openisle.repository.MessageRepository; +import com.openisle.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class ChannelService { + private final MessageConversationRepository conversationRepository; + private final MessageParticipantRepository participantRepository; + private final MessageRepository messageRepository; + private final UserRepository userRepository; + + @Transactional(readOnly = true) + public List listChannels(Long userId) { + List channels = conversationRepository.findByChannelTrue(); + return channels.stream().map(c -> toDto(c, userId)).collect(Collectors.toList()); + } + + @Transactional + public ChannelDto joinChannel(Long channelId, Long userId) { + MessageConversation channel = conversationRepository.findById(channelId) + .orElseThrow(() -> new IllegalArgumentException("Channel not found")); + User user = userRepository.findById(userId) + .orElseThrow(() -> new IllegalArgumentException("User not found")); + participantRepository.findByConversationIdAndUserId(channelId, userId) + .orElseGet(() -> { + MessageParticipant p = new MessageParticipant(); + p.setConversation(channel); + p.setUser(user); + MessageParticipant saved = participantRepository.save(p); + channel.getParticipants().add(saved); + return saved; + }); + return toDto(channel, userId); + } + + private ChannelDto toDto(MessageConversation channel, Long userId) { + ChannelDto dto = new ChannelDto(); + dto.setId(channel.getId()); + dto.setName(channel.getName()); + dto.setDescription(channel.getDescription()); + dto.setAvatar(channel.getAvatar()); + if (channel.getLastMessage() != null) { + dto.setLastMessage(toMessageDto(channel.getLastMessage())); + } + dto.setMemberCount(channel.getParticipants().size()); + boolean joined = channel.getParticipants().stream() + .anyMatch(p -> p.getUser().getId().equals(userId)); + dto.setJoined(joined); + if (joined) { + MessageParticipant participant = channel.getParticipants().stream() + .filter(p -> p.getUser().getId().equals(userId)) + .findFirst().orElse(null); + LocalDateTime lastRead = participant.getLastReadAt() == null + ? LocalDateTime.of(1970, 1, 1, 0, 0) + : participant.getLastReadAt(); + long unread = messageRepository + .countByConversationIdAndCreatedAtAfterAndSenderIdNot(channel.getId(), lastRead, userId); + dto.setUnreadCount(unread); + } else { + dto.setUnreadCount(0); + } + return dto; + } + + private MessageDto toMessageDto(Message message) { + MessageDto dto = new MessageDto(); + dto.setId(message.getId()); + dto.setContent(message.getContent()); + dto.setConversationId(message.getConversation().getId()); + dto.setCreatedAt(message.getCreatedAt()); + + UserSummaryDto userDto = new UserSummaryDto(); + userDto.setId(message.getSender().getId()); + userDto.setUsername(message.getSender().getUsername()); + userDto.setAvatar(message.getSender().getAvatar()); + dto.setSender(userDto); + + return dto; + } +} diff --git a/backend/src/main/java/com/openisle/service/MessageService.java b/backend/src/main/java/com/openisle/service/MessageService.java index 3e668742e..f4e1d20cd 100644 --- a/backend/src/main/java/com/openisle/service/MessageService.java +++ b/backend/src/main/java/com/openisle/service/MessageService.java @@ -82,6 +82,52 @@ public class MessageService { return message; } + @Transactional + public Message sendMessageToConversation(Long senderId, Long conversationId, String content) { + User sender = userRepository.findById(senderId) + .orElseThrow(() -> new IllegalArgumentException("Sender not found")); + MessageConversation conversation = conversationRepository.findById(conversationId) + .orElseThrow(() -> new IllegalArgumentException("Conversation not found")); + + // Join the conversation if not already a participant (useful for channels) + participantRepository.findByConversationIdAndUserId(conversationId, senderId) + .orElseGet(() -> { + MessageParticipant p = new MessageParticipant(); + p.setConversation(conversation); + p.setUser(sender); + return participantRepository.save(p); + }); + + Message message = new Message(); + message.setConversation(conversation); + message.setSender(sender); + message.setContent(content); + message = messageRepository.save(message); + + conversation.setLastMessage(message); + conversationRepository.save(conversation); + + MessageDto messageDto = toDto(message); + String conversationDestination = "/topic/conversation/" + conversation.getId(); + messagingTemplate.convertAndSend(conversationDestination, messageDto); + + // Notify all participants except sender for updates + for (MessageParticipant participant : conversation.getParticipants()) { + if (participant.getUser().getId().equals(senderId)) continue; + String userDestination = "/topic/user/" + participant.getUser().getId() + "/messages"; + messagingTemplate.convertAndSend(userDestination, messageDto); + + long unreadCount = getUnreadMessageCount(participant.getUser().getId()); + String username = participant.getUser().getUsername(); + messagingTemplate.convertAndSendToUser(username, "/queue/unread-count", unreadCount); + + long channelUnread = getUnreadChannelCount(participant.getUser().getId()); + messagingTemplate.convertAndSendToUser(username, "/queue/channel-unread", channelUnread); + } + + return message; + } + private MessageDto toDto(Message message) { MessageDto dto = new MessageDto(); dto.setId(message.getId()); @@ -108,7 +154,8 @@ public class MessageService { private MessageConversation findOrCreateConversation(User user1, User user2) { log.info("Searching for existing conversation between {} and {}", user1.getUsername(), user2.getUsername()); - return conversationRepository.findConversationByUsers(user1, user2) + return conversationRepository.findConversationsByUsers(user1, user2).stream() + .findFirst() .orElseGet(() -> { log.info("No existing conversation found. Creating a new one."); MessageConversation conversation = new MessageConversation(); @@ -134,12 +181,18 @@ public class MessageService { @Transactional(readOnly = true) public List getConversations(Long userId) { List conversations = conversationRepository.findConversationsByUserIdOrderByLastMessageDesc(userId); - return conversations.stream().map(c -> toDto(c, userId)).collect(Collectors.toList()); + return conversations.stream() + .filter(c -> !c.isChannel()) + .map(c -> toDto(c, userId)) + .collect(Collectors.toList()); } private ConversationDto toDto(MessageConversation conversation, Long userId) { ConversationDto dto = new ConversationDto(); dto.setId(conversation.getId()); + dto.setChannel(conversation.isChannel()); + dto.setName(conversation.getName()); + dto.setAvatar(conversation.getAvatar()); dto.setCreatedAt(conversation.getCreatedAt()); if (conversation.getLastMessage() != null) { dto.setLastMessage(toDto(conversation.getLastMessage())); @@ -189,6 +242,9 @@ public class MessageService { ConversationDetailDto detailDto = new ConversationDetailDto(); detailDto.setId(conversation.getId()); + detailDto.setName(conversation.getName()); + detailDto.setChannel(conversation.isChannel()); + detailDto.setAvatar(conversation.getAvatar()); detailDto.setParticipants(participants); detailDto.setMessages(messageDtoPage); @@ -208,10 +264,26 @@ public class MessageService { List participations = participantRepository.findByUserId(userId); long totalUnreadCount = 0; for (MessageParticipant p : participations) { + if (p.getConversation().isChannel()) continue; LocalDateTime lastRead = p.getLastReadAt() == null ? LocalDateTime.of(1970, 1, 1, 0, 0) : p.getLastReadAt(); // 只计算别人发送给当前用户的未读消息 totalUnreadCount += messageRepository.countByConversationIdAndCreatedAtAfterAndSenderIdNot(p.getConversation().getId(), lastRead, userId); } return totalUnreadCount; } + + @Transactional(readOnly = true) + public long getUnreadChannelCount(Long userId) { + List participations = participantRepository.findByUserId(userId); + long unreadChannelCount = 0; + for (MessageParticipant p : participations) { + if (!p.getConversation().isChannel()) continue; + LocalDateTime lastRead = p.getLastReadAt() == null ? LocalDateTime.of(1970, 1, 1, 0, 0) : p.getLastReadAt(); + long unread = messageRepository.countByConversationIdAndCreatedAtAfterAndSenderIdNot(p.getConversation().getId(), lastRead, userId); + if (unread > 0) { + unreadChannelCount++; + } + } + return unreadChannelCount; + } } \ No newline at end of file diff --git a/frontend_nuxt/assets/global.css b/frontend_nuxt/assets/global.css index edfad14a8..4f7a3a895 100644 --- a/frontend_nuxt/assets/global.css +++ b/frontend_nuxt/assets/global.css @@ -18,7 +18,7 @@ --background-color-blur: rgba(255, 255, 255, 0.57); --menu-border-color: lightgray; --normal-border-color: lightgray; - --menu-selected-background-color: rgba(208, 250, 255, 0.659); + --menu-selected-background-color: rgba(228, 228, 228, 0.884); --menu-text-color: black; --scroller-background-color: rgba(130, 175, 180, 0.5); /* --normal-background-color: rgb(241, 241, 241); */ diff --git a/frontend_nuxt/components/BaseTimeline.vue b/frontend_nuxt/components/BaseTimeline.vue index 41d6064a2..dac7e55cf 100644 --- a/frontend_nuxt/components/BaseTimeline.vue +++ b/frontend_nuxt/components/BaseTimeline.vue @@ -41,6 +41,12 @@ export default { margin-top: 10px; } +.timeline-item:hover { + background-color: var(--menu-selected-background-color); + transition: background-color 0.2s; + border-radius: 10px; +} + .timeline-icon { position: sticky; top: 0; diff --git a/frontend_nuxt/components/HeaderComponent.vue b/frontend_nuxt/components/HeaderComponent.vue index 5963eb5bc..37a824c93 100644 --- a/frontend_nuxt/components/HeaderComponent.vue +++ b/frontend_nuxt/components/HeaderComponent.vue @@ -6,7 +6,10 @@ - + {{ unreadMessageCount }} + @@ -85,6 +89,7 @@ import ToolTip from '~/components/ToolTip.vue' import SearchDropdown from '~/components/SearchDropdown.vue' import { authState, clearToken, loadCurrentUser } from '~/utils/auth' import { useUnreadCount } from '~/composables/useUnreadCount' +import { useChannelsUnreadCount } from '~/composables/useChannelsUnreadCount' import { useIsMobile } from '~/utils/screen' import { themeState, cycleTheme, ThemeMode } from '~/utils/theme' import { toast } from '~/main' @@ -103,6 +108,7 @@ const props = defineProps({ const isLogin = computed(() => authState.loggedIn) const isMobile = useIsMobile() const { count: unreadMessageCount, fetchUnreadCount } = useUnreadCount() +const { hasUnread: hasChannelUnread, fetchChannelUnread } = useChannelsUnreadCount() const avatar = ref('') const showSearch = ref(false) const searchDropdown = ref(null) @@ -227,8 +233,10 @@ onMounted(async () => { } const updateUnread = async () => { if (authState.loggedIn) { - // Initialize the unread count composable fetchUnreadCount() + fetchChannelUnread() + } else { + fetchChannelUnread() } } @@ -413,6 +421,16 @@ onMounted(async () => { box-sizing: border-box; } +.unread-dot { + position: absolute; + top: -2px; + right: -4px; + width: 8px; + height: 8px; + border-radius: 50%; + background-color: #ff4d4f; +} + .rss-icon { animation: rss-glow 2s 3; } diff --git a/frontend_nuxt/components/SearchPersonDropdown.vue b/frontend_nuxt/components/SearchPersonDropdown.vue new file mode 100644 index 000000000..1ff229bad --- /dev/null +++ b/frontend_nuxt/components/SearchPersonDropdown.vue @@ -0,0 +1,198 @@ + + + + + diff --git a/frontend_nuxt/composables/useChannelsUnreadCount.js b/frontend_nuxt/composables/useChannelsUnreadCount.js new file mode 100644 index 000000000..6db3d0c64 --- /dev/null +++ b/frontend_nuxt/composables/useChannelsUnreadCount.js @@ -0,0 +1,92 @@ +import { ref, computed, watch } from 'vue' +import { useWebSocket } from './useWebSocket' +import { getToken } from '~/utils/auth' + +const count = ref(0) +let isInitialized = false +let wsSubscription = null + +export function useChannelsUnreadCount() { + const config = useRuntimeConfig() + const API_BASE_URL = config.public.apiBaseUrl + const { subscribe, isConnected, connect } = useWebSocket() + + const fetchChannelUnread = async () => { + const token = getToken() + if (!token) { + count.value = 0 + return + } + try { + const response = await fetch(`${API_BASE_URL}/api/channels/unread-count`, { + headers: { Authorization: `Bearer ${token}` }, + }) + if (response.ok) { + const data = await response.json() + count.value = data + } + } catch (e) { + console.error('Failed to fetch channel unread count:', e) + } + } + + const initialize = () => { + const token = getToken() + if (!token) { + count.value = 0 + return + } + fetchChannelUnread() + if (!isConnected.value) { + connect(token) + } + setupWebSocketListener() + } + + const setupWebSocketListener = () => { + if (!wsSubscription) { + watch( + isConnected, + (newValue) => { + if (newValue && !wsSubscription) { + wsSubscription = subscribe('/user/queue/channel-unread', (message) => { + const unread = parseInt(message.body, 10) + if (!isNaN(unread)) { + count.value = unread + } + }) + } + }, + { immediate: true }, + ) + } + } + + const setFromList = (channels) => { + count.value = Array.isArray(channels) ? channels.filter((c) => c.unreadCount > 0).length : 0 + } + + const hasUnread = computed(() => count.value > 0) + + const token = getToken() + if (token) { + if (!isInitialized) { + isInitialized = true + initialize() + } else { + fetchChannelUnread() + if (!isConnected.value) { + connect(token) + } + setupWebSocketListener() + } + } + + return { + count, + hasUnread, + fetchChannelUnread, + initialize, + setFromList, + } +} diff --git a/frontend_nuxt/composables/useWebSocket.js b/frontend_nuxt/composables/useWebSocket.js index 4fc74a5ab..c07d3a4ea 100644 --- a/frontend_nuxt/composables/useWebSocket.js +++ b/frontend_nuxt/composables/useWebSocket.js @@ -55,7 +55,10 @@ const subscribe = (destination, callback) => { try { const subscription = client.value.subscribe(destination, (message) => { try { - if (destination.includes('/queue/unread-count')) { + if ( + destination.includes('/queue/unread-count') || + destination.includes('/queue/channel-unread') + ) { callback(message) } else { const parsedMessage = JSON.parse(message.body) diff --git a/frontend_nuxt/pages/message-box/[id].vue b/frontend_nuxt/pages/message-box/[id].vue index af0086974..e91eca09f 100644 --- a/frontend_nuxt/pages/message-box/[id].vue +++ b/frontend_nuxt/pages/message-box/[id].vue @@ -1,31 +1,47 @@