diff --git a/README.md b/README.md
index 2e2907e7..64d25373 100644
--- a/README.md
+++ b/README.md
@@ -10,4 +10,3 @@ following content:
```bash
REACT_APP_API_HOST="http://localhost:3000"
```
-
diff --git a/cypress/integration/admin-tools.cy.tsx b/cypress/integration/admin-tools.cy.tsx
deleted file mode 100644
index 9f3a4c15..00000000
--- a/cypress/integration/admin-tools.cy.tsx
+++ /dev/null
@@ -1,94 +0,0 @@
-///
-import { List } from 'immutable';
-
-import { ImmutableMember, Member } from '../../src';
-import Chatbox from '../../src/components/Chatbox/Chatbox';
-import {
- adminToolsContainerCypress,
- cancelDialogButtonCypress,
- clearChatButtonCypress,
- confirmDialogButtonCypress,
- dataCyWrapper,
- exportChatButtonCypress,
-} from '../../src/config/selectors';
-import { verifyDownloadedChat } from '../../src/test/utils/utils';
-import { CHAT_ID, CHAT_MESSAGES, spyMethod } from '../fixtures/chat_messages';
-import { MEMBERS } from '../fixtures/members';
-
-const mountChatbox = (showTools: boolean, emptyData = false): void => {
- cy.mount(
- ,
- );
-};
-
-describe('Admin tools', () => {
- it('should show admin tools', () => {
- // show tools
- mountChatbox(true);
- cy.get(dataCyWrapper(adminToolsContainerCypress))
- .should('exist')
- .and('be.visible');
- });
-
- it('should not show admin tools', () => {
- // do not show tools
- mountChatbox(false);
- cy.get(dataCyWrapper(adminToolsContainerCypress)).should('not.exist');
- });
-
- it('should show export chat and download csv', () => {
- // show tools
- mountChatbox(true);
- cy.get(dataCyWrapper(exportChatButtonCypress))
- .should('exist')
- .and('be.visible')
- // download file
- .click();
-
- // get file name from data-cy-filename attribute and check local csv
- cy.get(dataCyWrapper(exportChatButtonCypress))
- .should('have.attr', 'data-cy-filename')
- .then((filename) => {
- verifyDownloadedChat(filename.toString(), CHAT_MESSAGES.length);
- });
- });
-
- it('should not show export chat with empty chat', () => {
- // show tools but with no data
- mountChatbox(true, true);
- cy.get(dataCyWrapper(adminToolsContainerCypress)).should('exist');
- cy.get(dataCyWrapper(exportChatButtonCypress)).should('not.exist');
- });
-
- it('should show clear chat button', () => {
- const clearChatSpy = spyMethod('clearChatSpy');
- cy.mount(
- ,
- );
- cy.get(dataCyWrapper(clearChatButtonCypress))
- .should('exist')
- .and('be.visible')
- // click button
- .click();
- cy.get(dataCyWrapper(cancelDialogButtonCypress)).should('be.visible');
- cy.get(dataCyWrapper(confirmDialogButtonCypress))
- .should('be.visible')
- // validate popup
- .click();
- // check that spy method has been called
- cy.get('@clearChatSpy').should('have.been.called');
- });
-});
diff --git a/cypress/integration/render-ui.cy.tsx b/cypress/integration/render-ui.cy.tsx
index 854ff77c..ba6ff489 100644
--- a/cypress/integration/render-ui.cy.tsx
+++ b/cypress/integration/render-ui.cy.tsx
@@ -1,4 +1,4 @@
-///
+///
import { List } from 'immutable';
import { v4 } from 'uuid';
@@ -6,7 +6,6 @@ import { ImmutableMember, Member } from '../../src';
import Chatbox from '../../src/components/Chatbox/Chatbox';
import {
dataCyWrapper,
- exportChatButtonCypress,
inputTextFieldTextAreaCypress,
messageIdCyWrapper,
messagesContainerCypress,
@@ -88,45 +87,3 @@ describe('Messages container', () => {
});
});
});
-
-describe('Export Chat button', () => {
- it('should show export button', () => {
- cy.mount(
- ,
- ).then(() =>
- cy.get(dataCyWrapper(exportChatButtonCypress)).should('exist'),
- );
- });
-
- it('should not show button when chat is empty', () => {
- cy.mount(
- ,
- ).then(() =>
- cy.get(dataCyWrapper(exportChatButtonCypress)).should('not.exist'),
- );
- });
-
- it('should not show export button', () => {
- cy.mount(
- ,
- ).then(() =>
- cy.get(dataCyWrapper(exportChatButtonCypress)).should('not.exist'),
- );
- });
-});
diff --git a/example/package.json b/example/package.json
index a14e5630..83a78955 100644
--- a/example/package.json
+++ b/example/package.json
@@ -11,7 +11,7 @@
},
"dependencies": {
"@graasp/chatbox": "link:..",
- "@graasp/query-client": "github:graasp/graasp-query-client",
+ "@graasp/query-client": "github:graasp/graasp-query-client#202/exportChatHook",
"@material-ui/core": "4.12.4",
"@material-ui/icons": "4.11.2",
"@material-ui/lab": "4.0.0-alpha.60",
@@ -25,6 +25,7 @@
"immutable": "4.0.0",
"react": "17.0.2",
"react-dom": "17.0.2",
+ "react-i18next": "11.18.1",
"react-scripts": "5.0.1",
"typescript": "4.7.4"
},
diff --git a/example/src/components/ChatboxTest.tsx b/example/src/components/ChatboxTest.tsx
index e27f7c29..08d26069 100644
--- a/example/src/components/ChatboxTest.tsx
+++ b/example/src/components/ChatboxTest.tsx
@@ -1,4 +1,5 @@
-import { FC, useState } from 'react';
+import { FC, useEffect, useMemo, useState } from 'react';
+import { I18nextProvider } from 'react-i18next';
import {
Checkbox,
@@ -16,6 +17,7 @@ import {
import { MentionButton } from '@graasp/chatbox';
import { MUTATION_KEYS } from '@graasp/query-client';
import { ChatMention } from '@graasp/query-client/dist/src/types';
+import buildI18n, { namespaces } from '@graasp/translations';
import {
DEFAULT_CHAT_ID,
@@ -31,6 +33,12 @@ const ChatboxTest: FC = () => {
const [lang, setLang] = useState(DEFAULT_LANG);
const [chatId, setChatId] = useState(DEFAULT_CHAT_ID);
+ // get chatId from url
+ useEffect(() => {
+ const choppedUrl = window.location.pathname.split('/');
+ setChatId(choppedUrl[choppedUrl.length - 1]);
+ }, [window.location.pathname]);
+
const useStyles = makeStyles((theme) => ({
container: {
display: 'flex',
@@ -66,6 +74,7 @@ const ChatboxTest: FC = () => {
const classes = useStyles();
const { data: currentMember } = hooks.useCurrentMember();
const memberId = currentMember?.get('id') as string;
+
// mutations to handle the mentions
const { mutate: patchMentionMutate } = useMutation<
ChatMention,
@@ -74,6 +83,7 @@ const ChatboxTest: FC = () => {
>(MUTATION_KEYS.PATCH_MENTION);
const patchMentionFunction = (args: { id: string; status: string }): void =>
patchMentionMutate({ memberId, ...args });
+
const { mutate: deleteMentionMutate } = useMutation<
ChatMention,
unknown,
@@ -81,6 +91,7 @@ const ChatboxTest: FC = () => {
>(MUTATION_KEYS.DELETE_MENTION);
const deleteMentionFunction = (mentionId: string): void =>
deleteMentionMutate({ memberId, mentionId });
+
const { mutate: clearAllMentionsMutate } = useMutation<
ChatMention[],
unknown,
@@ -102,78 +113,93 @@ const ChatboxTest: FC = () => {
}
};
+ const i18n = useMemo(() => {
+ const i18nInstance = buildI18n(namespaces.chatbox);
+ i18nInstance.changeLanguage(lang);
+ return i18nInstance;
+ }, [lang]);
+
return (
-
-
-
Test parameters
-
setChatId(target.value)}
- />
- }
- label="Chat Id"
- labelPlacement="top"
- />
-
- Language
- setLang(target.value)}
- >
- } label="French" />
- } label="English" />
-
-
-
- Chatbox params
+
+
+
+ Test parameters
+
+ Current User: {currentMember?.get('name')}
+
setShowTools(!showTools)}
+ setChatId(target.value)}
/>
}
- label="Show Admin tools"
+ label="Chat Id"
+ labelPlacement="top"
/>
-
+ Language
+ setLang(target.value)}
+ >
+ } label="French" />
+ }
+ label="English"
/>
- }
- labelPlacement="top"
- label="Panel Width"
+
+
+
+ Chatbox params
+ setShowTools(!showTools)}
+ />
+ }
+ label="Show Admin tools"
+ />
+
+ }
+ labelPlacement="top"
+ label="Panel Width"
+ />
+
+
-
-
-
-
-
+
+
+
+
-
+
);
};
diff --git a/example/src/components/ChatboxWrapper.tsx b/example/src/components/ChatboxWrapper.tsx
index 5f758287..01153c21 100644
--- a/example/src/components/ChatboxWrapper.tsx
+++ b/example/src/components/ChatboxWrapper.tsx
@@ -7,7 +7,6 @@ import {
PartialNewChatMessage,
} from '@graasp/query-client/dist/src/types';
-import { ClearChatHookType } from '../../../src/types';
import { DEFAULT_LANG } from '../config/constants';
import { hooks, useMutation } from '../config/queryClient';
@@ -27,15 +26,16 @@ const ChatboxWrapper: FC
= ({
// use hooks
const { data: currentMember } = hooks.useCurrentMember();
const { data: chat } = hooks.useItemChat(chatId);
- const memberships = hooks.useItemMemberships(chatId).data;
// get chat messages
const chatMessages = chat?.messages;
+ const { data: memberships } = hooks.useItemMemberships(chatId);
+
const memberIds: string[] =
- memberships?.map((m) => m.memberId)?.toArray() || [];
+ (memberships?.size && memberships?.map((m) => m.memberId)?.toArray()) || [];
+ const members = hooks.useMembers(memberIds).data;
const member = new ImmutableMember(currentMember);
- const members = hooks.useMembers(memberIds).data;
const {
mutate: sendMessage,
@@ -52,9 +52,6 @@ const ChatboxWrapper: FC = ({
}: { mutate: (message: PartialChatMessage) => void } = useMutation(
MUTATION_KEYS.PATCH_ITEM_CHAT_MESSAGE,
);
- const { mutate: clearChat }: { mutate: ClearChatHookType } = useMutation(
- MUTATION_KEYS.CLEAR_ITEM_CHAT,
- );
return (
= ({
sendMessageFunction={sendMessage}
deleteMessageFunction={deleteMessage}
editMessageFunction={editMessage}
- clearChatFunction={clearChat}
useAvatarHook={hooks.useAvatar as AvatarHookType}
/>
);
diff --git a/example/src/config/constants.js b/example/src/config/constants.ts
similarity index 85%
rename from example/src/config/constants.js
rename to example/src/config/constants.ts
index a027e131..edfeeb50 100644
--- a/example/src/config/constants.js
+++ b/example/src/config/constants.ts
@@ -16,5 +16,5 @@ export const TOOLS_SIZE = 64;
export const GRAASP_PANEL_WIDTH = 290;
-export const DEFAULT_CHAT_ID = '39370f67-2153-4ab9-9679-b1966542d27d';
+export const DEFAULT_CHAT_ID = 'mock-id-replace-me!';
export const DEFAULT_LANG = 'fr';
diff --git a/package.json b/package.json
index 2b8c55cd..915084e2 100644
--- a/package.json
+++ b/package.json
@@ -47,7 +47,7 @@
"react-dom": "*"
},
"dependencies": {
- "@graasp/translations": "github:graasp/graasp-translations",
+ "@graasp/translations": "github:graasp/graasp-translations#34/chatboxTranslations",
"@graasp/ui": "github:graasp/graasp-ui",
"clsx": "1.1.1",
"i18next": "21.8.1",
@@ -72,7 +72,7 @@
"@cypress/instrument-cra": "1.4.0",
"@cypress/react": "5.12.4",
"@cypress/webpack-dev-server": "1.8.4",
- "@graasp/query-client": "github:graasp/graasp-query-client",
+ "@graasp/query-client": "github:graasp/graasp-query-client#202/exportChatHook",
"@graasp/sdk": "github:graasp/graasp-sdk",
"@material-ui/core": "4.12.4",
"@material-ui/icons": "4.11.2",
@@ -96,6 +96,7 @@
"@typescript-eslint/parser": "5.30.7",
"cross-env": "7.0.3",
"cypress": "10.6.0",
+ "env-cmd": "10.1.0",
"eslint": "8.23.0",
"eslint-config-prettier": "8.3.0",
"eslint-config-standard": "16.0.3",
@@ -122,8 +123,7 @@
},
"resolutions": {
"@types/react": "17.0.2",
- "@graasp/sdk": "github:graasp/graasp-sdk#main",
- "@graasp/translations": "github:graasp/graasp-translations#main"
+ "@graasp/sdk": "github:graasp/graasp-sdk#main"
},
"files": [
"dist"
diff --git a/src/components/Chatbox/AdminTools.tsx b/src/components/Chatbox/AdminTools.tsx
deleted file mode 100644
index f0892c40..00000000
--- a/src/components/Chatbox/AdminTools.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import { FC } from 'react';
-
-import Box from '@material-ui/core/Box';
-
-import { adminToolsContainerCypress } from '../../config/selectors';
-import { ToolVariants, ToolVariantsType } from '../../types';
-import ClearChat from './ClearChat';
-import ExportChat from './ExportChat';
-
-type Props = {
- variant?: ToolVariantsType;
-};
-
-const AdminTools: FC = ({ variant = ToolVariants.BUTTON }) => {
- return (
-
-
-
-
- );
-};
-
-export default AdminTools;
diff --git a/src/components/Chatbox/Chatbox.tsx b/src/components/Chatbox/Chatbox.tsx
index 1c0fabfa..a336c5c4 100644
--- a/src/components/Chatbox/Chatbox.tsx
+++ b/src/components/Chatbox/Chatbox.tsx
@@ -18,7 +18,6 @@ import { HooksContextProvider } from '../../context/HooksContext';
import { MessagesContextProvider } from '../../context/MessagesContext';
import type { ClearChatHookType, ImmutableMember } from '../../types';
import { AvatarHookType, ChatMessageList } from '../../types';
-import AdminTools from './AdminTools';
import Header from './Header';
import InputBar from './InputBar';
import Messages from './Messages';
@@ -112,7 +111,6 @@ const Chatbox: FC = ({
sendMessageFunction={sendMessageFunction}
editMessageFunction={editMessageFunction}
/>
- {showAdminTools && }
>
diff --git a/src/components/Chatbox/ClearChat.tsx b/src/components/Chatbox/ClearChat.tsx
deleted file mode 100644
index 8b4d0161..00000000
--- a/src/components/Chatbox/ClearChat.tsx
+++ /dev/null
@@ -1,92 +0,0 @@
-import { FC, ReactElement, useState } from 'react';
-import { useTranslation } from 'react-i18next';
-
-import { Box, Tooltip, Typography } from '@material-ui/core';
-import IconButton from '@material-ui/core/IconButton';
-import { DeleteForever } from '@material-ui/icons';
-
-import { Button } from '@graasp/ui';
-
-import { clearChatButtonCypress } from '../../config/selectors';
-import { useHooksContext } from '../../context/HooksContext';
-import { useMessagesContext } from '../../context/MessagesContext';
-import { ToolVariants, ToolVariantsType } from '../../types';
-import ConfirmationDialog from '../common/ConfirmationDialog';
-import ExportChat from './ExportChat';
-
-type Prop = {
- variant?: ToolVariantsType;
-};
-
-const ClearChat: FC = ({ variant = ToolVariants.BUTTON }) => {
- const [openConfirmation, setOpenConfirmation] = useState(false);
- const { t } = useTranslation();
- const { clearChatHook: clearChat } = useHooksContext();
- const { chatId, messages } = useMessagesContext();
-
- if (!clearChat || !messages || !messages?.size) {
- return null;
- }
-
- const handleClearChat = (): void => {
- clearChat?.(chatId);
- };
-
- const getContent = (contentType: ToolVariantsType): ReactElement => {
- const text = t('Clear Chat');
-
- switch (contentType) {
- case ToolVariants.ICON:
- return (
-
- setOpenConfirmation(true)}
- >
-
-
-
- );
- case ToolVariants.BUTTON:
- return (
-
- );
- }
- };
-
- return (
- <>
- {getContent(variant)}
-
-
- {t(
- 'Do you want to clear all messages from this chat? This action is non-reversible. Use the button below if you want to make a backup.',
- )}
-
-
-
- }
- confirmText={t('Clear Chat')}
- onConfirm={(): void => {
- setOpenConfirmation(false);
- handleClearChat();
- }}
- onCancel={(): void => {
- setOpenConfirmation(false);
- }}
- />
- >
- );
-};
-
-export default ClearChat;
diff --git a/src/components/Chatbox/ExportChat.tsx b/src/components/Chatbox/ExportChat.tsx
deleted file mode 100644
index 0c53d76d..00000000
--- a/src/components/Chatbox/ExportChat.tsx
+++ /dev/null
@@ -1,110 +0,0 @@
-import moment from 'moment';
-
-import { FC, ReactElement, useState } from 'react';
-import { CSVLink as CsvLink } from 'react-csv';
-import { useTranslation } from 'react-i18next';
-
-import { IconButton, Tooltip } from '@material-ui/core';
-import { makeStyles } from '@material-ui/core/styles';
-import { GetApp } from '@material-ui/icons';
-
-import { Button } from '@graasp/ui';
-
-import { exportChatButtonCypress } from '../../config/selectors';
-import {
- DEFAULT_USER_NAME,
- EXPORT_CSV_HEADERS,
- EXPORT_DATE_FORMAT,
-} from '../../constants';
-import { useMessagesContext } from '../../context/MessagesContext';
-import {
- ExportedChatMessage,
- ToolVariants,
- ToolVariantsType,
-} from '../../types';
-import { normalizeMentions } from '../../utils/mentions';
-
-const useStyles = makeStyles({
- link: {
- textDecoration: 'none',
- width: 'fit-content',
- },
-});
-
-type Props = {
- variant?: ToolVariantsType;
- text?: string;
-};
-
-const ExportChat: FC = ({ variant = ToolVariants.ICON, text }) => {
- const { messages, chatId, members } = useMessagesContext();
- const [filename, setFilename] = useState('');
- const { t } = useTranslation();
- const classes = useStyles();
-
- // if any of the used values is falsy we should not display the button
- if (!messages || !chatId || !members) {
- return null;
- }
-
- const csvMessages: ExportedChatMessage[] = messages
- .toArray()
- .map((message) => {
- const creatorName =
- members.find((m) => m.id === message.creator)?.name ||
- DEFAULT_USER_NAME;
- return {
- ...message,
- body: normalizeMentions(message.body),
- creatorName,
- };
- });
- // render nothing if there is no data
- if (!csvMessages.length) {
- return null;
- }
-
- // generate file name when user clicks on the button
- const onClick = (): void => {
- const currentDate = moment().format(EXPORT_DATE_FORMAT);
- setFilename(`${currentDate}_chat_${chatId}.csv`);
- };
-
- const getContent = (variant: ToolVariantsType): ReactElement | null => {
- const contentText: string = text || t('Download Chat');
- switch (variant) {
- case ToolVariants.ICON:
- return (
-
-
-
-
-
- );
- case ToolVariants.BUTTON:
- return ;
- default:
- return null;
- }
- };
-
- return (
-
- {getContent(variant)}
-
- );
-};
-
-export default ExportChat;
diff --git a/src/components/Chatbox/Header.tsx b/src/components/Chatbox/Header.tsx
index d80fbce1..067eff75 100644
--- a/src/components/Chatbox/Header.tsx
+++ b/src/components/Chatbox/Header.tsx
@@ -10,7 +10,6 @@ import ChatIcon from '@material-ui/icons/Chat';
import { CHATBOX } from '@graasp/translations';
import { HEADER_HEIGHT } from '../../constants';
-import ExportChat from './ExportChat';
const useStyles = makeStyles((theme) => ({
root: {
@@ -46,7 +45,6 @@ const Header: FC = ({ title }) => {
{title || t(CHATBOX.CHATBOX_HEADER)}
-
diff --git a/src/components/Chatbox/Input.tsx b/src/components/Chatbox/Input.tsx
index 09afb6bb..e806a95e 100644
--- a/src/components/Chatbox/Input.tsx
+++ b/src/components/Chatbox/Input.tsx
@@ -1,9 +1,10 @@
import clsx from 'clsx';
-import React, { FC, ReactElement, RefObject, useEffect } from 'react';
+import React, { FC, ReactElement, RefObject, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import {
Mention,
+ MentionItem,
MentionsInput,
OnChangeHandlerFunc,
SuggestionDataItem,
@@ -31,7 +32,6 @@ import {
} from '../../constants';
import { useCurrentMemberContext } from '../../context/CurrentMemberContext';
import { useMessagesContext } from '../../context/MessagesContext';
-import { getAllMentions, normalizeMentions } from '../../utils/mentions';
type Props = {
id?: string;
@@ -78,6 +78,7 @@ const Input: FC = ({
const inputRadius = theme.spacing(0.5);
const inputStyle = {
width: '100%',
+ height: '100%',
minWidth: '0px',
// mentions
control: {
@@ -88,7 +89,8 @@ const Input: FC = ({
border: '1px solid silver',
width: '100%',
overflow: 'auto',
- height: '70px',
+ height: '100%',
+ maxHeight: '30vh',
lineHeight: 'inherit',
borderRadius: inputRadius,
},
@@ -97,7 +99,8 @@ const Input: FC = ({
border: '1px solid transparent',
boxSizing: 'border-box',
overflow: 'hidden',
- height: '70px',
+ height: '100%',
+ maxHeight: '30vh',
},
suggestions: {
@@ -128,6 +131,8 @@ const Input: FC = ({
const { members } = useMessagesContext();
const { id: currentMemberId } = useCurrentMemberContext();
const { t } = useTranslation();
+ const [currentMentions, setCurrentMentions] = useState([]);
+ const [plainTextMessage, setPlainTextMessage] = useState('');
// exclude self from suggestions and add @all pseudo member
const memberSuggestions: SuggestionDataItem[] = [
@@ -148,15 +153,16 @@ const Input: FC = ({
const onSend = (): void => {
if (textInput) {
- const mentions = getAllMentions(textInput).map(({ id }) => id);
- let expandedMentions: string[] = mentions;
+ let expandedMentions: string[] = currentMentions;
// expand '@all' to all members in mentions array (skip if there are no members)
- if (mentions.includes(ALL_MEMBERS_ID) && members?.size) {
+ if (currentMentions.includes(ALL_MEMBERS_ID) && members?.size) {
expandedMentions = members.map((m) => m.id).toArray();
}
sendMessageFunction?.({ message: textInput, mentions: expandedMentions });
// reset input content
setTextInput('');
+ setPlainTextMessage('');
+ setCurrentMentions([]);
}
};
@@ -165,9 +171,15 @@ const Input: FC = ({
_: {
target: { value: string };
},
+ // new value of the field
newValue: string,
+ // newPlainTextValue of the field
+ newPlainTextValue: string,
+ newMentions: MentionItem[],
): void => {
setTextInput(newValue);
+ setPlainTextMessage(newPlainTextValue);
+ setCurrentMentions(newMentions.map(({ id }) => id));
};
// catch {enter} key press to send messages
@@ -191,9 +203,8 @@ const Input: FC = ({
// when the textInput is empty, return a text with just a whitespace
// to keep the height of the element the same
let helperText = ' ';
- const normalizedTextInput = normalizeMentions(textInput);
- if (textInput) {
- helperText = normalizedTextInput.length.toString();
+ if (textInput && plainTextMessage) {
+ helperText = plainTextMessage.length.toString();
// append the max message size
if (isMessageTooLong) {
// there is a "space" before the message
diff --git a/src/components/Mentions/MentionsTable.tsx b/src/components/Mentions/MentionsTable.tsx
index 4a3a9647..5ac36099 100644
--- a/src/components/Mentions/MentionsTable.tsx
+++ b/src/components/Mentions/MentionsTable.tsx
@@ -25,7 +25,7 @@ import {
import { CHATBOX } from '@graasp/translations';
import { Button } from '@graasp/ui';
-import { normalizeMentions } from '../../utils/mentions';
+import MessageBody from '../Chatbox/MessageBody';
import ConfirmationDialog from '../common/ConfirmationDialog';
const useStyles = makeStyles({
@@ -77,6 +77,7 @@ const MentionsTable: FC = ({
itemId: getIdsFromPath(m.itemPath).slice(-1)[0],
chatOpen: true,
});
+ markAsRead(m.id);
window.location.href = link;
}}
>
@@ -85,7 +86,9 @@ const MentionsTable: FC = ({
)}
- {normalizeMentions(m.message)}
+
+
+
{m.creator}
diff --git a/src/index.ts b/src/index.ts
index a35ee8a8..0a352b5c 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,6 +1,9 @@
export { default } from './components/Chatbox/Chatbox';
+
+// Mention button providing dialog with member mentions table
+// should be placed in the header next to the profile picture
export { default as MentionButton } from './components/Mentions/MentionButton';
export type { Member, ChatMessage, AvatarHookType } from './types';
-export { ImmutableMember } from './types';
+export { ImmutableMember, ToolVariants } from './types';
export * from './utils/mentions';
diff --git a/src/types.ts b/src/types.ts
index 37e275b7..7c9dfbca 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -2,7 +2,10 @@ import { List, Record } from 'immutable';
import { UseQueryResult } from 'react-query';
-import { ChatMessageRecord } from '@graasp/query-client/dist/src/types';
+import {
+ ChatMessageRecord,
+ ExportedItemChat,
+} from '@graasp/query-client/dist/src/types';
import { UUID } from '@graasp/ui/dist/types';
import { BUTTON_VARIANT, ICON_VARIANT } from './constants';
@@ -44,6 +47,10 @@ export type AvatarHookType = (args: {
size?: string;
}) => UseQueryResult;
export type ClearChatHookType = (id: UUID) => void;
+export type ExportChatHookType = (
+ id: UUID,
+ options: { enabled: boolean },
+) => UseQueryResult;
export const ToolVariants = {
ICON: ICON_VARIANT,
diff --git a/src/utils/mentions.ts b/src/utils/mentions.ts
index 3bd66722..46549571 100644
--- a/src/utils/mentions.ts
+++ b/src/utils/mentions.ts
@@ -1,22 +1,10 @@
-const mentionRegEx =
- /\w+)>\[(?[\da-f]{8}-[\da-f]{4}-4[\da-f]{3}-[89ab][\da-f]{3}-[\da-f]{12})]/i;
-
-const mentionCodeRegEx =
- /`\w+)>\[(?[\da-f]{8}-[\da-f]{4}-4[\da-f]{3}-[89ab][\da-f]{3}-[\da-f]{12})]`/i;
export const getMention = (textContent: string): RegExpMatchArray | null =>
- textContent.match(mentionRegEx);
+ textContent.match(
+ /[\s\w]+)>\[(?[\da-f]{8}-[\da-f]{4}-4[\da-f]{3}-[89ab][\da-f]{3}-[\da-f]{12})]/i,
+ );
-export const getAllMentions = (
- textContent: string,
-): { id: string; name: string }[] => {
- const arr = Array.from(
- textContent.matchAll(new RegExp(mentionRegEx, 'g')),
- ).map((match) => ({
- id: match.groups?.id || '',
- name: match.groups?.name || '',
- }));
- return arr.filter(({ id, name }) => id && name);
+export const normalizeMentions = (message: string): string => {
+ const regexMentions =
+ /`\[([\da-f]{8}-[\da-f]{4}-4[\da-f]{3}-[89ab][\da-f]{3}-[\da-f]{12})]`/gi;
+ return message.replace(regexMentions, '@$1');
};
-
-export const normalizeMentions = (message: string): string =>
- message.replaceAll(new RegExp(mentionCodeRegEx, 'g'), '@$');
diff --git a/yarn.lock b/yarn.lock
index 62ae159b..0cfd7671 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2177,9 +2177,9 @@ __metadata:
"@cypress/instrument-cra": 1.4.0
"@cypress/react": 5.12.4
"@cypress/webpack-dev-server": 1.8.4
- "@graasp/query-client": "github:graasp/graasp-query-client"
+ "@graasp/query-client": "github:graasp/graasp-query-client#202/exportChatHook"
"@graasp/sdk": "github:graasp/graasp-sdk"
- "@graasp/translations": "github:graasp/graasp-translations"
+ "@graasp/translations": "github:graasp/graasp-translations#34/chatboxTranslations"
"@graasp/ui": "github:graasp/graasp-ui"
"@material-ui/core": 4.12.4
"@material-ui/icons": 4.11.2
@@ -2204,6 +2204,7 @@ __metadata:
clsx: 1.1.1
cross-env: 7.0.3
cypress: 10.6.0
+ env-cmd: 10.1.0
eslint: 8.23.0
eslint-config-prettier: 8.3.0
eslint-config-standard: 16.0.3
@@ -2250,9 +2251,9 @@ __metadata:
languageName: unknown
linkType: soft
-"@graasp/query-client@github:graasp/graasp-query-client":
+"@graasp/query-client@github:graasp/graasp-query-client#202/exportChatHook":
version: 0.1.0
- resolution: "@graasp/query-client@https://github.com/graasp/graasp-query-client.git#commit=6ef71cc527741f4cce266e28265779149932d4f4"
+ resolution: "@graasp/query-client@https://github.com/graasp/graasp-query-client.git#commit=a51e6eee76b7063e813bddf78ccd25f39539634f"
dependencies:
"@graasp/sdk": "github:graasp/graasp-sdk"
"@graasp/translations": "github:graasp/graasp-translations.git"
@@ -2261,14 +2262,12 @@ __metadata:
crypto-js: 4.1.1
http-status-codes: 2.2.0
immutable: 4.0.0
- jest-immutable-matchers: 3.0.0
- js-cookie: 3.0.1
qs: 6.10.3
react-query: 3.34.19
uuid: 8.3.2
peerDependencies:
react: ^17.0.0
- checksum: c2224dc891e89c93b07469de1af99d0de53b71a7114a14808246ff3bb1e45744c3d49359114611c00a2a6c0a034d87f09d4d5b803a90b617d9dd276ca1deafe5
+ checksum: 1a4e640d3af2834b6fdbf774414840b319c1e6901c2cc38fc37094bdfe6e625fb4ca225dae0f656d69cab541711ec90d0d9f371fae40f32223ff3fa3a3d05680
languageName: node
linkType: hard
@@ -2289,7 +2288,16 @@ __metadata:
languageName: node
linkType: hard
-"@graasp/translations@github:graasp/graasp-translations#main":
+"@graasp/translations@github:graasp/graasp-translations#34/chatboxTranslations":
+ version: 0.1.0
+ resolution: "@graasp/translations@https://github.com/graasp/graasp-translations.git#commit=f180be4abd25f91e3d0eef68638eeace1b737448"
+ dependencies:
+ i18next: 21.8.1
+ checksum: 2c00fc7cff04584a07d66caabcb55d95f2eb2ead9071e28b9053c37b3f055bf056469a40fbd94f63d3091864f549854955ee6bc604c941fe02d397dbc51a6e66
+ languageName: node
+ linkType: hard
+
+"@graasp/translations@github:graasp/graasp-translations.git":
version: 0.1.0
resolution: "@graasp/translations@https://github.com/graasp/graasp-translations.git#commit=1eb30447fca754c2b418bfa6d86772348952ccf0"
dependencies:
@@ -5986,6 +5994,13 @@ __metadata:
languageName: node
linkType: hard
+"commander@npm:^4.0.0":
+ version: 4.1.1
+ resolution: "commander@npm:4.1.1"
+ checksum: d7b9913ff92cae20cb577a4ac6fcc121bd6223319e54a40f51a14740a681ad5c574fd29a57da478a5f234a6fa6c52cbf0b7c641353e03c648b1ae85ba670b977
+ languageName: node
+ linkType: hard
+
"commander@npm:^5.1.0":
version: 5.1.0
resolution: "commander@npm:5.1.0"
@@ -7503,6 +7518,18 @@ __metadata:
languageName: node
linkType: hard
+"env-cmd@npm:10.1.0":
+ version: 10.1.0
+ resolution: "env-cmd@npm:10.1.0"
+ dependencies:
+ commander: ^4.0.0
+ cross-spawn: ^7.0.0
+ bin:
+ env-cmd: bin/env-cmd.js
+ checksum: efef55074250f14cfc0b80c98dd98f1f056a80e1f3c7db14097ceefdd2d56c766bbb83b54c22932112707f41b36ce2e923f8015d69e62a75c12d98640b972e75
+ languageName: node
+ linkType: hard
+
"env-paths@npm:^2.2.0":
version: 2.2.1
resolution: "env-paths@npm:2.2.1"
@@ -9338,7 +9365,7 @@ __metadata:
dependencies:
"@babel/plugin-syntax-object-rest-spread": 7.8.3
"@graasp/chatbox": "link:.."
- "@graasp/query-client": "github:graasp/graasp-query-client"
+ "@graasp/query-client": "github:graasp/graasp-query-client#202/exportChatHook"
"@material-ui/core": 4.12.4
"@material-ui/icons": 4.11.2
"@material-ui/lab": 4.0.0-alpha.60
@@ -9352,6 +9379,7 @@ __metadata:
immutable: 4.0.0
react: 17.0.2
react-dom: 17.0.2
+ react-i18next: 11.18.1
react-scripts: 5.0.1
typescript: 4.7.4
peerDependencies:
@@ -10847,16 +10875,6 @@ __metadata:
languageName: node
linkType: hard
-"jest-immutable-matchers@npm:3.0.0":
- version: 3.0.0
- resolution: "jest-immutable-matchers@npm:3.0.0"
- peerDependencies:
- immutable: ">=3.0.0"
- jest: ">=21.0.0"
- checksum: 28c30e0bdb1637e3e1bf4a2b6f02cbc66ba6f3b4cb2c204968d13c591e0fabfdd0b68ee5fc1af49ba23ad702c0543676d7e70a6d19100193d2fafb1e7eb5621f
- languageName: node
- linkType: hard
-
"jest-jasmine2@npm:^27.5.1":
version: 27.5.1
resolution: "jest-jasmine2@npm:27.5.1"