fix: solve small pm chat ui issues

unwrap the pill
fix the fetching images in pm not auto download
This commit is contained in:
2026-05-25 17:17:32 +02:00
parent 1259645706
commit 161f57f52e
28 changed files with 697 additions and 82 deletions

View File

@@ -133,6 +133,8 @@ When the user navigates to a room, the manager watches the route and decides whi
The decision lives in `shouldAutoRequestWhenWatched()` which calls `isAttachmentMedia()` and checks against `MAX_AUTO_SAVE_SIZE_BYTES`.
Direct-message routes (`/dm/:conversationId` and `/pm/:conversationId`) are treated as watched attachment containers named `direct-message:<conversationId>`, so image/video metadata announced for the visible conversation is eligible for the same automatic request path as server-room media.
Browser chat views render audio/video larger than 50 MB with the same generic file interface as other downloads, even after the bytes are available. Attachments with audio/video MIME types that Chromium reports as unsupported also use the generic file interface instead of a broken native player.
An optional experimental VLC.js adapter can be enabled from General settings. When enabled, unsupported downloaded audio/video files show a manual Play action that lazy-loads `/vlcjs/metoyou-vlc-player.js`. The runtime is intentionally isolated in the experimental media domain and is not part of the default attachment path.

View File

@@ -6,8 +6,11 @@ import {
import { NavigationEnd, Router } from '@angular/router';
import { RealtimeSessionFacade } from '../../../../core/realtime';
import { DatabaseService } from '../../../../infrastructure/persistence';
import { ROOM_URL_PATTERN } from '../../../../core/constants';
import { shouldAutoRequestWhenWatched } from '../../domain/logic/attachment.logic';
import {
getWatchedAttachmentRoomIdFromUrl,
isDirectMessageAttachmentRoomId,
shouldAutoRequestWhenWatched
} from '../../domain/logic/attachment.logic';
import type { Attachment, AttachmentMeta } from '../../domain/models/attachment.model';
import type {
FileAnnouncePayload,
@@ -182,6 +185,11 @@ export class AttachmentManagerService {
return;
}
if (isDirectMessageAttachmentRoomId(roomId)) {
await this.requestAutoDownloadsForRuntimeRoom(roomId);
return;
}
if (this.database.isReady()) {
const messages = await this.database.getMessages(roomId, 500, 0);
@@ -193,6 +201,10 @@ export class AttachmentManagerService {
return;
}
await this.requestAutoDownloadsForRuntimeRoom(roomId);
}
private async requestAutoDownloadsForRuntimeRoom(roomId: string): Promise<void> {
for (const [messageId] of this.runtimeStore.getAttachmentEntries()) {
const attachmentRoomId = await this.persistence.resolveMessageRoomId(messageId);
@@ -235,9 +247,7 @@ export class AttachmentManagerService {
}
private extractWatchedRoomId(url: string): string | null {
const roomMatch = url.match(ROOM_URL_PATTERN);
return roomMatch ? roomMatch[1] : null;
return getWatchedAttachmentRoomIdFromUrl(url);
}
private isRoomWatched(roomId: string | null | undefined): boolean {

View File

@@ -0,0 +1,24 @@
import { getWatchedAttachmentRoomIdFromUrl, isDirectMessageAttachmentRoomId } from './attachment.logic';
describe('attachment logic', () => {
it('extracts watched server room ids from room URLs', () => {
expect(getWatchedAttachmentRoomIdFromUrl('/room/general')).toBe('general');
expect(getWatchedAttachmentRoomIdFromUrl('/room/general/chat')).toBe('general');
});
it('extracts watched direct-message storage ids from DM URLs', () => {
expect(getWatchedAttachmentRoomIdFromUrl('/dm/alice%3Abob')).toBe('direct-message:alice:bob');
expect(getWatchedAttachmentRoomIdFromUrl('/pm/dm-group-1?tab=chat')).toBe('direct-message:dm-group-1');
});
it('ignores non-message URLs', () => {
expect(getWatchedAttachmentRoomIdFromUrl('/settings')).toBeNull();
expect(getWatchedAttachmentRoomIdFromUrl('/dm')).toBeNull();
});
it('identifies direct-message attachment storage ids', () => {
expect(isDirectMessageAttachmentRoomId('direct-message:alice:bob')).toBe(true);
expect(isDirectMessageAttachmentRoomId('room-1')).toBe(false);
expect(isDirectMessageAttachmentRoomId(null)).toBe(false);
});
});

View File

@@ -1,6 +1,10 @@
import { MAX_AUTO_SAVE_SIZE_BYTES } from '../constants/attachment.constants';
import type { Attachment } from '../models/attachment.model';
const ROOM_URL_PATTERN = /\/room\/([^/]+)/;
const DIRECT_MESSAGE_URL_PATTERN = /^\/(?:dm|pm)\/([^/]+)/;
const DIRECT_MESSAGE_ATTACHMENT_STORAGE_PREFIX = 'direct-message:';
export function isAttachmentMedia(attachment: Pick<Attachment, 'mime'>): boolean {
return attachment.mime.startsWith('image/') ||
attachment.mime.startsWith('video/') ||
@@ -17,3 +21,28 @@ export function shouldPersistDownloadedAttachment(attachment: Pick<Attachment, '
attachment.mime.startsWith('video/') ||
attachment.mime.startsWith('audio/');
}
export function getWatchedAttachmentRoomIdFromUrl(url: string): string | null {
const path = url.split(/[?#]/, 1)[0];
const directMessageMatch = path.match(DIRECT_MESSAGE_URL_PATTERN);
if (directMessageMatch) {
return `${DIRECT_MESSAGE_ATTACHMENT_STORAGE_PREFIX}${decodeUrlSegment(directMessageMatch[1])}`;
}
const roomMatch = path.match(ROOM_URL_PATTERN);
return roomMatch ? decodeUrlSegment(roomMatch[1]) : null;
}
export function isDirectMessageAttachmentRoomId(roomId: string | null | undefined): boolean {
return !!roomId && roomId.startsWith(DIRECT_MESSAGE_ATTACHMENT_STORAGE_PREFIX);
}
function decodeUrlSegment(value: string): string {
try {
return decodeURIComponent(value);
} catch {
return value;
}
}