Add, edit, delete, and resend messages.
This commit is contained in:
@@ -37,6 +37,11 @@ const PAGE_SIZE = 50;
|
||||
const IMAGE_MAX_EDGE = 1600;
|
||||
const IMAGE_WEBP_QUALITY = 0.82;
|
||||
const MAX_UPLOAD_BYTES = 40 * 1024 * 1024;
|
||||
const DELETED_CONTENT = 'این پیام حذف شد';
|
||||
|
||||
function isDeletedContent(content: string): boolean {
|
||||
return /^(?:\[?\s*deleted\s*\]?|این پیام حذف شد|پیام حذف شد)$/i.test(content.trim());
|
||||
}
|
||||
|
||||
let socket: WebSocket | null = null;
|
||||
let sessionCache: ConversationSessionDto | null = null;
|
||||
@@ -305,7 +310,7 @@ function normalizeIncoming(
|
||||
base.deleted ||
|
||||
base.is_deleted ||
|
||||
base.isDeleted ||
|
||||
/^(?:\[?\s*deleted\s*\]?|این پیام حذف شد|پیام حذف شد)$/i.test(content),
|
||||
isDeletedContent(content),
|
||||
);
|
||||
const edited = Boolean(base.edited || base.is_edited || base.isEdited || base.edited_at);
|
||||
|
||||
@@ -315,7 +320,7 @@ function normalizeIncoming(
|
||||
senderId: String(
|
||||
base.sender_id ?? base.user_id ?? base.from_user_id ?? raw.sender_id ?? '',
|
||||
),
|
||||
content: deleted ? 'این پیام حذف شد' : content,
|
||||
content: deleted ? DELETED_CONTENT : content,
|
||||
type: deleted ? 'text' : type,
|
||||
createdAt: String(
|
||||
base.created_at ?? base.createdAt ?? raw.created_at ?? new Date().toISOString(),
|
||||
@@ -339,20 +344,33 @@ function mergeMessages(incoming: ChatMessage, list: ChatMessage[]): ChatMessage[
|
||||
...prev,
|
||||
...incoming,
|
||||
optimistic: false,
|
||||
type: inferMediaType(
|
||||
prev.type === 'voice' || incoming.type === 'voice'
|
||||
? 'voice'
|
||||
: prev.type === 'video' || incoming.type === 'video'
|
||||
? 'video'
|
||||
: incoming.type,
|
||||
incoming.content || prev.content,
|
||||
incoming.fileName || prev.fileName,
|
||||
),
|
||||
type:
|
||||
prev.deleted || incoming.deleted
|
||||
? 'text'
|
||||
: inferMediaType(
|
||||
prev.type === 'voice' || incoming.type === 'voice'
|
||||
? 'voice'
|
||||
: prev.type === 'video' || incoming.type === 'video'
|
||||
? 'video'
|
||||
: incoming.type,
|
||||
incoming.content || prev.content,
|
||||
incoming.fileName || prev.fileName,
|
||||
),
|
||||
content:
|
||||
prev.deleted || incoming.deleted
|
||||
? DELETED_CONTENT
|
||||
: incoming.content || prev.content,
|
||||
localPreviewUrl: incoming.localPreviewUrl ?? prev.localPreviewUrl,
|
||||
mediaUrl: incoming.mediaUrl || prev.mediaUrl,
|
||||
fileName: incoming.fileName || prev.fileName,
|
||||
mediaUrl:
|
||||
prev.deleted || incoming.deleted
|
||||
? undefined
|
||||
: incoming.mediaUrl || prev.mediaUrl,
|
||||
fileName:
|
||||
prev.deleted || incoming.deleted
|
||||
? undefined
|
||||
: incoming.fileName || prev.fileName,
|
||||
edited: incoming.edited || prev.edited,
|
||||
deleted: incoming.deleted || prev.deleted,
|
||||
deleted: prev.deleted || incoming.deleted,
|
||||
delivery:
|
||||
incoming.delivery === 'read' || prev.delivery === 'read'
|
||||
? 'read'
|
||||
@@ -520,6 +538,11 @@ function applyEditedMessage(payload: Record<string, unknown>) {
|
||||
if (!id) return;
|
||||
const content = String(base.content ?? base.text ?? base.body ?? '').trim();
|
||||
if (!content) return;
|
||||
// Delete-for-everyone is synced via edit tombstone (native delete is not broadcast by chat WS)
|
||||
if (isDeletedContent(content)) {
|
||||
applyDeletedMessage({ id });
|
||||
return;
|
||||
}
|
||||
messages.value = messages.value.map((m) =>
|
||||
m.id === id
|
||||
? {
|
||||
@@ -546,7 +569,7 @@ function applyDeletedMessage(payload: Record<string, unknown>) {
|
||||
m.id === id
|
||||
? {
|
||||
...m,
|
||||
content: 'این پیام حذف شد',
|
||||
content: DELETED_CONTENT,
|
||||
type: 'text',
|
||||
deleted: true,
|
||||
edited: false,
|
||||
@@ -1044,22 +1067,59 @@ export function useOrgChat() {
|
||||
|
||||
applyDeletedMessage({ id: messageId });
|
||||
|
||||
// Chat WS does not reliably broadcast type:delete — sync via edit (proven channel).
|
||||
socket.send(
|
||||
JSON.stringify({
|
||||
type: 'edit',
|
||||
id: messageId,
|
||||
room_id: roomId,
|
||||
content: DELETED_CONTENT,
|
||||
}),
|
||||
);
|
||||
// Best-effort native delete for backends that support it
|
||||
socket.send(
|
||||
JSON.stringify({
|
||||
type: 'delete',
|
||||
id: messageId,
|
||||
message_id: messageId,
|
||||
room_id: roomId,
|
||||
}),
|
||||
);
|
||||
|
||||
emitRoomLive({
|
||||
roomId,
|
||||
preview: 'این پیام حذف شد',
|
||||
preview: DELETED_CONTENT,
|
||||
at: new Date().toISOString(),
|
||||
fromSelf: true,
|
||||
});
|
||||
}
|
||||
|
||||
async function deleteMessages(roomId: string, messageIds: string[]) {
|
||||
const ids = [...new Set(messageIds.filter(Boolean))];
|
||||
for (const id of ids) {
|
||||
await deleteMessage(roomId, id);
|
||||
}
|
||||
}
|
||||
|
||||
async function forwardMessages(targetRoomId: string, sourceMessages: ChatMessage[]) {
|
||||
const roomId = targetRoomId.trim();
|
||||
if (!roomId) return;
|
||||
await connectSocket();
|
||||
for (const msg of sourceMessages) {
|
||||
if (!msg || msg.deleted || msg.optimistic) continue;
|
||||
if (msg.type === 'text') {
|
||||
const text = msg.content.trim();
|
||||
if (text && !isDeletedContent(text)) await sendText(roomId, text);
|
||||
continue;
|
||||
}
|
||||
const path = String(msg.content || '').trim();
|
||||
if (!path) continue;
|
||||
await sendMediaPath(roomId, path, msg.type, {
|
||||
fileName: msg.fileName,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function clearActiveRoom() {
|
||||
activeRoomId.value = '';
|
||||
messages.value = [];
|
||||
@@ -1083,6 +1143,8 @@ export function useOrgChat() {
|
||||
sendFile,
|
||||
editMessage,
|
||||
deleteMessage,
|
||||
deleteMessages,
|
||||
forwardMessages,
|
||||
sendRead,
|
||||
ensureConnected,
|
||||
setConversationsPageOpen,
|
||||
|
||||
Reference in New Issue
Block a user