diff --git a/apps/meteor/client/hooks/roomActions/useExportMessagesRoomAction.ts b/apps/meteor/client/hooks/roomActions/useExportMessagesRoomAction.ts
index 21d1da3a6843..9996f0da8769 100644
--- a/apps/meteor/client/hooks/roomActions/useExportMessagesRoomAction.ts
+++ b/apps/meteor/client/hooks/roomActions/useExportMessagesRoomAction.ts
@@ -5,14 +5,29 @@ import { useRoom } from '../../views/room/contexts/RoomContext';
import type { RoomToolboxActionConfig } from '../../views/room/contexts/RoomToolboxContext';
const ExportMessages = lazy(() => import('../../views/room/contextualBar/ExportMessages'));
+const ExportE2EEMessages = lazy(() => import('../../views/room/contextualBar/ExportMessages/ExportE2EEMessages'));
export const useExportMessagesRoomAction = () => {
const room = useRoom();
- const permitted = usePermission('mail-messages', room._id);
+ const hasPermission = usePermission('mail-messages', room._id);
- return useMemo((): RoomToolboxActionConfig | undefined => {
- if (!permitted) {
- return undefined;
+ return useMemo((): RoomToolboxActionConfig | null => {
+ if (!hasPermission) {
+ return null;
+ }
+
+ if (room.encrypted) {
+ return {
+ id: 'export-encrypted-messages',
+ groups: ['channel', 'group', 'direct', 'direct_multiple', 'team'],
+ anonymous: true,
+ title: 'Export_Encrypted_Messages',
+ icon: 'mail',
+ tabComponent: ExportE2EEMessages,
+ full: true,
+ order: 12,
+ type: 'communication',
+ };
}
return {
@@ -26,5 +41,5 @@ export const useExportMessagesRoomAction = () => {
order: 12,
type: 'communication',
};
- }, [permitted]);
+ }, [hasPermission, room.encrypted]);
};
diff --git a/apps/meteor/client/views/room/Header/icons/Encrypted.tsx b/apps/meteor/client/views/room/Header/icons/Encrypted.tsx
index ca21153126fd..8af62f3fdd15 100644
--- a/apps/meteor/client/views/room/Header/icons/Encrypted.tsx
+++ b/apps/meteor/client/views/room/Header/icons/Encrypted.tsx
@@ -9,7 +9,7 @@ import { HeaderState } from '../../../../components/Header';
const Encrypted = ({ room }: { room: IRoom }) => {
const { t } = useTranslation();
const e2eEnabled = useSetting('E2E_Enable');
- return e2eEnabled && room?.encrypted ? : null;
+ return e2eEnabled && room?.encrypted ? : null;
};
export default memo(Encrypted);
diff --git a/apps/meteor/client/views/room/MessageList/contexts/SelectedMessagesContext.tsx b/apps/meteor/client/views/room/MessageList/contexts/SelectedMessagesContext.tsx
index f9fda919d88c..8a47dfaf6a4b 100644
--- a/apps/meteor/client/views/room/MessageList/contexts/SelectedMessagesContext.tsx
+++ b/apps/meteor/client/views/room/MessageList/contexts/SelectedMessagesContext.tsx
@@ -44,6 +44,13 @@ export const useToggleSelect = (mid: string): (() => void) => {
}, [mid, selectedMessageStore]);
};
+export const useToggleSelectAll = (mids: string[]): (() => void) => {
+ const { selectedMessageStore } = useContext(SelectedMessageContext);
+ return useCallback(() => {
+ selectedMessageStore.toggleAll(mids);
+ }, [mids, selectedMessageStore]);
+};
+
export const useCountSelected = (): number => {
const { selectedMessageStore } = useContext(SelectedMessageContext);
diff --git a/apps/meteor/client/views/room/body/RoomBody.tsx b/apps/meteor/client/views/room/body/RoomBody.tsx
index 13c111592c39..93e806d5f562 100644
--- a/apps/meteor/client/views/room/body/RoomBody.tsx
+++ b/apps/meteor/client/views/room/body/RoomBody.tsx
@@ -1,5 +1,5 @@
import type { IUser } from '@rocket.chat/core-typings';
-import { Box } from '@rocket.chat/fuselage';
+import { Box, Button } from '@rocket.chat/fuselage';
import { useMergedRefs } from '@rocket.chat/fuselage-hooks';
import { usePermission, useRole, useSetting, useTranslation, useUser, useUserPreference } from '@rocket.chat/ui-contexts';
import type { MouseEventHandler, ReactElement, UIEvent } from 'react';
@@ -102,7 +102,7 @@ const RoomBody = (): ReactElement => {
const { innerRef: isAtBottomInnerRef, atBottomRef, sendToBottom, sendToBottomIfNecessary, isAtBottom } = useListIsAtBottom();
- const { innerRef: getMoreInnerRef } = useGetMore(room._id, atBottomRef);
+ const { innerRef: getMoreInnerRef, handleGetMore } = useGetMore(room._id, atBottomRef);
const { wrapperRef: leaderBannerWrapperRef, hideLeaderHeader, innerRef: leaderBannerInnerRef } = useLeaderBanner();
@@ -307,6 +307,9 @@ const RoomBody = (): ReactElement => {
+
) => {
+ const ref: MutableRefObject = useRef(null);
+ const messages = useMessages({ rid: 'GENERAL' });
+ const handleToggleAll = useToggleSelectAll(messages.map((message) => message._id));
+
+ const handleGetMore = () => {
+ ref.current?.scrollTo({ top: 0, behavior: 'smooth' });
+ handleToggleAll();
+ };
+
return {
+ handleGetMore,
innerRef: useCallback(
(wrapper: HTMLElement | null) => {
if (!wrapper) {
return;
}
+ ref.current = wrapper;
let lastScrollTopRef = 0;
wrapper.addEventListener(
diff --git a/apps/meteor/client/views/room/contextualBar/ExportMessages/ExportE2EEMessages.tsx b/apps/meteor/client/views/room/contextualBar/ExportMessages/ExportE2EEMessages.tsx
new file mode 100644
index 000000000000..1733edb32a87
--- /dev/null
+++ b/apps/meteor/client/views/room/contextualBar/ExportMessages/ExportE2EEMessages.tsx
@@ -0,0 +1,219 @@
+import type { IMessage, IRoom } from '@rocket.chat/core-typings';
+import type { SelectOption } from '@rocket.chat/fuselage';
+import {
+ Field,
+ FieldLabel,
+ FieldRow,
+ Select,
+ ButtonGroup,
+ Button,
+ FieldGroup,
+ InputBox,
+ Margins,
+ Box,
+ FieldHint,
+ Callout,
+} from '@rocket.chat/fuselage';
+import { useAutoFocus, useUniqueId } from '@rocket.chat/fuselage-hooks';
+import { useUserPreference } from '@rocket.chat/ui-contexts';
+import { useMutation } from '@tanstack/react-query';
+import React, { useCallback, useMemo } from 'react';
+import { Controller, useForm } from 'react-hook-form';
+import { useTranslation } from 'react-i18next';
+
+// import type { MailExportFormValues } from './ExportMessages';
+// import { useRoomExportMutation } from './useRoomExportMutation';
+import { Messages } from '../../../../../app/models/client';
+import {
+ ContextualbarScrollableContent,
+ ContextualbarFooter,
+ ContextualbarClose,
+ ContextualbarHeader,
+ ContextualbarIcon,
+ ContextualbarTitle,
+} from '../../../../components/Contextualbar';
+import { useReactiveValue } from '../../../../hooks/useReactiveValue';
+import { downloadJsonAs } from '../../../../lib/download';
+import { useRoom } from '../../contexts/RoomContext';
+import { useRoomToolbox } from '../../contexts/RoomToolboxContext';
+
+export const useMessages = ({ rid }: { rid: IRoom['_id'] }): IMessage[] => {
+ const showThreadsInMainChannel = useUserPreference('showThreadsInMainChannel', false);
+ // const hideSysMesSetting = useSetting('Hide_System_Messages', []);
+ const room = useRoom();
+ // const hideRoomSysMes: Array = Array.isArray(room.sysMes) ? room.sysMes : [];
+
+ // const hideSysMessages = useStableArray(mergeHideSysMessages(hideSysMesSetting, hideRoomSysMes));
+
+ const query: Mongo.Selector = useMemo(
+ () => ({
+ rid,
+ _hidden: { $ne: true },
+ // t: { $nin: hideSysMessages },
+ ...(!showThreadsInMainChannel && {
+ $or: [{ tmid: { $exists: false } }, { tshow: { $eq: true } }],
+ }),
+ ts: { $gte: new Date('2024-11-30'), $lt: new Date() },
+ }),
+ [rid, showThreadsInMainChannel],
+ );
+
+ return useReactiveValue(
+ useCallback(
+ () =>
+ Messages.find(query, {
+ sort: {
+ ts: 1,
+ },
+ }).fetch(),
+ [query],
+ ),
+ );
+};
+
+const useExportE2EEMessages = ({ rid }: { rid: string }) => {
+ const showThreadsInMainChannel = useUserPreference('showThreadsInMainChannel', false);
+
+ // const messages = useMessages({ rid: room._id });
+
+ // const query: Mongo.Selector = useMemo(
+ // () => ({
+ // rid,
+ // _hidden: { $ne: true },
+ // // t: { $nin: hideSysMessages },
+ // ...(!showThreadsInMainChannel && {
+ // $or: [{ tmid: { $exists: false } }, { tshow: { $eq: true } }],
+ // }),
+ // ts: { $gte: new Date('2024-11-15'), $lt: new Date() },
+ // }),
+ // [rid, showThreadsInMainChannel],
+ // );
+
+ return useMutation({
+ mutationFn: ({ from, until }: FormValues) => {
+ return Messages.find({
+ rid,
+ _hidden: { $ne: true },
+ // t: { $nin: hideSysMessages },
+ ...(!showThreadsInMainChannel && {
+ $or: [{ tmid: { $exists: false } }, { tshow: { $eq: true } }],
+ }),
+ ...((from.date || until.date) && { ts: { $gte: new Date(from.date), $lt: new Date(until.date) } }),
+ }).fetch();
+ },
+ onSuccess: (data) => {
+ console.log(data);
+ downloadJsonAs(data, 'exportedMessages');
+ },
+ });
+};
+
+type FormValues = {
+ type: 'file';
+ format: 'html' | 'json';
+ from: { date: string; time: string };
+ until: { date: string; time: string };
+};
+
+const ExportE2EEMessages = () => {
+ const { t } = useTranslation();
+ const room = useRoom();
+ const { closeTab } = useRoomToolbox();
+
+ // console.log(messages);
+ const exportE2EEMessages = useExportE2EEMessages({ rid: room._id });
+
+ const { control, register, handleSubmit } = useForm({
+ defaultValues: { type: 'file', format: 'html', from: { date: '', time: '' }, until: { date: '', time: '' } },
+ });
+
+ const outputOptions = useMemo(
+ () => [
+ ['html', t('HTML')],
+ ['json', t('JSON')],
+ ],
+ [t],
+ );
+
+ const handleExport = async (data: FormValues) => {
+ console.log(data);
+
+ exportE2EEMessages.mutate(data);
+ // return downloadJsonAs(statisticsQuery.data, 'statistics');
+ };
+
+ const formId = useUniqueId();
+ const typeField = useUniqueId();
+ const formatField = useUniqueId();
+ const formFocus = useAutoFocus();
+
+ return (
+ <>
+
+
+ {t('Export_Encrypted_Messages')}
+
+
+ <>
+
+
+
+
+
+
+
+
+
+ >
+ >
+ );
+};
+
+export default ExportE2EEMessages;
diff --git a/apps/meteor/client/views/room/providers/SelectedMessagesProvider.tsx b/apps/meteor/client/views/room/providers/SelectedMessagesProvider.tsx
index 4100751037ff..f9b133b1c039 100644
--- a/apps/meteor/client/views/room/providers/SelectedMessagesProvider.tsx
+++ b/apps/meteor/client/views/room/providers/SelectedMessagesProvider.tsx
@@ -45,6 +45,16 @@ export const selectedMessageStore = new (class SelectMessageStore extends Emitte
this.emit('change');
}
+ select(mid: string): void {
+ if (this.store.has(mid)) {
+ return;
+ }
+
+ this.store.add(mid);
+ this.emit(mid, true);
+ this.emit('change');
+ }
+
count(): number {
return this.store.size;
}
@@ -61,6 +71,10 @@ export const selectedMessageStore = new (class SelectMessageStore extends Emitte
this.isSelecting = false;
this.emit('toggleIsSelecting', false);
}
+
+ toggleAll(mids: string[]): void {
+ mids.forEach((mid) => this.select(mid));
+ }
})();
type SelectedMessagesProviderProps = {