diff --git a/package.json b/package.json
index 1553bfedca..b662b1e6db 100644
--- a/package.json
+++ b/package.json
@@ -10,6 +10,7 @@
"@floating-ui/react-dom-interactions": "^0.13.1",
"@headlessui/react": "^1.7.4",
"@storybook/addon-viewport": "^6.5.13",
+ "@tanstack/react-query": "4.5.0",
"@tanstack/react-table": "^8.7.9",
"@types/react-pdf": "^5.7.2",
"axios": "^0.21.0",
diff --git a/src/pages/App/App.tsx b/src/pages/App/App.tsx
index 5e0c242064..7e0f058f61 100644
--- a/src/pages/App/App.tsx
+++ b/src/pages/App/App.tsx
@@ -16,6 +16,13 @@ import {
NotificationsHandler,
} from "./handlers";
import { Router } from "./router";
+import {
+ QueryClient,
+ QueryClientProvider,
+} from '@tanstack/react-query'
+
+// Create a client
+const queryClient = new QueryClient()
const App = () => {
const dispatch = useDispatch();
@@ -28,17 +35,19 @@ const App = () => {
}, [dispatch, isDesktop]);
return (
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
);
};
diff --git a/src/pages/common/components/ChatComponent/ChatComponent.tsx b/src/pages/common/components/ChatComponent/ChatComponent.tsx
index 5bd1d392da..bed47c5079 100644
--- a/src/pages/common/components/ChatComponent/ChatComponent.tsx
+++ b/src/pages/common/components/ChatComponent/ChatComponent.tsx
@@ -30,6 +30,7 @@ import {
useZoomDisabling,
useImageSizeCheck,
useQueryParams,
+ useFetchDiscussionsByCommonId,
} from "@/shared/hooks";
import { ArrowInCircleIcon } from "@/shared/icons";
import { LinkPreviewData } from "@/shared/interfaces";
@@ -108,6 +109,7 @@ interface ChatComponentInterface {
directParent?: DirectParent | null;
renderChatInput?: () => ReactNode;
onUserClick?: (userId: string) => void;
+ onStreamMentionClick?: (feedItemId: string) => void;
onFeedItemClick?: (feedItemId: string) => void;
onInternalLinkClick?: (data: InternalLinkData) => void;
}
@@ -156,6 +158,7 @@ export default function ChatComponent({
directParent,
renderChatInput: renderChatInputOuter,
onUserClick,
+ onStreamMentionClick,
onFeedItemClick,
onInternalLinkClick,
}: ChatComponentInterface) {
@@ -202,6 +205,7 @@ export default function ChatComponent({
},
onFeedItemClick,
onUserClick,
+ onStreamMentionClick,
commonId,
onInternalLinkClick,
});
@@ -215,6 +219,9 @@ export default function ChatComponent({
chatChannelId: chatChannel?.id || "",
participants: chatChannel?.participants,
});
+
+ const {data: discussionsData} = useFetchDiscussionsByCommonId(commonId);
+
const users = useMemo(
() => (chatChannel ? chatUsers : discussionUsers),
[chatUsers, discussionUsers, chatChannel],
@@ -827,6 +834,7 @@ export default function ChatComponent({
onMessageDelete={handleMessageDelete}
directParent={directParent}
onUserClick={onUserClick}
+ onStreamMentionClick={onStreamMentionClick}
onFeedItemClick={onFeedItemClick}
onInternalLinkClick={onInternalLinkClick}
isEmpty={
@@ -864,6 +872,7 @@ export default function ChatComponent({
onClearFinished={onClearFinished}
shouldReinitializeEditor={shouldReinitializeEditor}
users={users}
+ discussions={discussionsData ?? []}
onEnterKeyDown={onEnterKeyDown}
emojiCount={emojiCount}
setMessage={setMessage}
diff --git a/src/pages/common/components/ChatComponent/components/ChatContent/ChatContent.tsx b/src/pages/common/components/ChatComponent/components/ChatContent/ChatContent.tsx
index 41c6772672..819964cb81 100644
--- a/src/pages/common/components/ChatComponent/components/ChatContent/ChatContent.tsx
+++ b/src/pages/common/components/ChatComponent/components/ChatContent/ChatContent.tsx
@@ -68,6 +68,7 @@ interface ChatContentInterface {
onMessageDelete?: (messageId: string) => void;
directParent?: DirectParent | null;
onUserClick?: (userId: string) => void;
+ onStreamMentionClick?: (link: string) => void;
onFeedItemClick?: (feedItemId: string) => void;
onInternalLinkClick?: (data: InternalLinkData) => void;
isEmpty?: boolean;
@@ -106,6 +107,7 @@ const ChatContent: ForwardRefRenderFunction<
onMessageDelete,
directParent,
onUserClick,
+ onStreamMentionClick,
onFeedItemClick,
onInternalLinkClick,
isEmpty,
@@ -292,6 +294,7 @@ const ChatContent: ForwardRefRenderFunction<
onMessageDelete={onMessageDelete}
directParent={directParent}
onUserClick={onUserClick}
+ onStreamMentionClick={onStreamMentionClick}
onFeedItemClick={onFeedItemClick}
onInternalLinkClick={onInternalLinkClick}
chatChannelId={chatChannelId}
@@ -312,6 +315,7 @@ const ChatContent: ForwardRefRenderFunction<
onMessageDelete={onMessageDelete}
directParent={directParent}
onUserClick={onUserClick}
+ onStreamMentionClick={onStreamMentionClick}
onFeedItemClick={onFeedItemClick}
onInternalLinkClick={onInternalLinkClick}
isMessageEditAllowed={isMessageEditAllowed}
diff --git a/src/pages/common/components/ChatComponent/components/ChatInput/ChatInput.tsx b/src/pages/common/components/ChatComponent/components/ChatInput/ChatInput.tsx
index d51f9098dd..f80f128a58 100644
--- a/src/pages/common/components/ChatComponent/components/ChatInput/ChatInput.tsx
+++ b/src/pages/common/components/ChatComponent/components/ChatInput/ChatInput.tsx
@@ -8,7 +8,7 @@ import React, {
import classNames from "classnames";
import { FILES_ACCEPTED_EXTENSIONS } from "@/shared/constants";
import { PlusIcon, SendIcon } from "@/shared/icons";
-import { User } from "@/shared/models";
+import { Discussion, User } from "@/shared/models";
import {
BaseTextEditor,
ButtonIcon,
@@ -30,6 +30,7 @@ interface ChatInputProps {
emojiCount: EmojiCount;
onEnterKeyDown: (event: React.KeyboardEvent) => void;
users: User[];
+ discussions: Discussion[];
shouldReinitializeEditor: boolean;
onClearFinished: () => void;
canSendMessage?: boolean;
@@ -58,6 +59,7 @@ export const ChatInput = React.memo(forwardRef void;
+ onStreamMentionClick?: (feedItemId: string) => void;
onFeedItemClick?: (feedItemId: string) => void;
onInternalLinkClick?: (data: InternalLinkData) => void;
directParent?: DirectParent | null;
@@ -37,6 +38,7 @@ export const useDiscussionChatAdapter = (options: Options): Return => {
discussionId,
onFeedItemClick,
onUserClick,
+ onStreamMentionClick,
commonId,
onInternalLinkClick,
} = options;
@@ -63,6 +65,7 @@ export const useDiscussionChatAdapter = (options: Options): Return => {
textStyles,
onFeedItemClick,
onUserClick,
+ onStreamMentionClick,
onInternalLinkClick,
});
const { markFeedItemAsSeen } = useUpdateFeedItemSeenState();
diff --git a/src/pages/common/components/FeedItem/FeedItem.tsx b/src/pages/common/components/FeedItem/FeedItem.tsx
index 15a8d10276..4ee33282d1 100644
--- a/src/pages/common/components/FeedItem/FeedItem.tsx
+++ b/src/pages/common/components/FeedItem/FeedItem.tsx
@@ -174,6 +174,7 @@ const FeedItem = forwardRef((props, ref) => {
shouldPreLoadMessages,
withoutMenu,
onUserClick: handleUserClick,
+ onStreamMentionClick: onFeedItemClick,
onFeedItemClick,
onInternalLinkClick,
}),
diff --git a/src/pages/commonFeed/CommonFeed.tsx b/src/pages/commonFeed/CommonFeed.tsx
index de5643757b..2d87529367 100644
--- a/src/pages/commonFeed/CommonFeed.tsx
+++ b/src/pages/commonFeed/CommonFeed.tsx
@@ -129,7 +129,7 @@ const CommonFeedComponent: FC = (props) => {
const anotherCommonId =
userCommonIds[0] === commonId ? userCommonIds[1] : userCommonIds[0];
const pinnedItemIds = useMemo(
- () => commonData?.common.pinnedFeedItems.map((item) => item.feedObjectId),
+ () => (commonData?.common.pinnedFeedItems ?? []).map((item) => item.feedObjectId),
[commonData?.common.pinnedFeedItems],
);
diff --git a/src/pages/commonFeed/components/FeedLayout/components/DesktopChat/DesktopChat.tsx b/src/pages/commonFeed/components/FeedLayout/components/DesktopChat/DesktopChat.tsx
index 99bffc75d0..73a15ed04a 100644
--- a/src/pages/commonFeed/components/FeedLayout/components/DesktopChat/DesktopChat.tsx
+++ b/src/pages/commonFeed/components/FeedLayout/components/DesktopChat/DesktopChat.tsx
@@ -131,6 +131,7 @@ const DesktopChat: FC = (props) => {
directParent={directParent}
renderChatInput={renderChatInput}
onUserClick={onUserClick}
+ onStreamMentionClick={onFeedItemClick}
onFeedItemClick={onFeedItemClick}
onInternalLinkClick={onInternalLinkClick}
/>
diff --git a/src/pages/commonFeed/components/FeedLayout/components/MobileChat/MobileChat.tsx b/src/pages/commonFeed/components/FeedLayout/components/MobileChat/MobileChat.tsx
index aa6cbcaebd..62a1f3244b 100644
--- a/src/pages/commonFeed/components/FeedLayout/components/MobileChat/MobileChat.tsx
+++ b/src/pages/commonFeed/components/FeedLayout/components/MobileChat/MobileChat.tsx
@@ -167,6 +167,7 @@ const MobileChat: FC = (props) => {
directParent={directParent}
renderChatInput={renderChatInput}
onUserClick={onUserClick}
+ onStreamMentionClick={onFeedItemClick}
onFeedItemClick={onFeedItemClick}
onInternalLinkClick={onInternalLinkClick}
/>
diff --git a/src/services/CommonFeed.ts b/src/services/CommonFeed.ts
index 2e2f4d31d8..32e30c024c 100644
--- a/src/services/CommonFeed.ts
+++ b/src/services/CommonFeed.ts
@@ -19,6 +19,7 @@ import {
CommonFeedObjectUserUnique,
CommonFeedType,
CommonMember,
+ FeedItemFollow,
SubCollections,
Timestamp,
} from "@/shared/models";
@@ -318,6 +319,20 @@ class CommonFeedService {
}
});
};
+
+
+ public getFeedItemByCommonAndDiscussionId = async ({commonId, discussionId}: {commonId: string; discussionId: string}): Promise => {
+ try {
+ const feedItems = await this.getCommonFeedSubCollection(commonId)
+ .where("data.id", "==", discussionId)
+ .get();
+
+ const data = feedItems.docs.map(doc => doc.data());
+ return data?.[0];
+ } catch (error) {
+ return null;
+ }
+ };
}
export default new CommonFeedService();
diff --git a/src/services/Discussion.ts b/src/services/Discussion.ts
index 778f6b1aec..7f4a8520c4 100644
--- a/src/services/Discussion.ts
+++ b/src/services/Discussion.ts
@@ -112,6 +112,17 @@ class DiscussionService {
public deleteDiscussion = async (discussionId: string): Promise => {
await Api.delete(ApiEndpoint.DeleteDiscussion(discussionId));
};
+
+ public getDiscussionsByCommonId = async (commonId: string) => {
+ const discussionCollection = await this.getDiscussionCollection()
+ .where("commonId", "==", commonId) // Query for documents where commonId matches
+ .get();
+
+ // Map the Firestore document data
+ const data = discussionCollection.docs.map(doc => doc.data());
+ return data;
+ };
+
}
export default new DiscussionService();
diff --git a/src/shared/components/Chat/ChatMessage/ChatMessage.tsx b/src/shared/components/Chat/ChatMessage/ChatMessage.tsx
index 204f5a5964..9d1438d928 100644
--- a/src/shared/components/Chat/ChatMessage/ChatMessage.tsx
+++ b/src/shared/components/Chat/ChatMessage/ChatMessage.tsx
@@ -78,6 +78,7 @@ interface ChatMessageProps {
onMessageDelete?: (messageId: string) => void;
directParent?: DirectParent | null;
onUserClick?: (userId: string) => void;
+ onStreamMentionClick?: (feedItemID: string) => void;
onFeedItemClick?: (feedItemId: string) => void;
onInternalLinkClick?: (data: InternalLinkData) => void;
isMessageEditAllowed: boolean;
@@ -109,6 +110,7 @@ const ChatMessage = ({
onMessageDelete,
directParent,
onUserClick,
+ onStreamMentionClick,
onFeedItemClick,
onInternalLinkClick,
isMessageEditAllowed,
@@ -165,6 +167,7 @@ const ChatMessage = ({
directParent,
onUserClick,
onFeedItemClick,
+ onStreamMentionClick,
onInternalLinkClick,
});
@@ -177,6 +180,7 @@ const ChatMessage = ({
isNotCurrentUserMessage,
discussionMessage.commonId,
onUserClick,
+ onStreamMentionClick,
onInternalLinkClick,
]);
@@ -302,6 +306,7 @@ const ChatMessage = ({
commonId: discussionMessage.commonId,
directParent,
onUserClick,
+ onStreamMentionClick,
onFeedItemClick,
onInternalLinkClick,
});
diff --git a/src/shared/components/Chat/ChatMessage/DMChatMessage.tsx b/src/shared/components/Chat/ChatMessage/DMChatMessage.tsx
index 99f8c2affa..8863bc506a 100644
--- a/src/shared/components/Chat/ChatMessage/DMChatMessage.tsx
+++ b/src/shared/components/Chat/ChatMessage/DMChatMessage.tsx
@@ -76,6 +76,7 @@ interface ChatMessageProps {
onMessageDelete?: (messageId: string) => void;
directParent?: DirectParent | null;
onUserClick?: (userId: string) => void;
+ onStreamMentionClick?: (feedItemId: string) => void;
onFeedItemClick?: (feedItemId: string) => void;
onInternalLinkClick?: (data: InternalLinkData) => void;
chatChannelId?: string;
@@ -112,6 +113,7 @@ export default function DMChatMessage({
onMessageDelete,
directParent,
onUserClick,
+ onStreamMentionClick,
onFeedItemClick,
onInternalLinkClick,
chatChannelId,
@@ -181,6 +183,7 @@ export default function DMChatMessage({
getCommonPageAboutTabPath,
directParent,
onUserClick,
+ onStreamMentionClick,
onFeedItemClick,
onInternalLinkClick,
});
@@ -201,6 +204,7 @@ export default function DMChatMessage({
getCommonPagePath,
getCommonPageAboutTabPath,
onUserClick,
+ onStreamMentionClick,
]);
useEffect(() => {
@@ -217,6 +221,7 @@ export default function DMChatMessage({
commonId: discussionMessage.commonId,
directParent,
onUserClick,
+ onStreamMentionClick,
onFeedItemClick,
onInternalLinkClick,
});
@@ -229,6 +234,7 @@ export default function DMChatMessage({
isNotCurrentUserMessage,
discussionMessage.commonId,
onUserClick,
+ onStreamMentionClick,
discussionMessageUserId,
userId,
onInternalLinkClick,
diff --git a/src/shared/components/Chat/ChatMessage/components/StreamMention/StreamMention.tsx b/src/shared/components/Chat/ChatMessage/components/StreamMention/StreamMention.tsx
new file mode 100644
index 0000000000..9fd2d74142
--- /dev/null
+++ b/src/shared/components/Chat/ChatMessage/components/StreamMention/StreamMention.tsx
@@ -0,0 +1,46 @@
+import React, { FC, useMemo } from "react";
+import classNames from "classnames";
+import styles from "../../ChatMessage.module.scss";
+import { useQuery } from "@tanstack/react-query";
+import { CommonFeedService } from "@/services";
+
+interface StreamMentionProps {
+ commonId: string;
+ discussionId: string;
+ title: string;
+ mentionTextClassName?: string;
+ onStreamMentionClick?: (feedItemId: string) => void;
+}
+
+const StreamMention: FC = (props) => {
+ const { discussionId, title, commonId, mentionTextClassName, onStreamMentionClick } =
+ props;
+
+ const { data } = useQuery({
+ queryKey: ["stream-mention", discussionId],
+ queryFn: () => CommonFeedService.getFeedItemByCommonAndDiscussionId({ commonId, discussionId }),
+ enabled: !!discussionId,
+ staleTime: Infinity
+ })
+
+ const feedItemId = useMemo(() => data?.id, [data?.id]);
+
+ const handleStreamNameClick = () => {
+ if (onStreamMentionClick && feedItemId) {
+ onStreamMentionClick(feedItemId);
+ }
+ };
+
+ return (
+ <>
+
+ @{title}
+
+ >
+ );
+};
+
+export default StreamMention;
diff --git a/src/shared/components/Chat/ChatMessage/components/StreamMention/index.ts b/src/shared/components/Chat/ChatMessage/components/StreamMention/index.ts
new file mode 100644
index 0000000000..eef391c277
--- /dev/null
+++ b/src/shared/components/Chat/ChatMessage/components/StreamMention/index.ts
@@ -0,0 +1 @@
+export { default as StreamMention } from "./StreamMention";
diff --git a/src/shared/components/Chat/ChatMessage/components/index.ts b/src/shared/components/Chat/ChatMessage/components/index.ts
index 9c5e506643..5b72d65788 100644
--- a/src/shared/components/Chat/ChatMessage/components/index.ts
+++ b/src/shared/components/Chat/ChatMessage/components/index.ts
@@ -3,6 +3,7 @@ export * from "./CheckboxItem";
export * from "./MessageLinkPreview";
export * from "./Time";
export * from "./UserMention";
+export * from "./StreamMention";
export * from "./InternalLink";
export * from "./ReactWithEmoji";
export * from "./Reactions";
diff --git a/src/shared/components/Chat/ChatMessage/types.ts b/src/shared/components/Chat/ChatMessage/types.ts
index 35d5b4d45e..0b812d28a3 100644
--- a/src/shared/components/Chat/ChatMessage/types.ts
+++ b/src/shared/components/Chat/ChatMessage/types.ts
@@ -18,6 +18,7 @@ export interface TextData {
getCommonPageAboutTabPath?: GetCommonPageAboutTabPath;
directParent?: DirectParent | null;
onUserClick?: (userId: string) => void;
+ onStreamMentionClick?: (feedItemId: string) => void;
onFeedItemClick?: (feedItemId: string) => void;
onMessageUpdate?: (message: TextEditorValue) => void;
onInternalLinkClick?: (data: InternalLinkData) => void;
diff --git a/src/shared/components/Chat/ChatMessage/utils/getTextFromTextEditorString.tsx b/src/shared/components/Chat/ChatMessage/utils/getTextFromTextEditorString.tsx
index a90ca84354..7f804576a7 100644
--- a/src/shared/components/Chat/ChatMessage/utils/getTextFromTextEditorString.tsx
+++ b/src/shared/components/Chat/ChatMessage/utils/getTextFromTextEditorString.tsx
@@ -13,7 +13,7 @@ import textEditorElementsStyles from "@/shared/ui-kit/TextEditor/shared/TextEdit
import { EmojiElement } from "@/shared/ui-kit/TextEditor/types";
import { isRtlWithNoMentions } from "@/shared/ui-kit/TextEditor/utils";
import { InternalLinkData } from "@/shared/utils";
-import { CheckboxItem, UserMention } from "../components";
+import { CheckboxItem, StreamMention, UserMention } from "../components";
import { Text, TextData } from "../types";
import { generateInternalLink } from "./generateInternalLink";
import { getTextFromSystemMessage } from "./getTextFromSystemMessage";
@@ -35,6 +35,7 @@ interface TextFromDescendant {
commonId?: string;
directParent?: DirectParent | null;
onUserClick?: (userId: string) => void;
+ onStreamMentionClick?: (feedItemId: string) => void;
onInternalLinkClick?: (data: InternalLinkData) => void;
showPlainText?: boolean;
}
@@ -47,6 +48,7 @@ const getTextFromDescendant = async ({
commonId,
directParent,
onUserClick,
+ onStreamMentionClick,
onInternalLinkClick,
showPlainText,
}: TextFromDescendant): Promise => {
@@ -60,7 +62,7 @@ const getTextFromDescendant = async ({
return await generateInternalLink({ text, onInternalLinkClick });
}),
);
- return {mappedText} || "";
+ return mappedText ? {mappedText} : "";
}
switch (descendant.type) {
@@ -79,6 +81,7 @@ const getTextFromDescendant = async ({
commonId,
directParent,
onUserClick,
+ onStreamMentionClick,
onInternalLinkClick,
showPlainText,
})}
@@ -98,6 +101,16 @@ const getTextFromDescendant = async ({
onUserClick={onUserClick}
/>
);
+ case ElementType.StreamMention:
+ return (
+
+ );
case ElementType.Emoji:
return (
void;
+ onStreamMentionClick?: (feedItemId: string) => void;
onFeedItemClick?: (feedItemId: string) => void;
users: User[];
textStyles: TextStyles;
@@ -78,6 +79,7 @@ export const useDiscussionMessagesById = ({
discussionId,
directParent,
onUserClick,
+ onStreamMentionClick,
onFeedItemClick,
users,
onInternalLinkClick,
@@ -126,6 +128,7 @@ export const useDiscussionMessagesById = ({
getCommonPageAboutTabPath,
directParent,
onUserClick,
+ onStreamMentionClick: onStreamMentionClick ?? onFeedItemClick,
onFeedItemClick,
onInternalLinkClick,
showPlainText: options?.showPlainText,
@@ -196,6 +199,7 @@ export const useDiscussionMessagesById = ({
getCommonPageAboutTabPath,
directParent,
onUserClick,
+ onStreamMentionClick: onStreamMentionClick ?? onFeedItemClick,
onFeedItemClick,
onInternalLinkClick,
});
@@ -228,6 +232,7 @@ export const useDiscussionMessagesById = ({
getCommonPagePath,
getCommonPageAboutTabPath,
onUserClick,
+ onStreamMentionClick,
onFeedItemClick,
onInternalLinkClick,
],
@@ -293,6 +298,7 @@ export const useDiscussionMessagesById = ({
getCommonPageAboutTabPath,
directParent,
onUserClick,
+ onStreamMentionClick: onStreamMentionClick ?? onFeedItemClick,
onFeedItemClick,
onInternalLinkClick,
});
@@ -339,6 +345,7 @@ export const useDiscussionMessagesById = ({
getCommonPagePath,
getCommonPageAboutTabPath,
onUserClick,
+ onStreamMentionClick,
onFeedItemClick,
onInternalLinkClick,
dispatch,
diff --git a/src/shared/hooks/useCases/usePreloadDiscussionMessagesById.ts b/src/shared/hooks/useCases/usePreloadDiscussionMessagesById.ts
index 2fba3af495..1c04567eeb 100644
--- a/src/shared/hooks/useCases/usePreloadDiscussionMessagesById.ts
+++ b/src/shared/hooks/useCases/usePreloadDiscussionMessagesById.ts
@@ -16,6 +16,7 @@ interface Options {
discussionId?: string | null;
commonId?: string;
onUserClick?: (userId: string) => void;
+ onStreamMentionClick?: (feedItemId: string) => void;
onFeedItemClick?: (feedItemId: string) => void;
onInternalLinkClick?: (data: InternalLinkData) => void;
}
@@ -31,6 +32,7 @@ export const usePreloadDiscussionMessagesById = ({
discussionId,
commonId,
onUserClick,
+ onStreamMentionClick,
onFeedItemClick,
onInternalLinkClick,
}: Options): Return => {
@@ -84,6 +86,7 @@ export const usePreloadDiscussionMessagesById = ({
getCommonPagePath,
getCommonPageAboutTabPath,
onUserClick,
+ onStreamMentionClick,
onFeedItemClick,
onInternalLinkClick,
});
diff --git a/src/shared/hooks/useFetchDiscussionsByCommonId.tsx b/src/shared/hooks/useFetchDiscussionsByCommonId.tsx
new file mode 100644
index 0000000000..cb8f490dcc
--- /dev/null
+++ b/src/shared/hooks/useFetchDiscussionsByCommonId.tsx
@@ -0,0 +1,13 @@
+import { DiscussionService } from "@/services";
+import { useQuery } from "@tanstack/react-query";
+
+// React Query hook to fetch discussions
+export const useFetchDiscussionsByCommonId = (commonId: string) => {
+ return useQuery(
+ ["allDiscussion", commonId], // queryKey based on commonId
+ () => DiscussionService.getDiscussionsByCommonId(commonId), // Query function that calls Firestore
+ {
+ cacheTime: 5 * 60 * 1000, // Cache time set to 5 minutes (300,000 milliseconds)
+ }
+ );
+ };
\ No newline at end of file
diff --git a/src/shared/ui-kit/TextEditor/BaseTextEditor.tsx b/src/shared/ui-kit/TextEditor/BaseTextEditor.tsx
index 87e1841587..beb4abad3b 100644
--- a/src/shared/ui-kit/TextEditor/BaseTextEditor.tsx
+++ b/src/shared/ui-kit/TextEditor/BaseTextEditor.tsx
@@ -25,7 +25,7 @@ import { withHistory } from "slate-history";
import { ReactEditor, Slate, withReact } from "slate-react";
import { DOMRange } from "slate-react/dist/utils/dom";
import { KeyboardKeys } from "@/shared/constants/keyboardKeys";
-import { User } from "@/shared/models";
+import { Discussion, User } from "@/shared/models";
import { getUserName, isMobile, isRtlText } from "@/shared/utils";
import {
Editor,
@@ -40,6 +40,7 @@ import {
parseStringToTextEditorValue,
insertEmoji,
insertMention,
+ insertStreamMention,
checkIsCheckboxCreationText,
toggleCheckboxItem,
checkIsEmptyCheckboxCreationText,
@@ -73,6 +74,7 @@ export interface TextEditorProps {
disabled?: boolean;
onKeyDown?: (event: KeyboardEvent) => void;
users?: User[];
+ discussions?: Discussion[];
shouldReinitializeEditor: boolean;
onClearFinished: () => void;
scrollSelectionIntoView?: (editor: ReactEditor, domRange: DOMRange) => void;
@@ -111,6 +113,7 @@ const BaseTextEditor = forwardRef((props
onClearFinished,
scrollSelectionIntoView,
elementStyles,
+ discussions,
} = props;
const editor = useMemo(
() =>
@@ -251,6 +254,12 @@ const BaseTextEditor = forwardRef((props
.startsWith(search.text.substring(1).toLowerCase());
});
+ const discussionChars = (discussions ?? []).filter((discussion) => {
+ return discussion.title
+ ?.toLowerCase()
+ .startsWith(search.text.substring(1).toLowerCase());
+ });
+
useEffect(() => {
if (search && search.text) {
setTarget({
@@ -409,7 +418,14 @@ const BaseTextEditor = forwardRef((props
setTarget(null);
setShouldFocusTarget(false);
}}
+ onClickDiscussion={(discussion: Discussion) => {
+ Transforms.select(editor, target);
+ insertStreamMention(editor, discussion);
+ setTarget(null);
+ setShouldFocusTarget(false);
+ }}
users={chars}
+ discussions={discussionChars}
onClose={() => {
setTarget(null);
setShouldFocusTarget(false);
diff --git a/src/shared/ui-kit/TextEditor/components/Element/Element.tsx b/src/shared/ui-kit/TextEditor/components/Element/Element.tsx
index 17f1e7960b..bddabac37d 100644
--- a/src/shared/ui-kit/TextEditor/components/Element/Element.tsx
+++ b/src/shared/ui-kit/TextEditor/components/Element/Element.tsx
@@ -22,6 +22,20 @@ const Mention = ({ attributes, element, className, children }) => {
);
};
+const StreamMention = ({ attributes, element, className, children }) => {
+ return (
+
+ @{element.title}
+ {children}
+
+ );
+};
+
const Element: FC = (
props,
) => {
@@ -83,6 +97,14 @@ const Element: FC = (
/>
);
}
+ case ElementType.StreamMention: {
+ return (
+
+ )
+ }
case ElementType.Emoji: {
return (
void;
+ onClickDiscussion: (discussion: Discussion) => void;
+ discussions?: Discussion[];
onClose: () => void;
users?: User[];
shouldFocusTarget?: boolean;
@@ -20,7 +22,9 @@ export interface MentionDropdownProps {
const MentionDropdown: FC = (props) => {
const {
onClick,
+ onClickDiscussion,
users = [],
+ discussions = [],
onClose,
shouldFocusTarget,
} = props;
@@ -30,11 +34,12 @@ const MentionDropdown: FC = (props) => {
const [index, setIndex] = useState(0);
const userIds = useMemo(() => users.map(({ uid }) => uid), [users]);
+ const discussionIds = useMemo(() => discussions.map(({ id }) => id), [discussions]);
useEffect(() => {
if (shouldFocusTarget) {
const filteredListRefs = uniq(listRefs.current).filter((item) => {
- if (userIds.includes(item?.id)) {
+ if (userIds.includes(item?.id) || discussionIds.includes(item?.id)) {
return true;
}
@@ -44,12 +49,14 @@ const MentionDropdown: FC = (props) => {
listRefs.current = filteredListRefs;
filteredListRefs && filteredListRefs?.[index]?.focus();
}
- }, [index, shouldFocusTarget, userIds]);
+ }, [index, shouldFocusTarget, userIds, discussionIds]);
const increment = () => {
setIndex((value) => {
const updatedValue = value + 1;
- return updatedValue > users.length - 1 ? value : updatedValue;
+ const usersLastIndex = users.length - 1;
+ const discussionsLastIndex = discussions.length - 1;
+ return updatedValue > discussionsLastIndex + usersLastIndex ? value : updatedValue;
});
};
const decrement = () =>
@@ -77,7 +84,11 @@ const MentionDropdown: FC = (props) => {
break;
}
case KeyboardKeys.Enter: {
- onClick(users[index]);
+ if(index > users.length - 1) {
+ onClickDiscussion(discussions[index - users.length]);
+ } else {
+ onClick(users[index]);
+ }
}
}
};
@@ -92,7 +103,7 @@ const MentionDropdown: FC = (props) => {
data-cy="mentions-portal"
onKeyDown={onKeyDown}
>
- {users.length === 0 && (
+ {(users.length === 0 && discussions.length === 0) && (
@@ -114,6 +125,23 @@ const MentionDropdown: FC = (props) => {
{getUserName(user)}
))}
+ {discussions.map((discussion, index) => (
+ onClickDiscussion(discussion)}
+ className={styles.content}
+ >
+
+ {discussion.title}
+
+ ))}
);
};
diff --git a/src/shared/ui-kit/TextEditor/constants/elementType.ts b/src/shared/ui-kit/TextEditor/constants/elementType.ts
index e9550961f5..45b5bb01c6 100644
--- a/src/shared/ui-kit/TextEditor/constants/elementType.ts
+++ b/src/shared/ui-kit/TextEditor/constants/elementType.ts
@@ -3,6 +3,7 @@ export enum ElementType {
Heading = "heading",
Link = "link",
Mention = "mention",
+ StreamMention = "StreamMention",
NumberedList = "numbered-list",
BulletedList = "bulleted-list",
ListItem = "list-item",
@@ -19,5 +20,6 @@ export const PARENT_TYPES = [
export const INLINE_TYPES = [
ElementType.Link,
ElementType.Mention,
+ ElementType.StreamMention,
ElementType.Emoji,
];
diff --git a/src/shared/ui-kit/TextEditor/hofs/withMentions.ts b/src/shared/ui-kit/TextEditor/hofs/withMentions.ts
index 91bc505b39..1e8522b921 100644
--- a/src/shared/ui-kit/TextEditor/hofs/withMentions.ts
+++ b/src/shared/ui-kit/TextEditor/hofs/withMentions.ts
@@ -9,14 +9,14 @@ export const withMentions = (editor: Editor): Editor => {
checkIsInlineType(element.type) || isInline(element);
editor.isVoid = (element) => {
- return (element.type as ElementType) === ElementType.Mention
+ return ((element.type as ElementType) === ElementType.Mention || (element.type as ElementType) === ElementType.StreamMention)
? true
: isVoid(element);
};
editor.markableVoid = (element) => {
return (
- (element.type as ElementType) === ElementType.Mention ||
+ ((element.type as ElementType) === ElementType.Mention || (element.type as ElementType) === ElementType.StreamMention) ||
markableVoid(element)
);
};
diff --git a/src/shared/ui-kit/TextEditor/types.ts b/src/shared/ui-kit/TextEditor/types.ts
index 0386b74bd3..c8587e93d4 100644
--- a/src/shared/ui-kit/TextEditor/types.ts
+++ b/src/shared/ui-kit/TextEditor/types.ts
@@ -59,6 +59,13 @@ export interface MentionElement extends BaseElement {
userId: string;
}
+export interface StreamMentionElement extends BaseElement {
+ type: ElementType.StreamMention;
+ title: string;
+ commonId: string;
+ discussionId
+}
+
export interface EmojiElement extends BaseElement {
type: ElementType.Emoji;
emoji: Skin;
@@ -90,5 +97,6 @@ export type CustomElement =
| BulletedListElement
| ListItemElement
| MentionElement
+ | StreamMentionElement
| EmojiElement
| CheckboxItemElement;
diff --git a/src/shared/ui-kit/TextEditor/utils/checkIsTextEditorValueEmpty.ts b/src/shared/ui-kit/TextEditor/utils/checkIsTextEditorValueEmpty.ts
index 3d0f5e9537..c92ce30e33 100644
--- a/src/shared/ui-kit/TextEditor/utils/checkIsTextEditorValueEmpty.ts
+++ b/src/shared/ui-kit/TextEditor/utils/checkIsTextEditorValueEmpty.ts
@@ -18,7 +18,7 @@ export const checkIsTextEditorValueEmpty = (
const firstChild = firstElement.children[0];
const secondChild = firstElement.children[1];
- if (Element.isElementType(secondChild, ElementType.Mention)) {
+ if (Element.isElementType(secondChild, ElementType.Mention) || Element.isElementType(secondChild, ElementType.StreamMention)) {
return false;
}
diff --git a/src/shared/ui-kit/TextEditor/utils/index.ts b/src/shared/ui-kit/TextEditor/utils/index.ts
index 152bee9588..c8eb1b7130 100644
--- a/src/shared/ui-kit/TextEditor/utils/index.ts
+++ b/src/shared/ui-kit/TextEditor/utils/index.ts
@@ -29,4 +29,5 @@ export * from "./removeTextEditorEmptyEndLinesValues";
export * from "./countTextEditorEmojiElements";
export * from "./insertEmoji";
export * from "./insertMention";
+export * from "./insertStreamMention";
export * from "./isRtlWithNoMentions";
diff --git a/src/shared/ui-kit/TextEditor/utils/insertStreamMention.ts b/src/shared/ui-kit/TextEditor/utils/insertStreamMention.ts
new file mode 100644
index 0000000000..3fabb6c9f6
--- /dev/null
+++ b/src/shared/ui-kit/TextEditor/utils/insertStreamMention.ts
@@ -0,0 +1,18 @@
+import { Transforms } from "slate";
+import { ReactEditor } from "slate-react";
+import { ElementType } from "../constants";
+import { StreamMentionElement } from "../types";
+
+export const insertStreamMention = (editor, character) => {
+ const mention: StreamMentionElement = {
+ type: ElementType.StreamMention,
+ title: `${character.title} `,
+ commonId: character.commonId,
+ discussionId: character.id,
+ children: [{ text: "" }],
+ };
+ Transforms.insertNodes(editor, mention);
+ Transforms.move(editor);
+
+ ReactEditor.focus(editor);
+};
diff --git a/src/shared/ui-kit/TextEditor/utils/removeTextEditorEmptyEndLinesValues.ts b/src/shared/ui-kit/TextEditor/utils/removeTextEditorEmptyEndLinesValues.ts
index 1010d568d0..965df2661b 100644
--- a/src/shared/ui-kit/TextEditor/utils/removeTextEditorEmptyEndLinesValues.ts
+++ b/src/shared/ui-kit/TextEditor/utils/removeTextEditorEmptyEndLinesValues.ts
@@ -24,6 +24,7 @@ export const removeTextEditorEmptyEndLinesValues = (
if (
firstChild?.text !== "" ||
Element.isElementType(secondChild, ElementType.Mention) ||
+ Element.isElementType(secondChild, ElementType.StreamMention) ||
Element.isElementType(secondChild, ElementType.Emoji)
) {
endOfTextIndex = index;
diff --git a/yarn.lock b/yarn.lock
index 14ceec5b07..09680b41c5 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5097,6 +5097,19 @@
"@svgr/plugin-svgo" "^5.5.0"
loader-utils "^2.0.0"
+"@tanstack/query-core@4.5.0":
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-4.5.0.tgz#eeb0f290adb34f8682f65ebfce4e74709f3ae130"
+ integrity sha512-9pHE4TNlnBxdF24bTH3GGAJ4JdIDfJyuE/q+snyV425XEimPDe+OfofM8mVHfrn01Spvk9xAMpbqoEcmQG4kMg==
+
+"@tanstack/react-query@4.5.0":
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-4.5.0.tgz#566fbf4286a075d74cce32859ecfaafd11cb2d89"
+ integrity sha512-58JRis0+1hdKe37L7ZAJex849mlqhBvpNwlOjz6KzEMXHH/b0AyUHp1YIqn6ULiw7YpZiheYpCkdB/7ArIgfrg==
+ dependencies:
+ "@tanstack/query-core" "4.5.0"
+ use-sync-external-store "^1.2.0"
+
"@tanstack/react-table@^8.7.9":
version "8.7.9"
resolved "https://registry.yarnpkg.com/@tanstack/react-table/-/react-table-8.7.9.tgz#9efcd168fb0080a7e0bc213b5eac8b55513babf4"
@@ -19926,6 +19939,11 @@ use-long-press@^2.0.2:
resolved "https://registry.yarnpkg.com/use-long-press/-/use-long-press-2.0.2.tgz#3c945ee45b671e9c6976fe5364bdb5f563b3ff82"
integrity sha512-zQ4sujilCykA7fSZ+m2gDuGw5aW3Gm3M4pulRH4e8c4mGXw8MDQIMthCsHiolxpt/hCe/BbIvd/iDn9XNDzkYg==
+use-sync-external-store@^1.2.0:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz#c3b6390f3a30eba13200d2302dcdf1e7b57b2ef9"
+ integrity sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==
+
use@^3.1.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"