diff --git a/web/components/chat/ChatUserMessage/ChatUserMessage.tsx b/web/components/chat/ChatUserMessage/ChatUserMessage.tsx index 30498c78f..0d1273cd6 100644 --- a/web/components/chat/ChatUserMessage/ChatUserMessage.tsx +++ b/web/components/chat/ChatUserMessage/ChatUserMessage.tsx @@ -1,4 +1,6 @@ +import { useEffect, useState } from 'react'; import { ChatMessage } from '../../../interfaces/chat-message.model'; +import { formatTimestamp, formatMessageText } from './messageFmt'; import s from './ChatUserMessage.module.scss'; interface Props { @@ -6,21 +8,23 @@ interface Props { showModeratorMenu: boolean; } -export default function ChatUserMessage(props: Props) { - const { message, showModeratorMenu } = props; - // eslint-disable-next-line @typescript-eslint/no-unused-vars +export default function ChatUserMessage({ message, showModeratorMenu }: Props) { const { body, user, timestamp } = message; - // eslint-disable-next-line @typescript-eslint/no-unused-vars const { displayName, displayColor } = user; - const color = `hsl(${displayColor}, 100%, 70%)`; + const formattedTimestamp = `Sent at ${formatTimestamp(timestamp)}`; + const [formattedMessage, setFormattedMessage] = useState(body); + + useEffect(() => { + setFormattedMessage(formatMessageText(body)); + }, [message]); return ( -
+
{displayName}
-
{body}
+
{showModeratorMenu &&
Moderator menu
}
); diff --git a/web/components/chat/ChatUserMessage/messageFmt.ts b/web/components/chat/ChatUserMessage/messageFmt.ts new file mode 100644 index 000000000..13e2b445c --- /dev/null +++ b/web/components/chat/ChatUserMessage/messageFmt.ts @@ -0,0 +1,63 @@ +import { convertToText } from '../chat'; +import { getDiffInDaysFromNow } from '../../../utils/helpers'; + +const stripTags = (str: string) => str && str.replace(/<\/?[^>]+(>|$)/g, ''); +const convertToMarkup = (str = '') => convertToText(str).replace(/\n/g, '

'); + +function getInstagramEmbedFromURL(url: string) { + const urlObject = new URL(url.replace(/\/$/, '')); + urlObject.pathname += '/embed'; + return ``; +} + +function isMessageJustAnchor(embedText: string, message: string, anchors: HTMLAnchorElement[]) { + if (embedText !== '' && anchors.length === 1) return false; + return stripTags(message) === stripTags(anchors[0]?.innerHTML); +} + +function getMessageWithEmbeds(message: string) { + let embedText = ''; + // Make a temporary element so we can actually parse the html and pull anchor tags from it. + // This is a better approach than regex. + const container = document.createElement('p'); + container.innerHTML = message; + + const anchors = Array.from(container.querySelectorAll('a')); + anchors.forEach(({ href }) => { + if (href.includes('instagram.com/p/')) embedText += getInstagramEmbedFromURL(href); + }); + + // If this message only consists of a single embeddable link + // then only return the embed and strip the link url from the text. + if (isMessageJustAnchor(embedText, message, anchors)) return embedText; + return message + embedText; +} + +export function formatTimestamp(sentAt: Date) { + const now = new Date(sentAt); + if (Number.isNaN(now)) return ''; + + const diffInDays = getDiffInDaysFromNow(sentAt); + + if (diffInDays >= 1) { + const localeDate = now.toLocaleDateString('en-US', { + dateStyle: 'medium', + }); + return `at ${localeDate} at ${now.toLocaleTimeString()}`; + } + + return `${now.toLocaleTimeString()}`; +} + +/* + You would call this when receiving a plain text + value back from an API, and before inserting the + text into the `contenteditable` area on a page. +*/ + +export function formatMessageText(message: string) { + let formattedText = getMessageWithEmbeds(message); + formattedText = convertToMarkup(formattedText); + return formattedText; + // return await highlightUsername(formattedText, username); +}