feat: Add emoji and alot of other fixes
This commit is contained in:
@@ -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.';
|
||||
|
||||
@@ -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'
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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)
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user