feat: Add emoji and alot of other fixes

This commit is contained in:
2026-06-05 05:40:18 +02:00
parent ca069e2f61
commit 6865147e8f
72 changed files with 3885 additions and 413 deletions

View File

@@ -18,3 +18,7 @@ export const NO_CONNECTED_PEERS_REQUEST_ERROR = 'No connected peers are availabl
/** User-facing error when connected peers cannot provide a requested file. */
export const FILE_NOT_FOUND_REQUEST_ERROR = 'The connected peers do not have this file right now.';
/** User-facing error when the uploader's local copy cannot be restored. */
export const UPLOADER_LOCAL_FILE_MISSING_ERROR =
'Your original upload could not be found on this device. Re-upload the file to restore playback.';

View File

@@ -0,0 +1,33 @@
import { mergeAttachmentLocalPaths } from './attachment-persistence.rules';
describe('attachment persistence rules', () => {
it('keeps incoming local paths when they are set', () => {
expect(mergeAttachmentLocalPaths(
{ filePath: '/tmp/new.mp4', savedPath: '/data/saved.mp4' },
{ filePath: '/tmp/old.mp4', savedPath: '/data/old.mp4' }
)).toEqual({
filePath: '/tmp/new.mp4',
savedPath: '/data/saved.mp4'
});
});
it('preserves stored local paths when incoming sync metadata omits them', () => {
expect(mergeAttachmentLocalPaths(
{},
{ filePath: '/home/ludde/video.mp4', savedPath: '/appdata/video.mp4' }
)).toEqual({
filePath: '/home/ludde/video.mp4',
savedPath: '/appdata/video.mp4'
});
});
it('does not overwrite a stored path with an explicit null', () => {
expect(mergeAttachmentLocalPaths(
{ filePath: null, savedPath: null },
{ filePath: '/home/ludde/video.mp4', savedPath: '/appdata/video.mp4' }
)).toEqual({
filePath: '/home/ludde/video.mp4',
savedPath: '/appdata/video.mp4'
});
});
});

View File

@@ -0,0 +1,26 @@
export interface AttachmentLocalPaths {
filePath?: string | null;
savedPath?: string | null;
}
export function mergeAttachmentLocalPaths(
incoming: AttachmentLocalPaths,
stored?: AttachmentLocalPaths | null
): Required<AttachmentLocalPaths> {
const resolvePath = (incomingPath: string | null | undefined, storedPath: string | null | undefined): string | null => {
if (typeof incomingPath === 'string' && incomingPath.trim()) {
return incomingPath;
}
if (typeof storedPath === 'string' && storedPath.trim()) {
return storedPath;
}
return null;
};
return {
filePath: resolvePath(incoming.filePath, stored?.filePath),
savedPath: resolvePath(incoming.savedPath, stored?.savedPath)
};
}

View File

@@ -1,4 +1,8 @@
import { getWatchedAttachmentRoomIdFromUrl, isDirectMessageAttachmentRoomId } from './attachment.logic';
import {
getWatchedAttachmentRoomIdFromUrl,
isDirectMessageAttachmentRoomId,
shouldCopyUploaderMediaToAppData
} from './attachment.logic';
describe('attachment logic', () => {
it('extracts watched server room ids from room URLs', () => {
@@ -21,4 +25,23 @@ describe('attachment logic', () => {
expect(isDirectMessageAttachmentRoomId('room-1')).toBe(false);
expect(isDirectMessageAttachmentRoomId(null)).toBe(false);
});
it('copies large uploader media into app data when a source path exists', () => {
expect(shouldCopyUploaderMediaToAppData({
size: 64 * 1024 * 1024,
mime: 'video/mp4'
}, '/home/ludde/video.mp4', true)).toBe(true);
});
it('skips app-data copy for small uploads and missing source paths', () => {
expect(shouldCopyUploaderMediaToAppData({
size: 1024,
mime: 'video/mp4'
}, '/home/ludde/video.mp4', true)).toBe(false);
expect(shouldCopyUploaderMediaToAppData({
size: 64 * 1024 * 1024,
mime: 'video/mp4'
}, undefined, true)).toBe(false);
});
});

View File

@@ -22,6 +22,17 @@ export function shouldPersistDownloadedAttachment(attachment: Pick<Attachment, '
attachment.mime.startsWith('audio/');
}
export function shouldCopyUploaderMediaToAppData(
attachment: Pick<Attachment, 'size' | 'mime'>,
sourcePath?: string | null,
canCopyFiles = false
): boolean {
return canCopyFiles &&
!!sourcePath &&
(attachment.mime.startsWith('video/') || attachment.mime.startsWith('audio/')) &&
attachment.size > MAX_AUTO_SAVE_SIZE_BYTES;
}
export function getWatchedAttachmentRoomIdFromUrl(url: string): string | null {
const path = url.split(/[?#]/, 1)[0];
const directMessageMatch = path.match(DIRECT_MESSAGE_URL_PATTERN);

View File

@@ -0,0 +1,27 @@
import { annotateLocalFilePath, resolveLocalFilePath } from './local-file-path.rules';
describe('local file path rules', () => {
it('prefers an existing path property on the file', () => {
const file = new File(['video'], 'clip.mp4', { type: 'video/mp4' });
Object.defineProperty(file, 'path', { value: '/tmp/clip.mp4' });
expect(resolveLocalFilePath(file)).toBe('/tmp/clip.mp4');
});
it('resolves drag-and-drop files through Electron getPathForFile', () => {
const file = new File(['video'], 'clip.mp4', { type: 'video/mp4' });
expect(resolveLocalFilePath(file, {
getPathForFile: () => '/home/ludde/Videos/clip.mp4'
})).toBe('/home/ludde/Videos/clip.mp4');
});
it('annotates a file object with the resolved path', () => {
const file = new File(['video'], 'clip.mp4', { type: 'video/mp4' });
const annotated = annotateLocalFilePath(file, {
getPathForFile: () => '/home/ludde/Videos/clip.mp4'
});
expect(resolveLocalFilePath(annotated)).toBe('/home/ludde/Videos/clip.mp4');
});
});

View File

@@ -0,0 +1,54 @@
export type LocalFilePathResolver = (file: File) => string;
type LocalFileWithPath = File & {
path?: string;
};
export function resolveLocalFilePath(
file: File,
options?: {
getPathForFile?: LocalFilePathResolver;
}
): string | undefined {
const existingPath = (file as LocalFileWithPath).path?.trim();
if (existingPath) {
return existingPath;
}
if (!options?.getPathForFile) {
return undefined;
}
try {
const resolvedPath = options.getPathForFile(file).trim();
return resolvedPath || undefined;
} catch {
return undefined;
}
}
export function annotateLocalFilePath(
file: File,
options?: {
getPathForFile?: LocalFilePathResolver;
}
): File {
const resolvedPath = resolveLocalFilePath(file, options);
if (!resolvedPath) {
return file;
}
try {
Object.defineProperty(file, 'path', {
configurable: true,
value: resolvedPath
});
} catch {
(file as LocalFileWithPath).path = resolvedPath;
}
return file;
}