Editing correction (in progress)
This commit is contained in:
@@ -62,6 +62,10 @@ export class ConversationsController {
|
||||
@CurrentUser() user: { userId: string; organizationId: string; role: string },
|
||||
@Query('externalRoomId') externalRoomId: string,
|
||||
) {
|
||||
// بدون roomId → همه اتاقهای سازمان (لیست گفتگو)
|
||||
if (!externalRoomId?.trim()) {
|
||||
return this.conversations.listTypingAll(user.userId, user.organizationId, user.role);
|
||||
}
|
||||
return this.conversations.listTyping(
|
||||
user.userId,
|
||||
user.organizationId,
|
||||
|
||||
@@ -98,6 +98,61 @@ export class ConversationsService {
|
||||
}));
|
||||
}
|
||||
|
||||
/** typing همه اتاقهای سازمان (برای پیشنمایش لیست گفتگو) */
|
||||
async listTypingAll(
|
||||
userId: string,
|
||||
organizationId: string,
|
||||
role: string,
|
||||
): Promise<
|
||||
Array<{ externalRoomId: string; userId: string; fullName: string; chatUserId: string }>
|
||||
> {
|
||||
await this.loadAccess(userId, organizationId, role);
|
||||
const now = Date.now();
|
||||
this.sweepTypingPresence(now);
|
||||
const orgPrefix = `${organizationId}|`;
|
||||
const byRoom = new Map<string, string[]>();
|
||||
for (const [k, at] of this.typingPresence) {
|
||||
if (!k.startsWith(orgPrefix)) continue;
|
||||
if (now - at > 4000) {
|
||||
this.typingPresence.delete(k);
|
||||
continue;
|
||||
}
|
||||
const rest = k.slice(orgPrefix.length);
|
||||
const sep = rest.lastIndexOf('|');
|
||||
if (sep <= 0) continue;
|
||||
const roomId = rest.slice(0, sep);
|
||||
const uid = rest.slice(sep + 1);
|
||||
if (!roomId || !uid || uid === userId) continue;
|
||||
const list = byRoom.get(roomId) || [];
|
||||
list.push(uid);
|
||||
byRoom.set(roomId, list);
|
||||
}
|
||||
if (!byRoom.size) return [];
|
||||
const allIds = [...new Set([...byRoom.values()].flat())];
|
||||
const users = await this.prisma.user.findMany({
|
||||
where: { id: { in: allIds } },
|
||||
select: { id: true, fullName: true },
|
||||
});
|
||||
const nameById = new Map(users.map((u) => [u.id, u.fullName || 'کاربر']));
|
||||
const out: Array<{
|
||||
externalRoomId: string;
|
||||
userId: string;
|
||||
fullName: string;
|
||||
chatUserId: string;
|
||||
}> = [];
|
||||
for (const [roomId, ids] of byRoom) {
|
||||
for (const uid of ids) {
|
||||
out.push({
|
||||
externalRoomId: roomId,
|
||||
userId: uid,
|
||||
fullName: nameById.get(uid) || 'کاربر',
|
||||
chatUserId: buildChatUserId(organizationId, uid),
|
||||
});
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
async getAccess(userId: string, organizationId: string, role: string): Promise<ConversationAccessDto> {
|
||||
const access = await this.loadAccess(userId, organizationId, role);
|
||||
return access;
|
||||
|
||||
@@ -267,16 +267,8 @@ function applyTypingFromServer(
|
||||
at: now,
|
||||
}))
|
||||
.filter((t) => t.userId && t.userId !== sessionCache?.chatUserId);
|
||||
const prev = typingMap.value[rid] || [];
|
||||
// نگه داشتن رویدادهای خیلی تازه از WS اگر سرور هنوز خالی است
|
||||
const merged = new Map<string, { userId: string; name?: string; at: number }>();
|
||||
for (const t of prev) {
|
||||
if (now - t.at < 4000) merged.set(t.userId, t);
|
||||
}
|
||||
for (const t of list) merged.set(t.userId, t);
|
||||
const nextList = [...merged.values()];
|
||||
if (!nextList.length) {
|
||||
if (prev.length) {
|
||||
if (!list.length) {
|
||||
if (typingMap.value[rid]) {
|
||||
const next = { ...typingMap.value };
|
||||
delete next[rid];
|
||||
typingMap.value = next;
|
||||
@@ -284,7 +276,31 @@ function applyTypingFromServer(
|
||||
return;
|
||||
}
|
||||
startTypingSweep();
|
||||
typingMap.value = { ...typingMap.value, [rid]: nextList };
|
||||
typingMap.value = { ...typingMap.value, [rid]: list };
|
||||
}
|
||||
|
||||
function applyTypingFromServerAll(
|
||||
rows: Array<{
|
||||
externalRoomId: string;
|
||||
userId: string;
|
||||
fullName: string;
|
||||
chatUserId: string;
|
||||
}>,
|
||||
) {
|
||||
const now = Date.now();
|
||||
const next: Record<string, Array<{ userId: string; name?: string; at: number }>> = {};
|
||||
for (const row of rows) {
|
||||
const rid = String(row.externalRoomId || '').trim();
|
||||
const uid = String(row.chatUserId || row.userId || '').trim();
|
||||
if (!rid || !uid || uid === sessionCache?.chatUserId) continue;
|
||||
const list = next[rid] || [];
|
||||
if (!list.some((t) => t.userId === uid)) {
|
||||
list.push({ userId: uid, name: row.fullName || undefined, at: now });
|
||||
}
|
||||
next[rid] = list;
|
||||
}
|
||||
typingMap.value = next;
|
||||
if (Object.keys(next).length) startTypingSweep();
|
||||
}
|
||||
|
||||
function parseTypingCommand(content: string): string | null {
|
||||
@@ -318,17 +334,21 @@ function stopTypingPoll() {
|
||||
}
|
||||
}
|
||||
|
||||
function startTypingPoll(roomId: string) {
|
||||
stopTypingPoll();
|
||||
const rid = String(roomId || '').trim();
|
||||
if (!rid) return;
|
||||
/** poll همه اتاقها تا پیشنمایش لیست هم «در حال نوشتن» بگیرد */
|
||||
function startTypingPoll() {
|
||||
if (typingPollTimer) return;
|
||||
const tick = async () => {
|
||||
if (activeRoomId.value !== rid) return;
|
||||
if (!pageOpen.value) return;
|
||||
try {
|
||||
const { data } = await api.get<
|
||||
Array<{ userId: string; fullName: string; chatUserId: string }>
|
||||
>('/conversations/rooms/typing', { params: { externalRoomId: rid } });
|
||||
applyTypingFromServer(rid, Array.isArray(data) ? data : []);
|
||||
Array<{
|
||||
externalRoomId: string;
|
||||
userId: string;
|
||||
fullName: string;
|
||||
chatUserId: string;
|
||||
}>
|
||||
>('/conversations/rooms/typing');
|
||||
applyTypingFromServerAll(Array.isArray(data) ? data : []);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
@@ -1130,6 +1150,11 @@ export function useOrgChat() {
|
||||
|
||||
function setConversationsPageOpen(open: boolean) {
|
||||
pageOpen.value = open;
|
||||
if (open) startTypingPoll();
|
||||
else {
|
||||
stopTypingPoll();
|
||||
typingMap.value = {};
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeMessageList(
|
||||
@@ -1164,9 +1189,8 @@ export function useOrgChat() {
|
||||
loadingOlderMessages.value = false;
|
||||
fetchedRawCount = 0;
|
||||
hasMoreMessages.value = false;
|
||||
clearTypingForRoom(roomId);
|
||||
startHiddenPoll(roomId);
|
||||
startTypingPoll(roomId);
|
||||
startTypingPoll();
|
||||
try {
|
||||
const session = await ensureSession();
|
||||
await connectSocket();
|
||||
@@ -1637,7 +1661,6 @@ export function useOrgChat() {
|
||||
|
||||
function clearActiveRoom() {
|
||||
stopHiddenPoll();
|
||||
stopTypingPoll();
|
||||
activeRoomId.value = '';
|
||||
messages.value = [];
|
||||
}
|
||||
|
||||
@@ -341,7 +341,31 @@ function deliveryLabel(delivery?: ChatDelivery) {
|
||||
return '…';
|
||||
}
|
||||
|
||||
function roomTypingPreview(roomId: string): string {
|
||||
void typingTick.value;
|
||||
const list = (typingMap.value?.[roomId] || []).filter(
|
||||
(t) => Date.now() - t.at < 4000,
|
||||
);
|
||||
if (!list.length) return '';
|
||||
const names = list
|
||||
.map((t) => {
|
||||
if (t.name) return t.name;
|
||||
if (t.userId === '__someone__') return '';
|
||||
return (
|
||||
peers.value.find((p) => p.chatUserId === t.userId)?.fullName ||
|
||||
peers.value.find((p) => p.id === t.userId)?.fullName ||
|
||||
''
|
||||
);
|
||||
})
|
||||
.filter(Boolean);
|
||||
if (!names.length) return 'در حال نوشتن…';
|
||||
if (names.length === 1) return `${names[0]} در حال نوشتن…`;
|
||||
return `${toFaDigits(names.length)} نفر در حال نوشتن…`;
|
||||
}
|
||||
|
||||
function roomPreviewText(room: ConversationRoomDto) {
|
||||
const typing = roomTypingPreview(room.externalRoomId);
|
||||
if (typing) return typing;
|
||||
const raw = (room.lastMessagePreview || '').trim();
|
||||
if (!raw) return 'بدون پیام';
|
||||
if (raw === 'وویس') return 'پیام صوتی';
|
||||
@@ -1648,7 +1672,9 @@ onUnmounted(() => {
|
||||
<UiIcon v-if="room.pinned" name="pin" :size="12" class="room-pin-ic" />
|
||||
{{ roomTitle(room) }}
|
||||
</strong>
|
||||
<small>{{ roomPreviewText(room) }}</small>
|
||||
<small :class="{ 'room-typing': Boolean(roomTypingPreview(room.externalRoomId)) }">{{
|
||||
roomPreviewText(room)
|
||||
}}</small>
|
||||
</span>
|
||||
<span v-if="room.unreadCount" class="unread">{{ toFaDigits(room.unreadCount) }}</span>
|
||||
<time>{{ formatListTime(room.lastMessageAt) }}</time>
|
||||
@@ -2694,6 +2720,10 @@ onUnmounted(() => {
|
||||
max-width: 100%;
|
||||
display: block;
|
||||
}
|
||||
.room-meta small.room-typing {
|
||||
color: #0f766e;
|
||||
font-weight: 600;
|
||||
}
|
||||
.unread {
|
||||
grid-column: 3;
|
||||
grid-row: 1;
|
||||
|
||||
Reference in New Issue
Block a user