diff --git a/packages/mgt-chat/src/components/Chat/Chat.tsx b/packages/mgt-chat/src/components/Chat/Chat.tsx
index b9067b8c29..3ab602f54e 100644
--- a/packages/mgt-chat/src/components/Chat/Chat.tsx
+++ b/packages/mgt-chat/src/components/Chat/Chat.tsx
@@ -69,6 +69,10 @@ const messageThreadStyles: MessageThreadStyles = {
chatContainer: {
'& .ui-box': {
zIndex: 'unset'
+ },
+ '& p': {
+ display: 'inline-flex',
+ justifyContent: 'center'
}
},
chatMessageContainer: {
diff --git a/packages/mgt-chat/src/statefulClient/StatefulGraphChatClient.ts b/packages/mgt-chat/src/statefulClient/StatefulGraphChatClient.ts
index f154ca61cd..2f4c241ce9 100644
--- a/packages/mgt-chat/src/statefulClient/StatefulGraphChatClient.ts
+++ b/packages/mgt-chat/src/statefulClient/StatefulGraphChatClient.ts
@@ -60,7 +60,7 @@ import {
} from './graph.chat';
import { updateMessageContentWithImage } from '../utils/updateMessageContentWithImage';
import { isChatMessage } from '../utils/types';
-import { rewriteEmojiContent } from '../utils/rewriteEmojiContent';
+import { rewriteEmojiContentToHTML } from '../utils/rewriteEmojiContent';
// 1x1 grey pixel
const placeholderImageContent =
@@ -1020,8 +1020,7 @@ detail: ${JSON.stringify(eventDetail)}`);
}
let content = graphMessage.body?.content ?? 'undefined';
let result: MessageConversion = {};
- // do simple emoji replacement first
- content = rewriteEmojiContent(content);
+ content = rewriteEmojiContentToHTML(content);
// Handle any mentions in the content
content = this.updateMentionsContent(content);
diff --git a/packages/mgt-chat/src/utils/rewriteEmojiContent.tests.ts b/packages/mgt-chat/src/utils/rewriteEmojiContent.tests.ts
index 4f6351fe55..a8424ae13a 100644
--- a/packages/mgt-chat/src/utils/rewriteEmojiContent.tests.ts
+++ b/packages/mgt-chat/src/utils/rewriteEmojiContent.tests.ts
@@ -1,23 +1,40 @@
+/**
+ * -------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License.
+ * See License in the project root for license information.
+ * -------------------------------------------------------------------------------------------
+ */
+
import { expect } from '@open-wc/testing';
-import { rewriteEmojiContent } from './rewriteEmojiContent';
+import { rewriteEmojiContentToHTML } from './rewriteEmojiContent';
-describe('emoji rewrite tests', () => {
+describe('rewrite emoji to standard HTML', () => {
it('rewrites an emoji correctly', async () => {
- const result = rewriteEmojiContent(``);
- await expect(result).to.be.equal('ð');
+ const result = rewriteEmojiContentToHTML(``);
+ await expect(result).to.be.equal(
+ ``
+ );
});
it('rewrites an emoji in a p tag correctly', async () => {
- const result = rewriteEmojiContent(`
`);
- await expect(result).to.be.equal('ð
');
+ const result = rewriteEmojiContentToHTML(`
`);
+ await expect(result).to.be.equal(
+ `
`
+ );
});
- it('rewrites multiple emoji in a p correctly', async () => {
- const result = rewriteEmojiContent(
- `
`
+
+ it('rewrites an emoji in a p tag with additional content correctly', async () => {
+ const result = rewriteEmojiContentToHTML(`Hello
`);
+ await expect(result).to.be.equal(
+ `Hello
`
);
- await expect(result).to.be.equal('ððĪŠ
');
});
- it('returns the original value if there is no emoji', async () => {
- const result = rewriteEmojiContent('Seb is cool
');
- await expect(result).to.be.equal('Seb is cool
');
+
+ it('rewrites emojis in multiple p tags correctly', async () => {
+ const result = rewriteEmojiContentToHTML(
+ `
`
+ );
+ await expect(result).to.be.equal(
+ `
`
+ );
});
});
diff --git a/packages/mgt-chat/src/utils/rewriteEmojiContent.ts b/packages/mgt-chat/src/utils/rewriteEmojiContent.ts
index 114982d377..57b23434df 100644
--- a/packages/mgt-chat/src/utils/rewriteEmojiContent.ts
+++ b/packages/mgt-chat/src/utils/rewriteEmojiContent.ts
@@ -1,32 +1,63 @@
/**
- * 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 angle bracket.
- * ><\/emoji>: Matches the remaining part of the tag.
+ * -------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License.
+ * See License in the project root for license information.
+ * -------------------------------------------------------------------------------------------
*/
-const emojiRegex = /(]+)alt=["'](\w*[^"']*)["'](.[^>]+)><\/emoji>/;
-const emojiMatch = (messageContent: string): RegExpMatchArray | null => {
- return messageContent.match(emojiRegex);
-};
-// iterative repave the emoji custom element with the content of the alt attribute
-// on the emoji element
-const processEmojiContent = (messageContent: string): string => {
- let result = messageContent;
- let match = emojiMatch(result);
- while (match) {
- result = result.replace(emojiRegex, '$2');
- match = emojiMatch(result);
+
+/**
+ * Checks if DOM content has emojis and other HTML content.
+ * @param dom the html content parsed into HTMLDocument.
+ * @param emojisCount number of emojis in the content.
+ * @returns true if only one emoji is in the content without other content, otherwise false.
+ */
+const hasOtherContent = (dom: Document, emojisCount: number): boolean => {
+ const isPtag = dom.body.firstChild?.nodeName === 'P';
+ if (isPtag) {
+ const firstChildNodes = dom.body.firstChild?.childNodes;
+ return firstChildNodes?.length !== emojisCount;
}
- return result;
+ return false;
};
/**
- * if the content contains an tag with an alt attribute the content is replaced by replacing the emoji tags with the content of their alt attribute.
- * @param {string} content
- * @returns {string} the content with any emoji tags replaced by the content of their alt attribute.
+ * Parses html content string into HTMLDocument, then replaces instances of the
+ * emoji tag.
+ * @param content the HTML string.
+ * @returns HTML string with emoji tags changed to the HTML representation.
*/
-export const rewriteEmojiContent = (content: string): string =>
- emojiMatch(content) ? processEmojiContent(content) : content;
+export const rewriteEmojiContentToHTML = (content: string): string => {
+ const parser = new DOMParser();
+ const dom = parser.parseFromString(content, 'text/html');
+ const emojis = dom.querySelectorAll('emoji');
+ const emojisCount = emojis.length;
+ const size = emojisCount > 1 || hasOtherContent(dom, emojisCount) ? 20 : 50;
+
+ for (const emoji of emojis) {
+ const id = emoji.getAttribute('id') ?? '';
+ const alt = emoji.getAttribute('alt') ?? '';
+ const title = emoji.getAttribute('title') ?? '';
+
+ const span = document.createElement('span');
+ span.setAttribute('contentEditable', 'false');
+ span.setAttribute('title', title);
+ span.setAttribute('type', `(${id})`);
+ span.setAttribute('class', `animated-emoticon-${size}-cool`);
+
+ const img = document.createElement('img');
+ img.setAttribute('itemscope', '');
+ img.setAttribute('itemtype', 'http://schema.skype.com/Emoji');
+ img.setAttribute('itemid', id);
+ img.setAttribute(
+ 'src',
+ `https://statics.teams.cdn.office.net/evergreen-assets/personal-expressions/v2/assets/emoticons/${id}/default/${size}_f.png`
+ );
+ img.setAttribute('title', title);
+ img.setAttribute('alt', alt);
+ img.setAttribute('style', `width:${size}px;height:${size}px;`);
+
+ span.appendChild(img);
+ emoji.replaceWith(span);
+ }
+ return dom.body.innerHTML;
+};