Implement formatting

This commit is contained in:
2025-09-21 16:55:39 +03:00
Unverified
parent b2710a8cf5
commit 5e0d225b24
3 changed files with 31 additions and 9 deletions
+9 -1
View File
@@ -205,8 +205,16 @@
.message-content { .message-content {
word-wrap: break-word; word-wrap: break-word;
margin-bottom: 0.3rem; margin-bottom: 10px;
white-space: pre-wrap; white-space: pre-wrap;
> p:first-child {
margin-block-start: 0;
}
> p:last-child {
margin-block-end: 0;
}
} }
.quote.reply-preview { .quote.reply-preview {
+21 -8
View File
@@ -2,6 +2,9 @@ import { formatTime } from "../../../utils/utils";
import type { Message as MessageType } from "../../../core/types"; import type { Message as MessageType } from "../../../core/types";
import defaultAvatar from "../../../resources/images/default-avatar.png"; import defaultAvatar from "../../../resources/images/default-avatar.png";
import Quote from "../core/Quote"; import Quote from "../core/Quote";
import { parse } from "marked";
import DOMPurify from "dompurify";
import { useEffect, useState } from "react";
interface MessageProps { interface MessageProps {
message: MessageType; message: MessageType;
@@ -13,11 +16,23 @@ interface MessageProps {
} }
export function Message({ message, isAuthor, onProfileClick, onContextMenu, isLoadingProfile = false, isDm = false }: 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.preventDefault();
e.stopPropagation(); e.stopPropagation();
onContextMenu(e, message); onContextMenu(e, message);
}; }
return ( return (
<div <div
@@ -47,7 +62,7 @@ export function Message({ message, isAuthor, onProfileClick, onContextMenu, isLo
<div <div
className={`message-username ${isLoadingProfile ? "loading" : ""}`} className={`message-username ${isLoadingProfile ? "loading" : ""}`}
onClick={() => !isLoadingProfile && onProfileClick(message.username)} onClick={() => !isLoadingProfile && onProfileClick(message.username)}
style={{ cursor: isLoadingProfile ? "default" : "pointer" }}> {/* TODO extract to SCSS */} style={{ cursor: isLoadingProfile ? "default" : "pointer" }}>
{message.username} {message.username}
</div> </div>
)} )}
@@ -60,17 +75,15 @@ export function Message({ message, isAuthor, onProfileClick, onContextMenu, isLo
</Quote> </Quote>
)} )}
<div className="message-content"> <div className="message-content" dangerouslySetInnerHTML={formattedMessage} />
{message.content}
</div>
<div className="message-time"> <div className="message-time">
{formatTime(message.timestamp)} {formatTime(message.timestamp)}
{message.is_edited ? " (edited)" : undefined} {message.is_edited ? " (edited)" : undefined}
{isAuthor && message.is_read ? ( {isAuthor && message.is_read && (
<span className="material-symbols outlined"></span> <span className="material-symbols outlined"></span>
) : undefined} )}
</div> </div>
</div> </div>
</div> </div>
+1
View File
@@ -61,6 +61,7 @@
"vite-plugin-html": "^3.2.2" "vite-plugin-html": "^3.2.2"
}, },
"dependencies": { "dependencies": {
"dompurify": "^3.2.7",
"electron-squirrel-startup": "^1.0.1", "electron-squirrel-startup": "^1.0.1",
"marked": "^16.3.0", "marked": "^16.3.0",
"mdui": "^2.1.4", "mdui": "^2.1.4",