Удаление сообщений
This commit is contained in:
@@ -32,7 +32,8 @@ public record MessageDetailDto(
|
||||
bool? PollIsMultipleChoice = null,
|
||||
bool? PollIsAnonymous = null,
|
||||
bool? PollIsClosed = null,
|
||||
List<Guid>? UserVotedOptionIds = null
|
||||
List<Guid>? UserVotedOptionIds = null,
|
||||
bool IsDeletedForUser = false
|
||||
);
|
||||
|
||||
public record ReplyToMessageDto(
|
||||
|
||||
+8
-13
@@ -42,10 +42,16 @@ public sealed class DeleteMessagesCommandHandler : ICommandHandler<DeleteMessage
|
||||
|
||||
if (request.DeleteForAll)
|
||||
{
|
||||
// Only message sender can delete for everyone
|
||||
if (message.SenderId == request.UserId)
|
||||
{
|
||||
message.Delete();
|
||||
}
|
||||
else
|
||||
{
|
||||
// If not the sender, just delete for current user
|
||||
message.DeleteForUser(request.UserId);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -55,24 +61,13 @@ public sealed class DeleteMessagesCommandHandler : ICommandHandler<DeleteMessage
|
||||
await _messageRepository.UpdateAsync(message, cancellationToken);
|
||||
}
|
||||
|
||||
if (request.DeleteForAll)
|
||||
{
|
||||
// Notify all clients in the chat about the deletion
|
||||
await _hubContext.Clients.Group(request.ChatId.ToString()).SendAsync("messages_deleted", new
|
||||
{
|
||||
chatId = request.ChatId,
|
||||
messageIds = request.MessageIds,
|
||||
deleteForAll = true
|
||||
deleteForAll = request.DeleteForAll
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
await _hubContext.Clients.User(request.UserId.ToString()).SendAsync("messages_deleted", new
|
||||
{
|
||||
chatId = request.ChatId,
|
||||
messageIds = request.MessageIds,
|
||||
deleteForAll = false
|
||||
});
|
||||
}
|
||||
|
||||
return global::Knot.Shared.Kernel.Result.Success();
|
||||
}
|
||||
|
||||
+12
-7
@@ -3,11 +3,11 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Knot.Contracts.Conversations.Application.Abstractions;
|
||||
using Knot.Contracts.Conversations.Domain;
|
||||
using Knot.Contracts.Messaging.Application.Abstractions;
|
||||
using Knot.Contracts.Messaging.Domain;
|
||||
using Knot.Contracts.Conversations.Application.Abstractions;
|
||||
using Knot.Modules.Conversations.Application.DTOs;
|
||||
using Knot.Contracts.Conversations.Domain;
|
||||
using Knot.Shared.Kernel;
|
||||
using MediatR;
|
||||
|
||||
@@ -115,6 +115,7 @@ internal sealed class GetMessagesQueryHandler : IQueryHandler<GetMessagesQuery,
|
||||
senders.TryGetValue(message.SenderId, out var sender);
|
||||
reactionsByMessage.TryGetValue(message.Id, out var reactions);
|
||||
|
||||
|
||||
Message? replyMsg = null;
|
||||
if (message.ReplyToId.HasValue)
|
||||
{
|
||||
@@ -141,7 +142,7 @@ internal sealed class GetMessagesQueryHandler : IQueryHandler<GetMessagesQuery,
|
||||
replyMsg is MediaMessage mm ? mm.Media.Select(m => new MediaDto(m.Id, m.Type, m.Url, m.Filename, m.Size, m.Duration)).ToList() : new List<MediaDto>(),
|
||||
replySender != null ? new MessageSenderDto(replySender.Id, replySender.Username, replySender.DisplayName, replySender.Avatar) : null
|
||||
) : null,
|
||||
message is TextMessage tm ? tm.Quote : null,
|
||||
(message as TextMessage)?.Quote,
|
||||
message.IsEdited,
|
||||
message.IsDeleted,
|
||||
message.CreatedAt,
|
||||
@@ -154,19 +155,22 @@ internal sealed class GetMessagesQueryHandler : IQueryHandler<GetMessagesQuery,
|
||||
(message as MediaMessage)?.Media.Select(m => new MediaDto(m.Id, m.Type, m.Url, m.Filename, m.Size, m.Duration)).ToList() ?? new List<MediaDto>(),
|
||||
sender != null ? new MessageSenderDto(sender.Id, sender.Username, sender.DisplayName, sender.Avatar) : new MessageSenderDto(message.SenderId, "unknown", "Unknown", null),
|
||||
new List<ReadByDto>(), // ReadBy not implemented in this detailed view yet
|
||||
reactions?.Select(r => {
|
||||
reactions?.Select(r =>
|
||||
{
|
||||
senders.TryGetValue(r.UserId, out var ru);
|
||||
return new MessageReactionDto(r.Id, r.Emoji, r.UserId, ru != null ? new MessageSenderDto(ru.Id, ru.Username, ru.DisplayName, ru.Avatar) : null);
|
||||
}).ToList() ?? new List<MessageReactionDto>(),
|
||||
(message as CallMessage)?.CallType,
|
||||
(message as CallMessage)?.CallStatus,
|
||||
(message as CallMessage)?.Duration,
|
||||
(message as PollMessage)?.Options.Select(o => {
|
||||
(message as PollMessage)?.Options.Select(o =>
|
||||
{
|
||||
var pm = (PollMessage)message;
|
||||
var voters = pm.IsAnonymous == false
|
||||
? pm.Votes
|
||||
.Where(v => v.OptionId == o.Id)
|
||||
.Select(v => {
|
||||
.Select(v =>
|
||||
{
|
||||
senders.TryGetValue(v.UserId, out var vu);
|
||||
return vu != null
|
||||
? new MessageSenderDto(vu.Id, vu.Username, vu.DisplayName, vu.Avatar)
|
||||
@@ -179,7 +183,8 @@ internal sealed class GetMessagesQueryHandler : IQueryHandler<GetMessagesQuery,
|
||||
(message as PollMessage)?.IsMultipleChoice,
|
||||
(message as PollMessage)?.IsAnonymous,
|
||||
(message as PollMessage)?.IsClosed,
|
||||
(message as PollMessage)?.Votes.Where(v => v.UserId == request.UserId).Select(v => v.OptionId).ToList()
|
||||
(message as PollMessage)?.Votes.Where(v => v.UserId == request.UserId).Select(v => v.OptionId).ToList(),
|
||||
message.IsDeletedForUser(request.UserId)
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -84,6 +84,7 @@ export interface Message {
|
||||
storyMediaType?: string | null;
|
||||
isEdited: boolean;
|
||||
isDeleted: boolean;
|
||||
isDeletedForUser?: boolean;
|
||||
scheduledAt?: string | null;
|
||||
createdAt: string;
|
||||
updatedAt?: string;
|
||||
|
||||
@@ -290,7 +290,7 @@ function MessageBubble({
|
||||
};
|
||||
}, [showContext]);
|
||||
|
||||
if (message.isDeleted) {
|
||||
if (message.isDeleted || message.isDeletedForUser) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -491,8 +491,7 @@ function MessageBubble({
|
||||
onContextMenu={handleContextMenu}
|
||||
onDoubleClick={handleReply}
|
||||
title={t('reply') ? `${t('reply')} (Double Click)` : 'Double click to reply'}
|
||||
className={`cursor-pointer max-w-full min-w-[60px] transition-all duration-500 overflow-hidden ${
|
||||
!needsFrame
|
||||
className={`cursor-pointer max-w-full min-w-[60px] transition-all duration-500 overflow-hidden ${!needsFrame
|
||||
? 'p-0 shadow-none border-none bg-transparent'
|
||||
: isMine
|
||||
? 'bubble-sent px-4 py-3 hover:brightness-110'
|
||||
@@ -503,8 +502,7 @@ function MessageBubble({
|
||||
{/* Reply */}
|
||||
{message.replyTo && (
|
||||
<div
|
||||
className={`mb-2 pl-3 py-2 cursor-pointer transition-all -mx-1 px-2 rounded-xl ${
|
||||
isMine ? 'bg-[#1a1a1a] border-l-[3px] border-l-primary hover:bg-[#202020]' : 'bg-white/5 border-l-[3px] border-l-primary hover:bg-white/10'
|
||||
className={`mb-2 pl-3 py-2 cursor-pointer transition-all -mx-1 px-2 rounded-xl ${isMine ? 'bg-[#1a1a1a] border-l-[3px] border-l-primary hover:bg-[#202020]' : 'bg-white/5 border-l-[3px] border-l-primary hover:bg-white/10'
|
||||
}`}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
@@ -565,8 +563,7 @@ function MessageBubble({
|
||||
{/* Story Reply Quote */}
|
||||
{message.storyId && (
|
||||
<div
|
||||
className={`mb-1.5 pl-2.5 py-0.5 border-l-[3px] transition-colors -mx-1 px-1 rounded-sm ${
|
||||
isMine ? 'border-l-white/80 hover:bg-white/10' : 'border-l-knot-500 hover:bg-knot-500/10'
|
||||
className={`mb-1.5 pl-2.5 py-0.5 border-l-[3px] transition-colors -mx-1 px-1 rounded-sm ${isMine ? 'border-l-white/80 hover:bg-white/10' : 'border-l-knot-500 hover:bg-knot-500/10'
|
||||
}`}
|
||||
>
|
||||
<p className={`text-[11px] font-bold uppercase tracking-wider mb-1 ${isMine ? 'text-white/80' : 'text-knot-500/80'}`}>
|
||||
@@ -617,8 +614,7 @@ function MessageBubble({
|
||||
${isSingleGif ? 'max-w-[260px]' : ''}
|
||||
overflow-hidden relative rounded-[1.25rem]
|
||||
`}>
|
||||
<div className={`grid gap-[2px] ${galleryMedia.length > 1 ? 'w-[80vw] sm:w-[380px] md:w-[450px]' : 'w-full'} ${
|
||||
galleryMedia.length === 1 ? 'grid-cols-1' : 'grid-cols-6'
|
||||
<div className={`grid gap-[2px] ${galleryMedia.length > 1 ? 'w-[80vw] sm:w-[380px] md:w-[450px]' : 'w-full'} ${galleryMedia.length === 1 ? 'grid-cols-1' : 'grid-cols-6'
|
||||
}`}>
|
||||
{galleryMedia.map((m, idx) => {
|
||||
const gif = isMediaGif(m);
|
||||
@@ -1071,8 +1067,7 @@ function MessageBubble({
|
||||
<button
|
||||
key={emoji}
|
||||
onClick={(e) => { e.stopPropagation(); handleReaction(emoji); }}
|
||||
className={`flex items-center gap-2 px-2.5 py-1.5 rounded-[10px] transition-all border ${
|
||||
data.isMine
|
||||
className={`flex items-center gap-2 px-2.5 py-1.5 rounded-[10px] transition-all border ${data.isMine
|
||||
? 'bg-primary/20 border-primary text-white shadow-lg'
|
||||
: 'bg-[#201F1F] border-white/5 text-zinc-300 hover:bg-[#2a2a2a]'
|
||||
} shadow-md group/react`}
|
||||
|
||||
Reference in New Issue
Block a user