Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/spotty-news-burn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@rocket.chat/core-typings': minor
'@rocket.chat/rest-typings': minor
'@rocket.chat/ui-composer': minor
'@rocket.chat/i18n': minor
'@rocket.chat/meteor': minor
---

Adds support for multiple files in message composer, improving file upload experience
2 changes: 1 addition & 1 deletion .github/actions/update-version-durability/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ async function generateTable({ owner, repo } = {}) {
minorDate.setDate(1);
supportDateStart = minorDate;
supportDate = new Date(minorDate);
supportDate.setMonth(supportDate.getMonth() + (lts ? 9 : 6));
supportDate.setMonth(supportDate.getMonth() + (lts ? 12 : 6));

releaseData.push({
release: {
Expand Down
15 changes: 15 additions & 0 deletions .yarn/patches/@react-pdf-layout-npm-4.4.2-6c2e3312fa.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
diff --git a/lib/index.js b/lib/index.js
index c64bdf2d5f7e704a65be4e9a7116c5ee6a582701..ce6641d0b63daf7c5d3f8a1de773f290c0e9d51c 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -2,8 +2,8 @@ import { upperFirst, capitalize, parseFloat as parseFloat$1, without, pick, comp
import * as P from '@react-pdf/primitives';
import resolveStyle, { transformColor, flatten } from '@react-pdf/stylesheet';
import layoutEngine, { fontSubstitution, wordHyphenation, scriptItemizer, textDecoration, justification, linebreaker, bidi, fromFragments } from '@react-pdf/textkit';
-import * as Yoga from 'yoga-layout/load';
-import { loadYoga as loadYoga$1 } from 'yoga-layout/load';
+import * as Yoga from 'yoga-layout/dist/src/load.js';
+import { loadYoga as loadYoga$1 } from 'yoga-layout/dist/src/load.js';
import emojiRegex from 'emoji-regex-xs';
import resolveImage from '@react-pdf/image';

26 changes: 26 additions & 0 deletions .yarn/patches/yoga-layout-npm-3.2.1-51ec934670.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
diff --git a/dist/binaries/yoga-wasm-base64-esm.js b/dist/binaries/yoga-wasm-base64-esm.js
index 350866aeaf90bdcc7bea18adfaae1cfcf2e40af6..a973419e8569791396b7a123599fd464b8c0cff4 100644
--- a/dist/binaries/yoga-wasm-base64-esm.js
+++ b/dist/binaries/yoga-wasm-base64-esm.js
@@ -1,6 +1,6 @@

var loadYoga = (() => {
- var _scriptDir = import.meta.url;
+ var _scriptDir = undefined;

return (
function(loadYoga) {
diff --git a/package.json b/package.json
index 1fb0482c9451d745ca010f9c1ad58f5d0f74a559..8f7705e1325c046fd671ddc163bc34b74d4389cf 100644
--- a/package.json
+++ b/package.json
@@ -14,7 +14,8 @@
"types": "./dist/src/index.d.ts",
"exports": {
".": "./dist/src/index.js",
- "./load": "./dist/src/load.js"
+ "./load": "./dist/src/load.js",
+ "./dist/src/load.js": "./dist/src/load.js"
},
"files": [
"dist/binaries/**",
10 changes: 10 additions & 0 deletions apps/meteor/app/api/server/v1/rooms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,16 @@ API.v1.addRoute(
file.description = this.bodyParams.description;
delete this.bodyParams.description;

if (this.bodyParams.fileName) {
file.name = this.bodyParams.fileName;
delete this.bodyParams.fileName;
}

if (this.bodyParams.fileContent) {
file.content = this.bodyParams.fileContent;
delete this.bodyParams.fileContent;
}

await applyAirGappedRestrictionsValidation(() =>
sendFileMessage(this.userId, { roomId: this.urlParams.rid, file, msgData: this.bodyParams }),
);
Expand Down
8 changes: 7 additions & 1 deletion apps/meteor/app/ui/client/lib/ChatMessages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { UserAction } from './UserAction';
import type { ChatAPI, ComposerAPI, DataAPI, UploadsAPI } from '../../../../client/lib/chats/ChatAPI';
import { createDataAPI } from '../../../../client/lib/chats/data';
import { processMessageEditing } from '../../../../client/lib/chats/flows/processMessageEditing';
import { processMessageUploads } from '../../../../client/lib/chats/flows/processMessageUploads';
import { processSetReaction } from '../../../../client/lib/chats/flows/processSetReaction';
import { processSlashCommand } from '../../../../client/lib/chats/flows/processSlashCommand';
import { processTooLongMessage } from '../../../../client/lib/chats/flows/processTooLongMessage';
Expand Down Expand Up @@ -44,6 +45,8 @@ export class ChatMessages implements ChatAPI {

public uploads: UploadsAPI;

public threadUploads: UploadsAPI;

public ActionManager: any;

public emojiPicker: {
Expand Down Expand Up @@ -121,6 +124,7 @@ export class ChatMessages implements ChatAPI {
await this.currentEditingMessage.stop();
},
editMessage: async (message: IMessage, { cursorAtStart = false }: { cursorAtStart?: boolean } = {}) => {
message.tmid ? this.threadUploads.clear() : this.uploads.clear();
const text = (await this.data.getDraft(message._id)) || message.attachments?.[0]?.description || message.msg;

await this.currentEditingMessage.stop();
Expand All @@ -147,7 +151,8 @@ export class ChatMessages implements ChatAPI {
this.tmid = tmid;
this.uid = params.uid;
this.data = createDataAPI({ rid, tmid });
this.uploads = createUploadsAPI({ rid, tmid });
this.uploads = createUploadsAPI({ rid });
this.threadUploads = createUploadsAPI({ rid });
this.ActionManager = params.actionManager;
this.currentEditingMessage = new CurrentEditingMessage(this);

Expand Down Expand Up @@ -180,6 +185,7 @@ export class ChatMessages implements ChatAPI {
processSlashCommand: processSlashCommand.bind(null, this),
processTooLongMessage: processTooLongMessage.bind(null, this),
processMessageEditing: processMessageEditing.bind(null, this),
processMessageUploads: processMessageUploads.bind(null, this),
processSetReaction: processSetReaction.bind(null, this),
requestMessageDeletion: requestMessageDeletion.bind(this, this),
replyBroadcast: replyBroadcast.bind(null, this),
Expand Down
34 changes: 26 additions & 8 deletions apps/meteor/client/lib/chats/ChatAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { IMessage, IRoom, ISubscription, IE2EEMessage, IUpload } from '@roc
import type { IActionManager } from '@rocket.chat/ui-contexts';
import type { RefObject } from 'react';

import type { Upload } from './Upload';
import type { Upload, EncryptedFile } from './Upload';
import type { ReadStateManager } from './readStateManager';
import type { FormattingButton } from '../../../app/ui-message/client/messageBox/messageBoxFormatting';

Expand Down Expand Up @@ -100,17 +100,24 @@ export type DataAPI = {
getSubscriptionFromMessage(message: IMessage): Promise<ISubscription>;
};

export type EncryptedFileUploadContent = {
rawFile: File;
fileContent: { raw: Partial<IUpload>; encrypted?: IE2EEMessage['content'] };
encryptedFile: EncryptedFile;
};

export type UploadsAPI = {
get(): readonly Upload[];
subscribe(callback: () => void): () => void;
wipeFailedOnes(): void;
clear(): void;
getProcessingUploads(): boolean;
setProcessingUploads(processing: boolean): void;
cancel(id: Upload['id']): void;
send(
file: File,
{ description, msg, t, e2e }: { description?: string; msg?: string; t?: IMessage['t']; e2e?: IMessage['e2e'] },
getContent?: (fileId: string, fileUrl: string) => Promise<IE2EEMessage['content']>,
fileContent?: { raw: Partial<IUpload>; encrypted: IE2EEMessage['content'] },
): Promise<void>;
removeUpload(id: Upload['id']): void;
editUploadFileName: (id: Upload['id'], fileName: string) => void;
send(file: File, encrypted?: never): Promise<void>;
send(file: File, encrypted: EncryptedFileUploadContent): Promise<void>;
};

export type ChatAPI = {
Expand All @@ -119,6 +126,7 @@ export type ChatAPI = {
readonly setComposerAPI: (composer?: ComposerAPI) => void;
readonly data: DataAPI;
readonly uploads: UploadsAPI;
readonly threadUploads: UploadsAPI;
readonly readStateManager: ReadStateManager;
readonly messageEditing: {
toPreviousMessage(): Promise<void>;
Expand Down Expand Up @@ -148,7 +156,15 @@ export type ChatAPI = {
ActionManager: IActionManager;

readonly flows: {
readonly uploadFiles: (files: readonly File[], resetFileInput?: () => void) => Promise<void>;
readonly uploadFiles: ({
files,
uploadsStore,
resetFileInput,
}: {
files: readonly File[];
uploadsStore: UploadsAPI;
resetFileInput?: () => void;
}) => Promise<void>;
readonly sendMessage: ({
text,
tshow,
Expand All @@ -157,13 +173,15 @@ export type ChatAPI = {
tshow?: boolean;
previewUrls?: string[];
isSlashCommandAllowed?: boolean;
tmid?: IMessage['tmid'];
}) => Promise<boolean>;
readonly processSlashCommand: (message: IMessage, userId: string | null) => Promise<boolean>;
readonly processTooLongMessage: (message: IMessage) => Promise<boolean>;
readonly processMessageEditing: (
message: Pick<IMessage, '_id' | 't'> & Partial<Omit<IMessage, '_id' | 't'>>,
previewUrls?: string[],
) => Promise<boolean>;
readonly processMessageUploads: (message: IMessage) => Promise<boolean>;
readonly processSetReaction: (message: Pick<IMessage, 'msg'>) => Promise<boolean>;
readonly requestMessageDeletion: (message: IMessage) => Promise<void>;
readonly replyBroadcast: (message: IMessage) => Promise<void>;
Expand Down
25 changes: 23 additions & 2 deletions apps/meteor/client/lib/chats/Upload.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,27 @@
export type Upload = {
import type { IUpload } from '@rocket.chat/core-typings';

export type NonEncryptedUpload = {
readonly id: string;
readonly name: string;
readonly file: File;
readonly url?: string;
readonly percentage: number;
readonly error?: Error;
};

export type EncryptedUpload = NonEncryptedUpload & {
readonly encryptedFile: EncryptedFile;
readonly metadataForEncryption: Partial<IUpload>;
};

export type Upload = EncryptedUpload | NonEncryptedUpload;

export type EncryptedFile = {
readonly file: File;
readonly key: JsonWebKey;
readonly iv: string;
readonly type: File['type'];
readonly hash: string;
};

export const isEncryptedUpload = (upload: Upload): upload is EncryptedUpload =>
'encryptedFile' in upload && upload.encryptedFile !== undefined;
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const processMessageEditing = async (
}

try {
chat.composer?.clear();
await chat.data.updateMessage({ ...message, _id: mid }, previewUrls);
} catch (error) {
dispatchToastMessage({ type: 'error', message: error });
Expand Down
Loading
Loading