diff --git a/.gitignore b/.gitignore index e7ba4d641..25cef1d90 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ node_modules .vscode +.gitpod.yml diff --git a/README.md b/README.md index 08de1c2e3..81b81cd01 100644 --- a/README.md +++ b/README.md @@ -108,3 +108,8 @@ Similarly, the `api` package depends on the `auth` package. After changes to `au This setup provides a comprehensive environment for developing and testing the `EmbeddedChat` component, along with its associated `api` and `auth` packages. Enjoy exploring and enhancing the capabilities of `EmbeddedChat`! +### Contributors + + + + diff --git a/packages/api/rollup.config.js b/packages/api/rollup.config.js index 5e8a14d65..5351b95f5 100644 --- a/packages/api/rollup.config.js +++ b/packages/api/rollup.config.js @@ -1,13 +1,16 @@ import dts from 'rollup-plugin-dts' import esbuild from 'rollup-plugin-esbuild' import packageJson from './package.json' assert { type: 'json' }; +import path from 'path'; const name = packageJson.main.replace(/\.js$/, ''); const bundle = config => ({ ...config, input: 'src/index.ts', - external: id => !/^[./]/.test(id), + external: id => { + return id[0] !== '.' && !path.isAbsolute(id); + }, }) export default [ diff --git a/packages/api/src/EmbeddedChatApi.ts b/packages/api/src/EmbeddedChatApi.ts index 8fc9580c7..ce81a5427 100644 --- a/packages/api/src/EmbeddedChatApi.ts +++ b/packages/api/src/EmbeddedChatApi.ts @@ -1,6 +1,6 @@ import { Rocketchat } from '@rocket.chat/sdk'; import cloneArray from './cloneArray'; -import { IRocketChatAuthOptions, RocketChatAuth } from '@embeddedchat/auth'; +import { IRocketChatAuthOptions, RocketChatAuth, ApiError } from '@embeddedchat/auth'; // mutliple typing status can come at the same time they should be processed in order. let typingHandlerLock = 0; @@ -133,6 +133,10 @@ export default class EmbeddedChatApi { } return { status: 'success', me: data.me }; } catch (error) { + if (error instanceof ApiError && error.response?.status === 401) { + const authErrorRes = await error.response.json(); + return { error: authErrorRes?.error }; + } console.error(error); } } @@ -407,7 +411,7 @@ export default class EmbeddedChatApi { try { const { userId, authToken } = await this.auth.getCurrentUser() || {}; const response = await fetch( - `${this.host}/api/v1/channels.info?roomId=${this.rid}`, + `${this.host}/api/v1/rooms.info?roomId=${this.rid}`, { headers: { 'Content-Type': 'application/json', @@ -441,7 +445,8 @@ export default class EmbeddedChatApi { } = { query: undefined, field: undefined - }) { + }, isChannelPrivate = false) { + const roomType = isChannelPrivate ? 'groups' : 'channels' ; const endp = anonymousMode ? 'anonymousread' : 'messages'; const query = options?.query ? `&query=${JSON.stringify(options.query)}` @@ -452,7 +457,7 @@ export default class EmbeddedChatApi { try { const { userId, authToken } = await this.auth.getCurrentUser() || {}; const messages = await fetch( - `${this.host}/api/v1/channels.${endp}?roomId=${this.rid}${query}${field}`, + `${this.host}/api/v1/${roomType}.${endp}?roomId=${this.rid}${query}${field}`, { headers: { 'Content-Type': 'application/json', @@ -468,19 +473,20 @@ export default class EmbeddedChatApi { } } - async getThreadMessages(tmid: string) { + async getThreadMessages(tmid: string, isChannelPrivate = false) { return this.getMessages(false, { query: { tmid, }, - }); + }, isChannelPrivate); } - async getChannelRoles() { + async getChannelRoles(isChannelPrivate = false) { + const roomType = isChannelPrivate ? 'groups' : 'channels'; try { const { userId, authToken } = await this.auth.getCurrentUser() || {}; const roles = await fetch( - `${this.host}/api/v1/channels.roles?roomId=${this.rid}`, + `${this.host}/api/v1/${roomType}.roles?roomId=${this.rid}`, { headers: { 'Content-Type': 'application/json', @@ -796,11 +802,12 @@ export default class EmbeddedChatApi { } } - async getChannelMembers() { + async getChannelMembers(isChannelPrivate = false) { + const roomType = isChannelPrivate ? 'groups' : 'channels'; try { const { userId, authToken } = await this.auth.getCurrentUser() || {}; const response = await fetch( - `${this.host}/api/v1/channels.members?roomId=${this.rid}`, + `${this.host}/api/v1/${roomType}.members?roomId=${this.rid}`, { headers: { 'Content-Type': 'application/json', diff --git a/packages/auth/rollup.config.js b/packages/auth/rollup.config.js index c0d288ea8..ab1343496 100644 --- a/packages/auth/rollup.config.js +++ b/packages/auth/rollup.config.js @@ -1,6 +1,6 @@ import dts from 'rollup-plugin-dts' import esbuild from 'rollup-plugin-esbuild' - +import path from 'path'; import packageJson from './package.json' assert { type: 'json' }; const name = packageJson.main.replace(/\.js$/, ''); @@ -8,7 +8,9 @@ const name = packageJson.main.replace(/\.js$/, ''); const bundle = config => ({ ...config, input: 'src/index.ts', - external: id => !/^[./]/.test(id), + external: id => { + return id[0] !== '.' && !path.isAbsolute(id); + }, }) export default [ diff --git a/packages/auth/src/index.ts b/packages/auth/src/index.ts index f9f155135..91b9edfc0 100644 --- a/packages/auth/src/index.ts +++ b/packages/auth/src/index.ts @@ -1,3 +1,4 @@ export * from './auth'; export { default as RocketChatAuth } from './RocketChatAuth'; -export * from './IRocketChatAuthOptions'; \ No newline at end of file +export * from './IRocketChatAuthOptions'; +export {ApiError} from './Api'; \ No newline at end of file diff --git a/packages/react/src/components/AllThreads/AllThreads.js b/packages/react/src/components/AllThreads/AllThreads.js new file mode 100644 index 000000000..24a56dc02 --- /dev/null +++ b/packages/react/src/components/AllThreads/AllThreads.js @@ -0,0 +1,149 @@ +import React, { useState, useMemo } from 'react'; +import { css } from '@emotion/react'; +import classes from './AllThreads.module.css'; +import { Icon } from '../Icon'; +import { Box } from '../Box'; +import { ActionButton } from '../ActionButton'; +import { useMessageStore, useUserStore, useThreadsMessageStore } from '../../store'; +import { MessageBody } from '../Message/MessageBody'; +import { MessageMetrics } from '../Message/MessageMetrics'; +import MessageAvatarContainer from '../Message/MessageAvatarContainer'; +import MessageBodyContainer from '../Message/MessageBodyContainer'; +import MessageHeader from '../Message/MessageHeader'; + + +const MessageCss = css` + display: flex; + flex-direction: row; + align-items: flex-start; + padding-top: 0.5rem; + -webkit-padding-before: 0.5rem; + padding-block-start: 0.5rem; + padding-bottom: 0.25rem; + -webkit-padding-after: 0.25rem; + padding-block-end: 0.25rem; + padding-left: 1.25rem; + padding-right: 1.25rem; + padding-inline: 1.25rem; + cursor: pointer; + &:hover { + background: #f2f3f5; + } +`; + +const AllThreads = () => { + const showAvatar = useUserStore((state) => state.showAvatar); + const messages = useMessageStore((state) => state.messages); + const setShowAllThreads = useThreadsMessageStore((state) => state.setShowAllThreads); + const openThread = useMessageStore((state) => state.openThread); + const [text, setText] = useState(''); + + const toggleShowAllThreads = () => { + setShowAllThreads(false); + }; + + const handleOpenThread = (msg) => () => { + openThread(msg); + toggleShowAllThreads(false); + }; + + const handleInputChange = (e) => { + setText(e.target.value); + }; + + const filteredThreads = useMemo(() => { + return messages.filter((message) => + message.msg.toLowerCase().includes(text.toLowerCase()) + ); + }, [messages, text]); + + return ( + + + + + +

+ + + Threads + + + + +

+
+ + + + + + +
+ + + {filteredThreads.length === 0 ? ( + + + No threads found + + ) : (filteredThreads + .map((message) => ( + !message.t && message.tcount && ( + + {showAvatar && ( + + )} + + {} + + {message.attachments && message.attachments.length > 0 ? ( + message.file.name + ) : ( + message.msg + )} + + + + + + ) + )))} + +
+
+ ); +}; + +export default AllThreads; diff --git a/packages/react/src/components/AllThreads/AllThreads.module.css b/packages/react/src/components/AllThreads/AllThreads.module.css new file mode 100644 index 000000000..1ae7eaf6b --- /dev/null +++ b/packages/react/src/components/AllThreads/AllThreads.module.css @@ -0,0 +1,41 @@ +.component { + position: fixed; + right: 0; + top: 0; + width: 350px; + height: 100%; + overflow: hidden; + background-color: white; + box-shadow: -1px 0px 5px rgb(0 0 0 / 25%); + z-index: 100; +} + +.wrapContainer { + height: 100%; + display: flex; + flex-direction: column; +} + +.searchContainer { + display: flex; + align-items: center; + justify-content: center; + background-color: #fff; +} + +.textInput { + width: 75%; + height: 2.5rem; + border: none; + outline: none; +} + +.textInput::placeholder { + padding-left: 5px; +} + +@media (max-width: 550px) { + .component { + width: 100vw; + } +} diff --git a/packages/react/src/components/Attachments/AttachmentWindow.js b/packages/react/src/components/Attachments/AttachmentWindow.js index 5d192af32..9536ace88 100644 --- a/packages/react/src/components/Attachments/AttachmentWindow.js +++ b/packages/react/src/components/Attachments/AttachmentWindow.js @@ -1,10 +1,13 @@ import React, { useContext, useState } from 'react'; import useAttachmentWindowStore from '../../store/attachmentwindow'; import ValidateComponent from './AttachmentWindow/validateComponent'; +import Backdrop from './AttachmentWindow/Backdrop'; import RCContext from '../../context/RCInstance'; import styles from './AttachmentWindow.module.css'; import { useMessageStore } from '../../store'; import { Box } from '../Box'; +import { Icon } from '../Icon'; +import { css } from '@emotion/react'; function AttachmentWindow() { const { RCInstance, ECOptions } = useContext(RCContext); @@ -36,72 +39,103 @@ function AttachmentWindow() { setData(null); }; return ( - -