Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
dougfabris committed Nov 29, 2024
1 parent fd44a8b commit 5a4d892
Show file tree
Hide file tree
Showing 7 changed files with 280 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -26,5 +41,5 @@ export const useExportMessagesRoomAction = () => {
order: 12,
type: 'communication',
};
}, [permitted]);
}, [hasPermission, room.encrypted]);
};
2 changes: 1 addition & 1 deletion apps/meteor/client/views/room/Header/icons/Encrypted.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 ? <HeaderState title={t('Encrypted')} icon='key' color={colors.g500} tiny /> : null;
return e2eEnabled && room?.encrypted ? <HeaderState title={t('Encrypted')} icon='key' color={colors.g500} /> : null;
};

export default memo(Encrypted);
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
7 changes: 5 additions & 2 deletions apps/meteor/client/views/room/body/RoomBody.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -307,6 +307,9 @@ const RoomBody = (): ReactElement => {
</div>
</div>
<RoomComposer>
<Button small primary onClick={handleGetMore}>
Select all messages
</Button>
<ComposerContainer
subscription={subscription}
onResize={handleComposerResize}
Expand Down
15 changes: 14 additions & 1 deletion apps/meteor/client/views/room/body/hooks/useGetMore.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,30 @@
import type { MutableRefObject } from 'react';
import { useCallback } from 'react';
import { useCallback, useRef } from 'react';

import { RoomHistoryManager } from '../../../../../app/ui-utils/client';
import { withThrottling } from '../../../../../lib/utils/highOrderFunctions';
import { useToggleSelectAll } from '../../MessageList/contexts/SelectedMessagesContext';
import { useMessages } from '../../MessageList/hooks/useMessages';

export const useGetMore = (rid: string, atBottomRef: MutableRefObject<boolean>) => {
const ref: MutableRefObject<HTMLElement | null> = 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(
Expand Down
Original file line number Diff line number Diff line change
@@ -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<boolean>('showThreadsInMainChannel', false);
// const hideSysMesSetting = useSetting<MessageTypesValues[]>('Hide_System_Messages', []);
const room = useRoom();

Check failure on line 43 in apps/meteor/client/views/room/contextualBar/ExportMessages/ExportE2EEMessages.tsx

View workflow job for this annotation

GitHub Actions / 🔎 Code Check / Code Lint

'room' is assigned a value but never used
// const hideRoomSysMes: Array<MessageTypesValues> = Array.isArray(room.sysMes) ? room.sysMes : [];

// const hideSysMessages = useStableArray(mergeHideSysMessages(hideSysMesSetting, hideRoomSysMes));

const query: Mongo.Selector<IMessage> = 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<boolean>('showThreadsInMainChannel', false);

// const messages = useMessages({ rid: room._id });

// const query: Mongo.Selector<IMessage> = 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<FormValues>({
defaultValues: { type: 'file', format: 'html', from: { date: '', time: '' }, until: { date: '', time: '' } },
});

const outputOptions = useMemo<SelectOption[]>(
() => [
['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<HTMLFormElement>();

return (
<>
<ContextualbarHeader>
<ContextualbarIcon name='mail' />
<ContextualbarTitle id={`${formId}-title`}>{t('Export_Encrypted_Messages')}</ContextualbarTitle>
<ContextualbarClose onClick={closeTab} />
</ContextualbarHeader>
<>
<ContextualbarScrollableContent>
<form ref={formFocus} tabIndex={-1} aria-labelledby={`${formId}-title`} id={formId} onSubmit={handleSubmit(handleExport)}>
<FieldGroup>
<Field>
<FieldLabel htmlFor={typeField}>{t('Method')}</FieldLabel>
<FieldRow>
<Controller
name='type'
control={control}
render={({ field }) => (
<Select id={typeField} {...field} disabled placeholder={t('Type')} options={[['file', t('Export_as_file')]]} />
)}
/>
</FieldRow>
<FieldHint>Encrypted messages cannot be emailed.</FieldHint>
</Field>
<Field>
<FieldLabel flexGrow={0}>{t('From_date')}</FieldLabel>
<Box display='flex' mi='neg-x4'>
<Margins inline={4}>
<InputBox type='date' flexGrow={1} h='x20' {...register('from.date')} />
<InputBox type='time' flexGrow={1} h='x20' {...register('from.time')} />
</Margins>
</Box>
</Field>
<Field>
<FieldLabel flexGrow={0}>{t('Until_date')}</FieldLabel>
<Box display='flex' mi='neg-x4'>
<Margins inline={4}>
<InputBox type='date' flexGrow={1} h='x20' {...register('until.date')} />
<InputBox type='time' flexGrow={1} h='x20' {...register('until.time')} />
</Margins>
</Box>
</Field>
<Field>
<FieldLabel htmlFor={formatField}>{t('Output_format')}</FieldLabel>
<FieldRow>
<Controller
name='format'
control={control}
render={({ field }) => <Select {...field} id={formatField} placeholder={t('Format')} options={outputOptions} />}
/>
</FieldRow>
</Field>
<Callout>A maximum of 500 messages can be exported at a time from encrypted `room type`.</Callout>
</FieldGroup>
</form>
</ContextualbarScrollableContent>
<ContextualbarFooter>
<ButtonGroup stretch>
<Button onClick={() => closeTab()}>{t('Cancel')}</Button>
<Button form={formId} primary type='submit'>
{t('Export')}
</Button>
</ButtonGroup>
</ContextualbarFooter>
</>
</>
);
};

export default ExportE2EEMessages;
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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 = {
Expand Down

0 comments on commit 5a4d892

Please sign in to comment.