Editing correction (in progress)

This commit is contained in:
2026-09-30 13:01:42 +03:30
parent 3c0ed3369e
commit c7a60c524d
4 changed files with 135 additions and 23 deletions
@@ -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;
+45 -22
View File
@@ -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 = [];
}
+31 -1
View File
@@ -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;