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"