diff --git a/.yarn/cache/@fluentui-contrib-react-chat-npm-0.1.7-66185e2a2e-41b1a4be48.zip b/.yarn/cache/@fluentui-contrib-react-chat-npm-0.1.7-66185e2a2e-41b1a4be48.zip
new file mode 100644
index 0000000000..452c4233f6
Binary files /dev/null and b/.yarn/cache/@fluentui-contrib-react-chat-npm-0.1.7-66185e2a2e-41b1a4be48.zip differ
diff --git a/.yarn/cache/@types-linkify-it-npm-3.0.3-cb8d4d3e99-a734becc4e.zip b/.yarn/cache/@types-linkify-it-npm-3.0.3-cb8d4d3e99-a734becc4e.zip
new file mode 100644
index 0000000000..533dd7ab1f
Binary files /dev/null and b/.yarn/cache/@types-linkify-it-npm-3.0.3-cb8d4d3e99-a734becc4e.zip differ
diff --git a/.yarn/cache/@types-markdown-it-npm-13.0.2-4839de9ecd-fe1f6a12ee.zip b/.yarn/cache/@types-markdown-it-npm-13.0.2-4839de9ecd-fe1f6a12ee.zip
new file mode 100644
index 0000000000..3f81e423c7
Binary files /dev/null and b/.yarn/cache/@types-markdown-it-npm-13.0.2-4839de9ecd-fe1f6a12ee.zip differ
diff --git a/.yarn/cache/@types-mdurl-npm-1.0.3-07a1eff7b0-5bbed4f0eb.zip b/.yarn/cache/@types-mdurl-npm-1.0.3-07a1eff7b0-5bbed4f0eb.zip
new file mode 100644
index 0000000000..ad9b0810ac
Binary files /dev/null and b/.yarn/cache/@types-mdurl-npm-1.0.3-07a1eff7b0-5bbed4f0eb.zip differ
diff --git a/.yarn/cache/adaptivecards-controls-npm-0.10.1-8cec73a1ae-a6bec98489.zip b/.yarn/cache/adaptivecards-controls-npm-0.10.1-8cec73a1ae-a6bec98489.zip
new file mode 100644
index 0000000000..74582cdd5f
Binary files /dev/null and b/.yarn/cache/adaptivecards-controls-npm-0.10.1-8cec73a1ae-a6bec98489.zip differ
diff --git a/.yarn/cache/adaptivecards-designer-npm-2.4.3-943f1e15cc-0428c2eb94.zip b/.yarn/cache/adaptivecards-designer-npm-2.4.3-943f1e15cc-0428c2eb94.zip
new file mode 100644
index 0000000000..45ec2a3832
Binary files /dev/null and b/.yarn/cache/adaptivecards-designer-npm-2.4.3-943f1e15cc-0428c2eb94.zip differ
diff --git a/.yarn/cache/adaptivecards-npm-3.0.1-cdb87b27d4-b87c2ba37d.zip b/.yarn/cache/adaptivecards-npm-3.0.1-cdb87b27d4-b87c2ba37d.zip
new file mode 100644
index 0000000000..52a10f3c8c
Binary files /dev/null and b/.yarn/cache/adaptivecards-npm-3.0.1-cdb87b27d4-b87c2ba37d.zip differ
diff --git a/.yarn/cache/clipboard-npm-2.0.11-45358b5ae8-413055a603.zip b/.yarn/cache/clipboard-npm-2.0.11-45358b5ae8-413055a603.zip
new file mode 100644
index 0000000000..dc934e50f4
Binary files /dev/null and b/.yarn/cache/clipboard-npm-2.0.11-45358b5ae8-413055a603.zip differ
diff --git a/.yarn/cache/delegate-npm-3.2.0-d3f849ea99-d943058fe0.zip b/.yarn/cache/delegate-npm-3.2.0-d3f849ea99-d943058fe0.zip
new file mode 100644
index 0000000000..b52baa9778
Binary files /dev/null and b/.yarn/cache/delegate-npm-3.2.0-d3f849ea99-d943058fe0.zip differ
diff --git a/.yarn/cache/entities-npm-3.0.1-21eeb201ba-aaf7f12033.zip b/.yarn/cache/entities-npm-3.0.1-21eeb201ba-aaf7f12033.zip
new file mode 100644
index 0000000000..78991fcd20
Binary files /dev/null and b/.yarn/cache/entities-npm-3.0.1-21eeb201ba-aaf7f12033.zip differ
diff --git a/.yarn/cache/good-listener-npm-1.2.2-e7865da849-f39fb82c4e.zip b/.yarn/cache/good-listener-npm-1.2.2-e7865da849-f39fb82c4e.zip
new file mode 100644
index 0000000000..5ff451212a
Binary files /dev/null and b/.yarn/cache/good-listener-npm-1.2.2-e7865da849-f39fb82c4e.zip differ
diff --git a/.yarn/cache/linkify-it-npm-4.0.1-9c7d5a3cd6-3e0a299212.zip b/.yarn/cache/linkify-it-npm-4.0.1-9c7d5a3cd6-3e0a299212.zip
new file mode 100644
index 0000000000..372277e403
Binary files /dev/null and b/.yarn/cache/linkify-it-npm-4.0.1-9c7d5a3cd6-3e0a299212.zip differ
diff --git a/.yarn/cache/markdown-it-npm-13.0.2-4aeddbcb85-bb4bf2cb3e.zip b/.yarn/cache/markdown-it-npm-13.0.2-4aeddbcb85-bb4bf2cb3e.zip
new file mode 100644
index 0000000000..be25ad8313
Binary files /dev/null and b/.yarn/cache/markdown-it-npm-13.0.2-4aeddbcb85-bb4bf2cb3e.zip differ
diff --git a/.yarn/cache/mdurl-npm-1.0.1-054d974269-71731ecba9.zip b/.yarn/cache/mdurl-npm-1.0.1-054d974269-71731ecba9.zip
new file mode 100644
index 0000000000..e8e8256e0c
Binary files /dev/null and b/.yarn/cache/mdurl-npm-1.0.1-054d974269-71731ecba9.zip differ
diff --git a/.yarn/cache/select-npm-1.1.2-13cd366fa2-4346151e94.zip b/.yarn/cache/select-npm-1.1.2-13cd366fa2-4346151e94.zip
new file mode 100644
index 0000000000..7bae1c0ec1
Binary files /dev/null and b/.yarn/cache/select-npm-1.1.2-13cd366fa2-4346151e94.zip differ
diff --git a/.yarn/cache/swiper-npm-10.3.1-76fd5fe27f-9a785930ca.zip b/.yarn/cache/swiper-npm-10.3.1-76fd5fe27f-9a785930ca.zip
new file mode 100644
index 0000000000..d99ca9660f
Binary files /dev/null and b/.yarn/cache/swiper-npm-10.3.1-76fd5fe27f-9a785930ca.zip differ
diff --git a/.yarn/cache/tiny-emitter-npm-2.1.0-2a4d94f487-fbcfb51457.zip b/.yarn/cache/tiny-emitter-npm-2.1.0-2a4d94f487-fbcfb51457.zip
new file mode 100644
index 0000000000..00d74e1bc4
Binary files /dev/null and b/.yarn/cache/tiny-emitter-npm-2.1.0-2a4d94f487-fbcfb51457.zip differ
diff --git a/package.json b/package.json
index e95835135b..e7ea452e3f 100644
--- a/package.json
+++ b/package.json
@@ -73,6 +73,7 @@
"@babel/preset-typescript": "^7.23.0",
"@custom-elements-manifest/analyzer": "^0.8.3",
"@esm-bundle/chai": "^4.3.4-fix.0",
+ "@fluentui-contrib/react-chat": "^0.1.7",
"@microsoft/eslint-config-msgraph": "^2.0.0",
"@octokit/rest": "^18.5.3",
"@open-wc/testing": "^3.2.0",
diff --git a/packages/mgt-chat/package.json b/packages/mgt-chat/package.json
index 8d815a63cf..5e379f0c61 100644
--- a/packages/mgt-chat/package.json
+++ b/packages/mgt-chat/package.json
@@ -38,7 +38,9 @@
"author": "Microsoft",
"license": "MIT",
"devDependencies": {
+ "@types/markdown-it": "^13.0.2",
"@types/react": "^17.0.0",
+ "adaptivecards-designer": "^2.4.3",
"react": "^17.0.0",
"react-dom": "^17.0.0",
"react-scripts": "5.0.1",
@@ -63,8 +65,11 @@
"@microsoft/microsoft-graph-types": "^2.0.0",
"@microsoft/microsoft-graph-types-beta": "^0.16.0-preview",
"@microsoft/signalr": "^7.0.4",
+ "adaptivecards": "^3.0.1",
"immer": "^9.0.6",
+ "markdown-it": "^13.0.2",
"opencrypto": "1.5.5",
+ "swiper": "^10.3.1",
"uuid": "^9.0.0",
"web-vitals": "^2.1.4"
},
diff --git a/packages/mgt-chat/src/components/Chat/Chat.tsx b/packages/mgt-chat/src/components/Chat/Chat.tsx
index 5cb5407318..441ab7f248 100644
--- a/packages/mgt-chat/src/components/Chat/Chat.tsx
+++ b/packages/mgt-chat/src/components/Chat/Chat.tsx
@@ -5,11 +5,11 @@ import { Person, PersonCardInteraction, Spinner } from '@microsoft/mgt-react';
import React, { useEffect, useState } from 'react';
import { StatefulGraphChatClient } from '../../statefulClient/StatefulGraphChatClient';
import { useGraphChatClient } from '../../statefulClient/useGraphChatClient';
-import { onRenderMessage } from '../../utils/chat';
+import { ChatHeader } from '../ChatHeader/ChatHeader';
import ChatMessageBar from '../ChatMessageBar/ChatMessageBar';
+import { onRenderMessage } from '../../utils/onRenderMessage';
import { renderMGTMention } from '../../utils/mentions';
import { registerAppIcons } from '../styles/registerIcons';
-import { ChatHeader } from '../ChatHeader/ChatHeader';
registerAppIcons();
@@ -23,7 +23,11 @@ const useStyles = makeStyles({
flexDirection: 'column',
height: '100%',
...shorthands.overflow('auto'),
- paddingBlockEnd: '12px'
+ paddingBlockEnd: '12px',
+
+ '& p': {
+ ...shorthands.margin('unset')
+ }
},
chatMessages: {
height: 'auto',
@@ -69,6 +73,37 @@ const messageThreadStyles: MessageThreadStyles = {
chatContainer: {
'& .ui-box': {
zIndex: 'unset'
+ },
+ '& .fui-ChatMessage': {
+ marginLeft: 'unset',
+ width: '100%'
+ },
+ '& .fui-ChatMessage__author': {
+ fontWeight: 'var(--fontWeightSemibold)',
+ color: 'var(--colorNeutralForeground1)',
+ ...shorthands.margin('0px', '0px', 'var(--spacingVerticalXL)', '0px')
+ },
+ '& .fui-ChatMessage__timestamp,.fui-ChatMessage__details': {
+ fontWeight: 'var(--fontWeightRegular)',
+ color: 'var(--colorNeutralForeground3)',
+ ...shorthands.margin('0px', '0px', 'var(--spacingVerticalXL)', '0px')
+ },
+ '& .fui-ChatMyMessage': {
+ gridTemplateColumns: 'auto auto',
+ columnGap: 'unset'
+ },
+ '& .fui-ChatMyMessage__body': {
+ background: '#c7e0f4' // No token found for this color, yet.
+ },
+ '& .fui-ChatMyMessage__author': {
+ fontWeight: 'var(--fontWeightSemibold)',
+ color: 'var(--colorNeutralForeground1)',
+ ...shorthands.margin('0px', '0px', 'var(--spacingVerticalXL)', '0px')
+ },
+ '& span.fui-ChatMyMessage__timestamp,.fui-ChatMyMessage__details': {
+ fontWeight: 'var(--fontWeightRegular)',
+ color: 'var(--colorNeutralForeground3)',
+ ...shorthands.margin('0px', '0px', 'var(--spacingVerticalXL)', '0px')
}
},
chatMessageContainer: {
diff --git a/packages/mgt-chat/src/components/ChatContainer/ChatContainer.tsx b/packages/mgt-chat/src/components/ChatContainer/ChatContainer.tsx
new file mode 100644
index 0000000000..c165a3c8dd
--- /dev/null
+++ b/packages/mgt-chat/src/components/ChatContainer/ChatContainer.tsx
@@ -0,0 +1,80 @@
+/**
+ * -------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License.
+ * See License in the project root for license information.
+ * -------------------------------------------------------------------------------------------
+ */
+
+import React from 'react';
+import { MessageProps, MessageRenderer } from '@azure/communication-react';
+import { getRelativeDisplayDate } from '@microsoft/mgt-components';
+import { messageContainer } from '../../utils/messageContainer';
+import { isChatMessage } from '../../utils/types';
+
+interface MgtMessageContainerProps {
+ messageProps: MessageProps;
+ defaultOnRender?: MessageRenderer;
+}
+
+const MgtMessageContainer = ({ messageProps, defaultOnRender }: MgtMessageContainerProps) => {
+ // TODO: find out how to render emojis
+ //
This is
+ if (isChatMessage(messageProps.message)) {
+ const author = messageProps.message?.senderDisplayName ?? '';
+ const timestamp = getRelativeDisplayDate(new Date(messageProps.message.createdOn));
+ const details = messageProps.message?.status ?? '';
+ const body: string = messageProps.message?.content ?? '';
+ const contentType = messageProps.message.contentType;
+ const Container = messageContainer(messageProps.message);
+
+ console.log(messageProps);
+ switch (contentType) {
+ case 'text':
+ return (
+
+ {body}
+
+ );
+ case 'html':
+ case 'richtext/html': {
+ const bodyContent = processEmojiContent(body);
+ const html = { __html: bodyContent };
+ return (
+
+
+
+ );
+ }
+ default:
+ return defaultOnRender ? defaultOnRender(messageProps) : <>>;
+ }
+ }
+ return defaultOnRender ? defaultOnRender(messageProps) : <>>;
+};
+
+/**
+ * Regex to detect and extract emoji alt text
+ *
+ * Pattern breakdown:
+ * (]+): Captures the opening emoji tag, including any attributes.
+ * alt=["'](\w*[^"']*)["']: Matches and captures the "alt" attribute value within single or double quotes. The value can contain word characters but not quotes.
+ * (.*[^>]): Captures any remaining text within the opening emoji tag, excluding the closing tag.
+ * : Matches the closing emoji tag.
+ */
+const emojiRegex = /(]+)alt=["'](\w*[^"']*)["'](.*[^>])<\/emoji>/;
+
+const emojiMatch = (messageContent: string): RegExpMatchArray | null => {
+ return messageContent.match(emojiRegex);
+};
+
+const processEmojiContent = (messageContent: string): string => {
+ let result = messageContent;
+ let match = emojiMatch(result);
+ while (match) {
+ result = result.replace(emojiRegex, '$2');
+ match = emojiMatch(result);
+ }
+ return result;
+};
+
+export default MgtMessageContainer;
diff --git a/packages/mgt-chat/src/components/MgtAdaptiveCard/MgtAdaptiveCard.tsx b/packages/mgt-chat/src/components/MgtAdaptiveCard/MgtAdaptiveCard.tsx
new file mode 100644
index 0000000000..dc518b8335
--- /dev/null
+++ b/packages/mgt-chat/src/components/MgtAdaptiveCard/MgtAdaptiveCard.tsx
@@ -0,0 +1,152 @@
+/**
+ * -------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License.
+ * See License in the project root for license information.
+ * -------------------------------------------------------------------------------------------
+ */
+
+import { MessageProps } from '@azure/communication-react';
+import { Action, AdaptiveCard, IMarkdownProcessingResult } from 'adaptivecards';
+import MarkdownIt from 'markdown-it';
+import React, { useEffect, useRef } from 'react';
+import { isChatMessage, isActionOpenUrl } from '../../utils/types';
+import { ChatMessageAttachment } from '@microsoft/microsoft-graph-types';
+import { FluentIcon } from '@fluentui/react-icons/lib/utils/createFluentIcon';
+import { Eye12Filled, Eye12Regular, Send16Filled, Send16Regular, bundleIcon } from '@fluentui/react-icons';
+import {
+ IAdaptiveCard,
+ ISubmitAction,
+ IOpenUrlAction,
+ IShowCardAction,
+ IExecuteAction
+} from 'adaptivecards/lib/schema';
+import { messageContainer } from '../../utils/messageContainer';
+import { getRelativeDisplayDate } from '@microsoft/mgt-components';
+
+type IAction = ISubmitAction | IOpenUrlAction | IShowCardAction | IExecuteAction;
+
+/**
+ * Props for an adaptive card message.s
+ */
+interface MgtAdaptiveCardProps {
+ attachments: ChatMessageAttachment[];
+ defaultOnRender?: (props: MessageProps) => JSX.Element;
+ messageProps: MessageProps;
+}
+
+/**
+ * TODO: find the correct icons, if needed.
+ * Message status icons.
+ */
+const detailsIcons: Record = {
+ seen: bundleIcon(Eye12Filled, Eye12Regular),
+ delivered: bundleIcon(Eye12Filled, Eye12Regular),
+ sending: bundleIcon(Send16Filled, Send16Regular),
+ failed: bundleIcon(Eye12Filled, Eye12Regular),
+ '': bundleIcon(Eye12Filled, Eye12Regular)
+};
+
+/**
+ * Render an adaptive card from the attachments
+ */
+const MgtAdaptiveCard = (msg: MgtAdaptiveCardProps) => {
+ const cardRef = useRef(null);
+ const attachments = msg.attachments;
+ const adaptiveCardAttachments = getAdaptiveCardAttachments(attachments);
+ useEffect(() => {
+ if (adaptiveCardAttachments.length) {
+ const cardElement = cardRef?.current;
+ // Remove all children before appending the attachment elements
+ while (cardElement?.firstChild) cardElement.removeChild(cardElement?.lastChild as Node);
+ for (const attachment of adaptiveCardAttachments) {
+ const cardHtmlElement = getHtmlElementFromAttachment(attachment);
+ cardElement?.appendChild(cardHtmlElement!);
+ }
+ }
+ }, [cardRef, adaptiveCardAttachments]);
+ const defaultOnRender = msg?.defaultOnRender;
+ const messageProps = msg.messageProps;
+ const defaultRender = defaultOnRender ? defaultOnRender(messageProps) : <>>;
+ const Container = messageContainer(msg.messageProps.message);
+ const author = isChatMessage(msg.messageProps.message) ? msg.messageProps.message?.senderDisplayName : '';
+ const timestamp = getRelativeDisplayDate(new Date(msg.messageProps.message.createdOn));
+ const details = isChatMessage(msg.messageProps.message) ? msg.messageProps.message?.status : '';
+ const DetailsIcon: FluentIcon = detailsIcons[details as string];
+
+ return adaptiveCardAttachments.length ? (
+ }>
+
+
+ ) : (
+ defaultRender
+ );
+};
+
+/**
+ * Filters out the adaptive card attachments.
+ * @param attachments
+ * @returns
+ */
+const getAdaptiveCardAttachments = (attachments: ChatMessageAttachment[]): ChatMessageAttachment[] => {
+ const cardAttachments: ChatMessageAttachment[] = [];
+ for (const att of attachments) {
+ const contentType = att?.contentType ?? '';
+ if (contentType === 'application/vnd.microsoft.card.adaptive') {
+ cardAttachments.push(att);
+ }
+ }
+ return cardAttachments;
+};
+
+/**
+ * Process the attachment object and return an HTMLElement or nothing.
+ * @param attachment
+ * @returns
+ */
+const getHtmlElementFromAttachment = (attachment: ChatMessageAttachment | undefined): HTMLElement | undefined => {
+ const adaptiveCard = new AdaptiveCard();
+ const adaptiveCardContentString: string = attachment?.content ?? '';
+ const adaptiveCardContent = JSON.parse(adaptiveCardContentString) as IAdaptiveCard;
+
+ // Check if the actions property has OpenUrl actions only
+ const actions = adaptiveCardContent?.actions?.filter(ac => ac.type === 'Action.OpenUrl');
+ if (actions) {
+ // Update actions to only Action.OpenUrl actions.
+ adaptiveCardContent.actions = actions;
+ }
+
+ // Check if the body has actionSet actions and filter for OpenUrl only
+ const actionSetArray = adaptiveCardContent?.body?.filter(ac => Object.values(ac).includes('ActionSet'));
+ if (actionSetArray) {
+ const finalInnerActions = [];
+ for (const actionSet of actionSetArray) {
+ const innerActions = actionSet?.actions as IAction[];
+ const valid = innerActions?.filter(ac => ac?.type === 'Action.OpenUrl');
+ if (valid) finalInnerActions.push(...valid);
+ }
+
+ for (const b of adaptiveCardContent?.body ?? []) {
+ if (Object.values(b).includes('ActionSet')) {
+ b.actions = finalInnerActions;
+ }
+ }
+ }
+
+ // markdown support
+ AdaptiveCard.onProcessMarkdown = (text: string, result: IMarkdownProcessingResult) => {
+ const md = new MarkdownIt();
+ result.outputHtml = md.render(text);
+ result.didProcess = true;
+ };
+
+ adaptiveCard.parse(adaptiveCardContent);
+ adaptiveCard.onExecuteAction = (action: Action) => {
+ if (isActionOpenUrl(action)) {
+ const url: string = action?.url ?? '';
+ window.open(url, '_blank', 'noopener,noreferrer');
+ }
+ };
+ return adaptiveCard.render();
+};
+
+export default MgtAdaptiveCard;
diff --git a/packages/mgt-chat/src/components/UnsupportedContent/UnsupportedContent.tsx b/packages/mgt-chat/src/components/UnsupportedContent/UnsupportedContent.tsx
index cb59943ea7..0be3a2e28c 100644
--- a/packages/mgt-chat/src/components/UnsupportedContent/UnsupportedContent.tsx
+++ b/packages/mgt-chat/src/components/UnsupportedContent/UnsupportedContent.tsx
@@ -15,7 +15,8 @@ const useStyles = makeStyles({
boxShadow: '0px 4px 8px 0px rgba(0, 0, 0, 0.14), 0px 0px 2px 0px rgba(0, 0, 0, 0.12)',
textDecorationLine: 'none',
color: '#424242',
- ...shorthands.margin('18px', '0px', '8px', '0px'),
+ alignItems: 'center',
+ ...shorthands.margin('0px', '0px', '2px', '0px'),
...shorthands.borderRadius('6px'),
...shorthands.padding('16px'),
...shorthands.gap('6px'),
diff --git a/packages/mgt-chat/src/statefulClient/StatefulGraphChatClient.ts b/packages/mgt-chat/src/statefulClient/StatefulGraphChatClient.ts
index 40c63b4630..2db2de126c 100644
--- a/packages/mgt-chat/src/statefulClient/StatefulGraphChatClient.ts
+++ b/packages/mgt-chat/src/statefulClient/StatefulGraphChatClient.ts
@@ -14,7 +14,7 @@ import {
SendBoxProps,
SystemMessage
} from '@azure/communication-react';
-import { IDynamicPerson, getUserWithPhoto } from '@microsoft/mgt-components';
+import { getUserWithPhoto } from '@microsoft/mgt-components';
import {
ActiveAccountChanged,
IGraph,
@@ -24,6 +24,7 @@ import {
log,
warn
} from '@microsoft/mgt-element';
+import { IDynamicPerson } from '@microsoft/mgt-react';
import { GraphError } from '@microsoft/microsoft-graph-client';
import {
AadUserConversationMember,
@@ -175,6 +176,7 @@ type MessageEventType =
* Extended Message type with additional properties.
*/
export type GraphChatMessage = Message & {
+ attachments?: ChatMessageAttachment[];
hasUnsupportedContent: boolean;
rawChatUrl: string;
};
@@ -917,7 +919,6 @@ detail: ${JSON.stringify(eventDetail)}`);
*
* @private
* @param {(GraphChatMessage)} [message]
- * @return {*}
* @memberof StatefulGraphChatClient
*/
private updateMessages(message?: GraphChatMessage) {
@@ -1049,10 +1050,22 @@ detail: ${JSON.stringify(eventDetail)}`);
return content;
}
+ /**
+ * Checks through a list of attachments if they are supported. It checks
+ * the content if it has unsupported text formats when there are no attachments.
+ * @param content to be rendered.
+ * @param attachments in the chat.
+ * @returns {boolean}
+ */
private hasUnsupportedContent(content: string, attachments: ChatMessageAttachment[]): boolean {
const unsupportedContentTypes = [
'application/vnd.microsoft.card.codesnippet',
'application/vnd.microsoft.card.fluid',
+ 'application/vnd.microsoft.card.list',
+ 'application/vnd.microsoft.card.hero',
+ 'application/vnd.microsoft.card.o365connector',
+ 'application/vnd.microsoft.card.receipt',
+ 'application/vnd.microsoft.card.thumbnail',
'application/vnd.microsoft.card.fluidEmbedCard',
'reference'
];
@@ -1079,10 +1092,10 @@ detail: ${JSON.stringify(eventDetail)}`);
content: string
): GraphChatMessage {
const senderId = graphMessage.from?.user?.id || undefined;
+ const attachments = graphMessage?.attachments ?? [];
const chatId = graphMessage?.chatId ?? '';
const id = graphMessage?.id ?? '';
const chatUrl = `https://teams.microsoft.com/l/message/${chatId}/${id}?context={"contextType":"chat"}`;
- const attachments = graphMessage?.attachments ?? [];
let messageData: GraphChatMessage = {
messageId,
@@ -1096,6 +1109,7 @@ detail: ${JSON.stringify(eventDetail)}`);
mine: senderId === currentUser,
status: 'seen',
attached: 'top',
+ attachments,
hasUnsupportedContent: this.hasUnsupportedContent(content, attachments),
rawChatUrl: chatUrl
};
diff --git a/packages/mgt-chat/src/utils/chat.tsx b/packages/mgt-chat/src/utils/chat.tsx
deleted file mode 100644
index 67a73277a7..0000000000
--- a/packages/mgt-chat/src/utils/chat.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-import { MessageProps, MessageRenderer } from '@azure/communication-react';
-import produce from 'immer';
-import React from 'react';
-import { renderToString } from 'react-dom/server';
-import UnsupportedContent from '../components/UnsupportedContent/UnsupportedContent';
-import { isChatMessage, isGraphChatMessage } from '../utils/types';
-
-/**
- * Renders the preferred content depending on whether it is supported.
- *
- * @param messageProps final message values from the state.
- * @param defaultOnRender default component to render content.
- * @returns
- */
-const onRenderMessage = (messageProps: MessageProps, defaultOnRender?: MessageRenderer) => {
- const message = messageProps?.message;
- if (isGraphChatMessage(message) && message?.hasUnsupportedContent) {
- const unsupportedContentComponent = ;
- messageProps = produce(messageProps, (draft: MessageProps) => {
- if (isChatMessage(draft.message)) {
- draft.message.content = renderToString(unsupportedContentComponent);
- }
- });
- }
-
- return defaultOnRender ? defaultOnRender(messageProps) : <>>;
-};
-export { onRenderMessage };
diff --git a/packages/mgt-chat/src/utils/messageContainer.tsx b/packages/mgt-chat/src/utils/messageContainer.tsx
new file mode 100644
index 0000000000..f23cb7a471
--- /dev/null
+++ b/packages/mgt-chat/src/utils/messageContainer.tsx
@@ -0,0 +1,20 @@
+/**
+ * -------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License.
+ * See License in the project root for license information.
+ * -------------------------------------------------------------------------------------------
+ */
+
+import { Message } from '@azure/communication-react';
+import { ChatMessage, ChatMyMessage } from '@fluentui-contrib/react-chat';
+import { isChatMessage } from './types';
+/**
+ * Determine which message container to render. By default use the ChatMessage.
+ * @param msg is the Message
+ */
+export const messageContainer = (msg: Message) => {
+ if (isChatMessage(msg) && msg?.mine) {
+ return ChatMyMessage;
+ }
+ return ChatMessage;
+};
diff --git a/packages/mgt-chat/src/utils/onRenderMessage.tsx b/packages/mgt-chat/src/utils/onRenderMessage.tsx
new file mode 100644
index 0000000000..a626538dd2
--- /dev/null
+++ b/packages/mgt-chat/src/utils/onRenderMessage.tsx
@@ -0,0 +1,54 @@
+/**
+ * -------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License.
+ * See License in the project root for license information.
+ * -------------------------------------------------------------------------------------------
+ */
+
+import { MessageProps, MessageRenderer } from '@azure/communication-react';
+import produce from 'immer';
+import React from 'react';
+import { isChatMessage, isGraphChatMessage } from './types';
+
+import { renderToString } from 'react-dom/server';
+import MgtMessageContainer from '../components/ChatContainer/ChatContainer';
+import MgtAdaptiveCard from '../components/MgtAdaptiveCard/MgtAdaptiveCard';
+import UnsupportedContent from '../components/UnsupportedContent/UnsupportedContent';
+/**
+ * This is a _dirty_ hack to bundle the teams light CSS used in adaptive cards
+ * designer.
+ * THOUGHT: import this on demand based on the set theme?
+ */
+import 'adaptivecards-designer/dist/containers/teams-container-light.css';
+
+/**
+ * Renders the preferred content depending on whether it is supported.
+ *
+ * @param messageProps final message values from the state.
+ * @param defaultOnRender default component to render content.
+ * @returns
+ */
+const onRenderMessage = (messageProps: MessageProps, defaultOnRender?: MessageRenderer) => {
+ const message = messageProps?.message;
+ if (isGraphChatMessage(message)) {
+ const attachments = message?.attachments ?? [];
+
+ if (message?.hasUnsupportedContent) {
+ const unsupportedContentComponent = ;
+ messageProps = produce(messageProps, (draft: MessageProps) => {
+ if (isChatMessage(draft.message)) {
+ draft.message.content = renderToString(unsupportedContentComponent);
+ }
+ });
+ } else if (attachments.length) {
+ return (
+
+ );
+ }
+ }
+
+ return ;
+ // return defaultOnRender ? defaultOnRender(messageProps) : <>>;
+};
+
+export { onRenderMessage };
diff --git a/packages/mgt-chat/src/utils/types.ts b/packages/mgt-chat/src/utils/types.ts
index 6f2c5f1eda..18ebc2a997 100644
--- a/packages/mgt-chat/src/utils/types.ts
+++ b/packages/mgt-chat/src/utils/types.ts
@@ -7,11 +7,12 @@
import { ChatMessage, Message } from '@azure/communication-react';
import { GraphChatMessage } from 'src/statefulClient/StatefulGraphChatClient';
+import { Action, OpenUrlAction } from 'adaptivecards';
/**
* A typeguard to get the ChatMessage type
* @param msg of Message
- * @returns ChatMessage
+ * @returns {ChatMessage}
*/
export const isChatMessage = (msg: Message): msg is ChatMessage => {
return 'content' in msg;
@@ -20,8 +21,17 @@ export const isChatMessage = (msg: Message): msg is ChatMessage => {
/**
* A typeguard to get the GraphChatMessage type
* @param msg of Message
- * @returns GraphChatMessage
+ * @returns {GraphChatMessage}
*/
export const isGraphChatMessage = (msg: Message): msg is GraphChatMessage => {
- return 'content' in msg && 'hasUnsupportedContent' in msg && 'rawChatUrl' in msg;
+ return 'content' in msg && 'hasUnsupportedContent' in msg && 'rawChatUrl' in msg && 'attachments' in msg;
+};
+
+/**
+ * A typeguard to get the OpenUrlAction type
+ * @param o of OpenUrlAction
+ * @returns {OpenUrlAction}
+ */
+export const isActionOpenUrl = (o: Action): o is OpenUrlAction => {
+ return 'url' in o;
};
diff --git a/packages/mgt-components/src/utils/Utils.tests.ts b/packages/mgt-components/src/utils/Utils.tests.ts
new file mode 100644
index 0000000000..071ed138b2
--- /dev/null
+++ b/packages/mgt-components/src/utils/Utils.tests.ts
@@ -0,0 +1,34 @@
+/**
+ * -------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License.
+ * See License in the project root for license information.
+ * -------------------------------------------------------------------------------------------
+ */
+
+import { getRelativeDisplayDate } from './Utils';
+import { expect } from '@open-wc/testing';
+
+describe('Utils - getRelativeDisplayDate', () => {
+ it('should render the date today in AM format', async () => {
+ const today = new Date();
+ today.setHours(10, 10);
+ const result = getRelativeDisplayDate(today);
+ await expect(result).equal('10:10 AM');
+ });
+
+ it('should render the date today in PM format', async () => {
+ const today = new Date();
+ today.setHours(15, 10);
+ const result = getRelativeDisplayDate(today);
+ await expect(result).equal('3:10 PM');
+ });
+
+ it('should show the date in more than two weeks ago format', async () => {
+ const today = new Date();
+ const twoWeeksAgo = today.getDate() - 15;
+ today.setMonth(1);
+ today.setDate(twoWeeksAgo);
+ const result = getRelativeDisplayDate(today);
+ await expect(result).equal('1/24/2023');
+ });
+});
diff --git a/yarn.lock b/yarn.lock
index 98bd5ab0d5..da61a69412 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2940,6 +2940,22 @@ __metadata:
languageName: node
linkType: hard
+"@fluentui-contrib/react-chat@npm:^0.1.7":
+ version: 0.1.7
+ resolution: "@fluentui-contrib/react-chat@npm:0.1.7"
+ dependencies:
+ "@swc/helpers": ~0.5.1
+ peerDependencies:
+ "@fluentui/react-components": ">=9.25.1 <10.0.0"
+ "@fluentui/react-icons": ">=2.0.204 <3.0.0"
+ "@types/react": ">=16.8.0 <19.0.0"
+ "@types/react-dom": ">=16.8.0 <19.0.0"
+ react: ">=16.8.0 <19.0.0"
+ react-dom: ">=16.8.0 <19.0.0"
+ checksum: 41b1a4be48409d1bfe15f881cf5b6c104b4f445c94e3f5a1d0fb08fa15c8e0f0bc405782c66ac8cf79b27776b2420d800fac866e564befbe4cb093816482a0e9
+ languageName: node
+ linkType: hard
+
"@fluentui/accessibility@npm:^0.66.5":
version: 0.66.5
resolution: "@fluentui/accessibility@npm:0.66.5"
@@ -5947,12 +5963,17 @@ __metadata:
"@microsoft/microsoft-graph-types": ^2.0.0
"@microsoft/microsoft-graph-types-beta": ^0.16.0-preview
"@microsoft/signalr": ^7.0.4
+ "@types/markdown-it": ^13.0.2
"@types/react": ^17.0.0
+ adaptivecards: ^3.0.1
+ adaptivecards-designer: ^2.4.3
immer: ^9.0.6
+ markdown-it: ^13.0.2
opencrypto: 1.5.5
react: ^17.0.0
react-dom: ^17.0.0
react-scripts: 5.0.1
+ swiper: ^10.3.1
typescript: ^4.9.5
uuid: ^9.0.0
web-vitals: ^2.1.4
@@ -9786,7 +9807,7 @@ __metadata:
languageName: node
linkType: hard
-"@swc/helpers@npm:^0.5.1":
+"@swc/helpers@npm:^0.5.1, @swc/helpers@npm:~0.5.1":
version: 0.5.3
resolution: "@swc/helpers@npm:0.5.3"
dependencies:
@@ -10430,6 +10451,13 @@ __metadata:
languageName: node
linkType: hard
+"@types/linkify-it@npm:*":
+ version: 3.0.3
+ resolution: "@types/linkify-it@npm:3.0.3"
+ checksum: a734becc4e7476833b0e6951ec133c006a34809639c722d3e28b7cf88f5f6ccbb433f195788be5e56209b1e9e6e0778879291dd2db401acee3bb585c44dcc329
+ languageName: node
+ linkType: hard
+
"@types/lodash@npm:4.14.117":
version: 4.14.117
resolution: "@types/lodash@npm:4.14.117"
@@ -10444,6 +10472,16 @@ __metadata:
languageName: node
linkType: hard
+"@types/markdown-it@npm:^13.0.2":
+ version: 13.0.2
+ resolution: "@types/markdown-it@npm:13.0.2"
+ dependencies:
+ "@types/linkify-it": "*"
+ "@types/mdurl": "*"
+ checksum: fe1f6a12ee8ad2246359376431a30d22c9b603e63e93e3e27d6920840934b9764034679a4d0b01ec54b0693c8d5c42012ec34715cba4f5b0736b8a4b66db4c74
+ languageName: node
+ linkType: hard
+
"@types/mdast@npm:^3.0.0":
version: 3.0.14
resolution: "@types/mdast@npm:3.0.14"
@@ -10453,6 +10491,13 @@ __metadata:
languageName: node
linkType: hard
+"@types/mdurl@npm:*":
+ version: 1.0.3
+ resolution: "@types/mdurl@npm:1.0.3"
+ checksum: 5bbed4f0eb9f60040fa26be77aa2158ca468b6423876cec0d2043e7f8298e83b8e5b95fb66056327b02d747c4d376aed16c11ff3fdc4cb3dca327a6931a71f18
+ languageName: node
+ linkType: hard
+
"@types/mdx@npm:^2.0.0":
version: 2.0.9
resolution: "@types/mdx@npm:2.0.9"
@@ -12851,6 +12896,37 @@ __metadata:
languageName: node
linkType: hard
+"adaptivecards-controls@npm:^0.10.1":
+ version: 0.10.1
+ resolution: "adaptivecards-controls@npm:0.10.1"
+ checksum: a6bec984894e35f647b3f6ccdcccbf4d8e263e0970534c007a268e2c044c90a63cb18c43d3c72d953760dfd0d261fcad925527e85941889606028095a7375874
+ languageName: node
+ linkType: hard
+
+"adaptivecards-designer@npm:^2.4.3":
+ version: 2.4.3
+ resolution: "adaptivecards-designer@npm:2.4.3"
+ dependencies:
+ adaptivecards-controls: ^0.10.1
+ clipboard: ^2.0.1
+ peerDependencies:
+ adaptive-expressions: ^4.11.0
+ adaptivecards: ^2.10.0
+ adaptivecards-templating: ^2.2.0
+ monaco-editor: ^0.29.1
+ checksum: 0428c2eb9458f14d6b9f3e61dc9ae4670fec65bb35ecca06c35012845a6c23f4183b8b4982663e8515f88d2c148d9a991e5fa7df349781fbae3ca232d87b85bd
+ languageName: node
+ linkType: hard
+
+"adaptivecards@npm:^3.0.1":
+ version: 3.0.1
+ resolution: "adaptivecards@npm:3.0.1"
+ peerDependencies:
+ swiper: ^8.2.6
+ checksum: b87c2ba37d129dc1f732c3101029b670d0f2b39c0b672f67307e5a46a0b2ea6aa780b43451b8819275f51d48d8b0156fa80b424ba9b3cee8e1806b3b93de9c28
+ languageName: node
+ linkType: hard
+
"add-stream@npm:^1.0.0":
version: 1.0.0
resolution: "add-stream@npm:1.0.0"
@@ -15854,6 +15930,17 @@ __metadata:
languageName: node
linkType: hard
+"clipboard@npm:^2.0.1":
+ version: 2.0.11
+ resolution: "clipboard@npm:2.0.11"
+ dependencies:
+ good-listener: ^1.2.2
+ select: ^1.1.2
+ tiny-emitter: ^2.0.0
+ checksum: 413055a6038e43898e0e895216b58ed54fbf386f091cb00188875ef35b186cefbd258acdf4cb4b0ac87cbc00de936f41b45dde9fe1fd1a57f7babb28363b8748
+ languageName: node
+ linkType: hard
+
"cliui@npm:^3.2.0":
version: 3.2.0
resolution: "cliui@npm:3.2.0"
@@ -17835,6 +17922,13 @@ __metadata:
languageName: node
linkType: hard
+"delegate@npm:^3.1.2":
+ version: 3.2.0
+ resolution: "delegate@npm:3.2.0"
+ checksum: d943058fe05897228b158cbd1bab05164df28c8f54127873231d6b03b0a5acc1b3ee1f98ac70ccc9b79cd84aa47118a7de111fee2923753491583905069da27d
+ languageName: node
+ linkType: hard
+
"delegates@npm:^1.0.0":
version: 1.0.0
resolution: "delegates@npm:1.0.0"
@@ -18620,6 +18714,13 @@ __metadata:
languageName: node
linkType: hard
+"entities@npm:~3.0.1":
+ version: 3.0.1
+ resolution: "entities@npm:3.0.1"
+ checksum: aaf7f12033f0939be91f5161593f853f2da55866db55ccbf72f45430b8977e2b79dbd58c53d0fdd2d00bd7d313b75b0968d09f038df88e308aa97e39f9456572
+ languageName: node
+ linkType: hard
+
"env-paths@npm:^2.2.0":
version: 2.2.1
resolution: "env-paths@npm:2.2.1"
@@ -21925,6 +22026,15 @@ __metadata:
languageName: node
linkType: hard
+"good-listener@npm:^1.2.2":
+ version: 1.2.2
+ resolution: "good-listener@npm:1.2.2"
+ dependencies:
+ delegate: ^3.1.2
+ checksum: f39fb82c4e41524f56104cfd2d7aef1a88e72f3f75139115fbdf98cc7d844e0c1b39218b2e83438c6188727bf904ed78c7f0f2feff67b32833bc3af7f0202b33
+ languageName: node
+ linkType: hard
+
"gopd@npm:^1.0.1":
version: 1.0.1
resolution: "gopd@npm:1.0.1"
@@ -26569,6 +26679,15 @@ __metadata:
languageName: node
linkType: hard
+"linkify-it@npm:^4.0.1":
+ version: 4.0.1
+ resolution: "linkify-it@npm:4.0.1"
+ dependencies:
+ uc.micro: ^1.0.1
+ checksum: 3e0a29921269c14eb7ac6f5db2da68d4854ea9acca6e9014a323f75f2dd39b197ffab57c1fbd6a906ceb021aad3ee6d7ba7d0181236dd9630ffc452b392f7f71
+ languageName: node
+ linkType: hard
+
"lit-element@npm:^2.2.1":
version: 2.5.1
resolution: "lit-element@npm:2.5.1"
@@ -27391,6 +27510,21 @@ __metadata:
languageName: node
linkType: hard
+"markdown-it@npm:^13.0.2":
+ version: 13.0.2
+ resolution: "markdown-it@npm:13.0.2"
+ dependencies:
+ argparse: ^2.0.1
+ entities: ~3.0.1
+ linkify-it: ^4.0.1
+ mdurl: ^1.0.1
+ uc.micro: ^1.0.5
+ bin:
+ markdown-it: bin/markdown-it.js
+ checksum: bb4bf2cb3e5d77a7f3dc9cf48e17d050fbcd26d37992204eaa5812220752858fe9debe439b2ae1de06e749a3bba537c0baf6ce7510307cf7163a70f04fafe672
+ languageName: node
+ linkType: hard
+
"markdown-table@npm:^3.0.0":
version: 3.0.3
resolution: "markdown-table@npm:3.0.3"
@@ -27634,6 +27768,13 @@ __metadata:
languageName: node
linkType: hard
+"mdurl@npm:^1.0.1":
+ version: 1.0.1
+ resolution: "mdurl@npm:1.0.1"
+ checksum: 71731ecba943926bfbf9f9b51e28b5945f9411c4eda80894221b47cc105afa43ba2da820732b436f0798fd3edbbffcd1fc1415843c41a87fea08a41cc1e3d02b
+ languageName: node
+ linkType: hard
+
"media-typer@npm:0.3.0":
version: 0.3.0
resolution: "media-typer@npm:0.3.0"
@@ -34538,6 +34679,7 @@ __metadata:
"@babel/preset-typescript": ^7.23.0
"@custom-elements-manifest/analyzer": ^0.8.3
"@esm-bundle/chai": ^4.3.4-fix.0
+ "@fluentui-contrib/react-chat": ^0.1.7
"@microsoft/eslint-config-msgraph": ^2.0.0
"@octokit/rest": ^18.5.3
"@open-wc/testing": ^3.2.0
@@ -34929,6 +35071,13 @@ __metadata:
languageName: node
linkType: hard
+"select@npm:^1.1.2":
+ version: 1.1.2
+ resolution: "select@npm:1.1.2"
+ checksum: 4346151e94f226ea6131e44e68e6d837f3fdee64831b756dd657cc0b02f4cb5107f867cb34a1d1216ab7737d0bf0645d44546afb030bbd8d64e891f5e4c4814e
+ languageName: node
+ linkType: hard
+
"selfsigned@npm:^2.0.1, selfsigned@npm:^2.1.1":
version: 2.4.1
resolution: "selfsigned@npm:2.4.1"
@@ -36757,6 +36906,13 @@ __metadata:
languageName: node
linkType: hard
+"swiper@npm:^10.3.1":
+ version: 10.3.1
+ resolution: "swiper@npm:10.3.1"
+ checksum: 9a785930ca0ab0683d7e5e116c25a38e1f8818d6f9e93848125bdf3e1874e12a6c3b9f4e9c54668e4accc2440a6f031de98cfc01a1b0068c77f8de4115034e4d
+ languageName: node
+ linkType: hard
+
"symbol-tree@npm:^3.2.2, symbol-tree@npm:^3.2.4":
version: 3.2.4
resolution: "symbol-tree@npm:3.2.4"
@@ -37254,6 +37410,13 @@ __metadata:
languageName: node
linkType: hard
+"tiny-emitter@npm:^2.0.0":
+ version: 2.1.0
+ resolution: "tiny-emitter@npm:2.1.0"
+ checksum: fbcfb5145751a0e3b109507a828eb6d6d4501352ab7bb33eccef46e22e9d9ad3953158870a6966a59e57ab7c3f9cfac7cab8521db4de6a5e757012f4677df2dd
+ languageName: node
+ linkType: hard
+
"tiny-invariant@npm:^1.3.1":
version: 1.3.1
resolution: "tiny-invariant@npm:1.3.1"
@@ -37968,7 +38131,7 @@ __metadata:
languageName: node
linkType: hard
-"uc.micro@npm:^1.0.1":
+"uc.micro@npm:^1.0.1, uc.micro@npm:^1.0.5":
version: 1.0.6
resolution: "uc.micro@npm:1.0.6"
checksum: 6898bb556319a38e9cf175e3628689347bd26fec15fc6b29fa38e0045af63075ff3fea4cf1fdba9db46c9f0cbf07f2348cd8844889dd31ebd288c29fe0d27e7a