UNPKG

@azure/communication-react

Version:

React library for building modern communication user experiences utilizing Azure Communication Services

177 lines • 8.4 kB
// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. import React from 'react'; import { _formatString } from "../../../../acs-ui-common/src"; import parse, { Element as DOMElement } from 'html-react-parser'; import { attributesToProps } from 'html-react-parser'; import Linkify from 'react-linkify'; import { Link } from '@fluentui/react'; import LiveMessage from '../Announcer/LiveMessage'; import DOMPurify from 'dompurify'; import { messageTextContentStyles } from '../styles/MessageThread.styles'; /** @private */ export const ChatMessageContent = (props) => { switch (props.message.contentType) { case 'text': return MessageContentAsText(props); case 'html': return MessageContentAsRichTextHTML(props); case 'richtext/html': return MessageContentAsRichTextHTML(props); default: console.warn('unknown message content type'); return React.createElement(React.Fragment, null); } }; const MessageContentWithLiveAria = (props) => { return React.createElement("div", { "data-ui-status": props.message.status, role: "text", "aria-label": props.ariaLabel, className: props.className }, React.createElement(LiveMessage, { message: props.liveMessage, ariaLive: "polite" }), props.content); }; const MessageContentAsRichTextHTML = (props) => { return React.createElement(MessageContentWithLiveAria, { message: props.message, liveMessage: generateLiveMessage(props), ariaLabel: messageContentAriaText(props), content: processHtmlToReact(props) }); }; const MessageContentAsText = (props) => { return React.createElement(MessageContentWithLiveAria, { message: props.message, liveMessage: generateLiveMessage(props), ariaLabel: messageContentAriaText(props), className: messageTextContentStyles, content: React.createElement(Linkify, { componentDecorator: (decoratedHref, decoratedText, key) => { return React.createElement(Link, { target: "_blank", href: decoratedHref, key: key }, decoratedText); } }, props.message.content) }); }; const extractContentForAllyMessage = (props) => { var _a; if (props.message.content || props.message.attachments) { // Replace all <img> tags with 'image' for aria. const parsedContent = DOMPurify.sanitize((_a = props.message.content) !== null && _a !== void 0 ? _a : '', { ALLOWED_TAGS: ['img'], RETURN_DOM_FRAGMENT: true }); parsedContent.childNodes.forEach(child => { if (child.nodeName.toLowerCase() !== 'img') { return; } const imageTextNode = document.createElement('div'); imageTextNode.innerHTML = 'image '; parsedContent.replaceChild(imageTextNode, child); }); // Inject message attachment count for aria. // this is only applying to file attachments not for inline images. if (props.message.attachments && props.message.attachments.length > 0) { const attachmentCardDescription = attachmentCardGroupDescription(props); const attachmentTextNode = document.createElement('div'); attachmentTextNode.innerHTML = `${attachmentCardDescription}`; parsedContent.appendChild(attachmentTextNode); } // Strip all html tags from the content for aria. let message = DOMPurify.sanitize(parsedContent, { ALLOWED_TAGS: [] }); // decode HTML entities so that screen reader can read the content properly. message = decodeEntities(message); return message; } return ''; }; const generateLiveMessage = (props) => { const messageContent = extractContentForAllyMessage(props); if (props.message.editedOn) { const liveAuthor = _formatString(props.strings.editedMessageLiveAuthorIntro, { author: `${props.message.senderDisplayName}` }); return `${props.message.mine ? props.strings.editedMessageLocalUserLiveAuthorIntro : liveAuthor} ${messageContent}`; } else { const liveAuthor = _formatString(props.strings.liveAuthorIntro, { author: `${props.message.senderDisplayName}` }); return `${props.message.mine ? '' : liveAuthor} ${messageContent} `; } }; const messageContentAriaText = (props) => { var _a, _b; const message = extractContentForAllyMessage(props); return props.message.mine ? _formatString(props.strings.messageContentMineAriaText, { status: (_a = props.message.status) !== null && _a !== void 0 ? _a : '', message: message }) : _formatString(props.strings.messageContentAriaText, { status: (_b = props.message.status) !== null && _b !== void 0 ? _b : '', author: `${props.message.senderDisplayName}`, message: message }); }; const attachmentCardGroupDescription = (props) => { const attachments = props.message.attachments; return getAttachmentCountLiveMessage(attachments !== null && attachments !== void 0 ? attachments : [], props.strings.attachmentCardGroupMessage); }; /** * @private */ export const getAttachmentCountLiveMessage = (attachments, attachmentCardGroupMessage) => { if (attachments.length === 0) { return ''; } return _formatString(attachmentCardGroupMessage, { attachmentCount: `${attachments.length}` }); }; const defaultOnRenderInlineImage = (inlineImage) => { return React.createElement("img", Object.assign({ key: inlineImage.imageAttributes.id, tabIndex: 0, "data-ui-id": inlineImage.imageAttributes.id }, inlineImage.imageAttributes)); }; const processHtmlToReact = (props) => { var _a; const options = { transform(reactNode, domNode) { var _a; if (domNode instanceof DOMElement && domNode.attribs) { // Transform custom rendering of mentions // Transform inline images if (domNode.name && domNode.name === 'img' && domNode.attribs && domNode.attribs.id) { if (domNode.attribs.name) { domNode.attribs['aria-label'] = domNode.attribs.name; } const imgProps = attributesToProps(domNode.attribs); const inlineImageProps = { messageId: props.message.messageId, imageAttributes: imgProps }; return ((_a = props.inlineImageOptions) === null || _a === void 0 ? void 0 : _a.onRenderInlineImage) ? props.inlineImageOptions.onRenderInlineImage(inlineImageProps, defaultOnRenderInlineImage) : defaultOnRenderInlineImage(inlineImageProps); } // Transform links to open in new tab if (domNode.name === 'a' && React.isValidElement(reactNode)) { return React.cloneElement(reactNode, { target: '_blank', rel: 'noreferrer noopener' }); } } // Pass through the original node return reactNode; } }; return React.createElement(React.Fragment, null, parse((_a = props.message.content) !== null && _a !== void 0 ? _a : '', options)); }; const decodeEntities = (encodedString) => { // This regular expression matches HTML entities. const translate_re = /&(nbsp|amp|quot|lt|gt);/g; // This object maps HTML entities to their respective characters. const translate = { nbsp: ' ', amp: '&', quot: '"', lt: '<', gt: '>' }; return encodedString // Find all matches of HTML entities defined in translate_re and // replace them with the corresponding character from the translate object. .replace(translate_re, function (match, entity) { var _a; return (_a = translate[entity]) !== null && _a !== void 0 ? _a : match; }) // Find numeric entities (e.g., &#65;) // and replace them with the equivalent character using the String.fromCharCode method, // which converts Unicode values into characters. .replace(/&#(\d+);/gi, function (match, numStr) { const num = parseInt(numStr, 10); return String.fromCharCode(num); }); }; //# sourceMappingURL=ChatMessageContent.js.map