diff --git a/frontend/src/resources/css/_chat.scss b/frontend/src/resources/css/_chat.scss index 2eb1864..d1c65e7 100644 --- a/frontend/src/resources/css/_chat.scss +++ b/frontend/src/resources/css/_chat.scss @@ -205,8 +205,16 @@ .message-content { word-wrap: break-word; - margin-bottom: 0.3rem; + margin-bottom: 10px; white-space: pre-wrap; + + > p:first-child { + margin-block-start: 0; + } + + > p:last-child { + margin-block-end: 0; + } } .quote.reply-preview { diff --git a/frontend/src/ui/components/chat/Message.tsx b/frontend/src/ui/components/chat/Message.tsx index ab095ad..e5835be 100644 --- a/frontend/src/ui/components/chat/Message.tsx +++ b/frontend/src/ui/components/chat/Message.tsx @@ -2,6 +2,9 @@ import { formatTime } from "../../../utils/utils"; import type { Message as MessageType } from "../../../core/types"; import defaultAvatar from "../../../resources/images/default-avatar.png"; import Quote from "../core/Quote"; +import { parse } from "marked"; +import DOMPurify from "dompurify"; +import { useEffect, useState } from "react"; interface MessageProps { message: MessageType; @@ -13,11 +16,23 @@ interface MessageProps { } export function Message({ message, isAuthor, onProfileClick, onContextMenu, isLoadingProfile = false, isDm = false }: MessageProps) { - const handleContextMenu = (e: React.MouseEvent) => { + const [formattedMessage, setFormattedMessage] = useState({ __html: DOMPurify.sanitize(message.content).trim() }); + + useEffect(() => { + (async () => { + setFormattedMessage({ + __html: DOMPurify.sanitize( + await parse(message.content) + ).trim() + }); + })(); + }, [message]); + + function handleContextMenu(e: React.MouseEvent) { e.preventDefault(); e.stopPropagation(); onContextMenu(e, message); - }; + } return (