Implement emoji-only message styling

This commit is contained in:
2025-10-18 22:24:00 +03:00
Unverified
parent b5b5547927
commit c70836c8a7
2 changed files with 117 additions and 67 deletions
+101 -63
View File
@@ -20,6 +20,8 @@
} }
.message { .message {
$status-indicator-size: 16px;
margin-bottom: 1rem; margin-bottom: 1rem;
max-width: 70%; max-width: 70%;
position: relative; position: relative;
@@ -171,69 +173,55 @@
.message-status-indicator { .message-status-indicator {
display: flex; display: flex;
align-items: center; align-items: center;
width: 16px; width: $status-indicator-size;
height: 16px; height: $status-indicator-size;
.error-icon { .error-icon, .success-icon {
color: #f44336; font-size: $status-indicator-size;
font-size: 16px; width: $status-indicator-size;
} height: $status-indicator-size;
.success-icon {
color: #4caf50;
font-size: 16px;
} }
mdui-circular-progress { mdui-circular-progress {
width: 16px; width: $status-indicator-size;
height: 16px; height: $status-indicator-size;
} }
} }
} }
} }
&.received .message-inner { &.received {
background: $color-dark-surface-container; .message-inner {
color: $color-dark-on-surface; background: $color-dark-surface-container;
border-top-left-radius: 5px; color: $color-dark-on-surface;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); border-top-left-radius: 5px;
border: 1px solid rgba($color-dark-outline-variant, 0.4); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
position: relative; border: 1px solid rgba($color-dark-outline-variant, 0.4);
overflow: hidden;
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(135deg, rgba($color-dark-primary, 0.05), rgba($color-dark-tertiary, 0.03));
pointer-events: none;
z-index: 0;
}
> * {
position: relative; position: relative;
z-index: 1; overflow: hidden;
}
} &::before {
content: '';
&.received .message-time { position: absolute;
color: $color-dark-on-surface-variant; top: 0;
font-weight: 500; left: 0;
right: 0;
.message-status-indicator { bottom: 0;
.success-icon { background: linear-gradient(135deg, rgba($color-dark-primary, 0.05), rgba($color-dark-tertiary, 0.03));
color: $color-dark-on-surface-variant; pointer-events: none;
filter: brightness(1.1); z-index: 0;
} }
.error-icon { > * {
color: #ff6b6b; position: relative;
filter: brightness(1.1); z-index: 1;
} }
} }
.message-time {
color: $color-dark-on-surface-variant;
font-weight: 500;
}
} }
&.sent { &.sent {
@@ -267,26 +255,76 @@
} }
} }
.message-time { &.emoji-message {
color: $color-dark-on-primary; .message-inner {
font-weight: 500; align-items: flex-end;
display: flex;
.message-status-indicator { flex-direction: column;
.success-icon {
color: $color-dark-on-primary; .message-content {
filter: brightness(1.2); &.emoji-content {
} text-align: right;
}
.error-icon {
color: #ff6b6b;
filter: brightness(1.2);
} }
} }
} }
.message-time {
color: $color-dark-on-primary;
font-weight: 500;
}
}
// Emoji message styles
&.emoji-message {
.message-inner {
background: transparent;
border: none;
box-shadow: none;
padding: 0;
border-radius: 0;
&::before {
display: none;
}
}
.message-content {
margin: 0;
padding: 0;
text-align: right;
&.emoji-content {
font-size: 2rem;
line-height: 1.2;
}
&.single-emoji-content {
font-size: 4rem;
line-height: 1;
margin-bottom: 8px;
}
}
.message-time {
background: rgba(0, 0, 0, 0.3);
border-radius: 12px;
padding: 4px 8px;
margin-top: 8px;
margin-right: 0;
font-size: 0.75rem;
color: rgba(255, 255, 255, 0.7);
backdrop-filter: blur(10px);
display: flex;
align-items: center;
justify-content: flex-end;
gap: 4px;
user-select: none;
width: fit-content;
}
} }
} }
.message-username { .message-username {
&.loading { &.loading {
opacity: 0.6; opacity: 0.6;
+16 -4
View File
@@ -4,7 +4,7 @@ import defaultAvatar from "@/images/default-avatar.png";
import Quote from "@/core/components/Quote"; import Quote from "@/core/components/Quote";
import { parse } from "marked"; import { parse } from "marked";
import DOMPurify from "dompurify"; import DOMPurify from "dompurify";
import { useEffect, useState, useRef } from "react"; import { useEffect, useState, useRef, useMemo } from "react";
import { getCurrentKeys } from "@/core/api/authApi"; import { getCurrentKeys } from "@/core/api/authApi";
import { ecdhSharedSecret, deriveWrappingKey } from "@/utils/crypto/asymmetric"; import { ecdhSharedSecret, deriveWrappingKey } from "@/utils/crypto/asymmetric";
import { importAesGcmKey, aesGcmDecrypt } from "@/utils/crypto/symmetric"; import { importAesGcmKey, aesGcmDecrypt } from "@/utils/crypto/symmetric";
@@ -409,11 +409,23 @@ export function Message({ message, isAuthor, onContextMenu, onReactionClick, isD
e.stopPropagation(); e.stopPropagation();
onContextMenu(e, message); onContextMenu(e, message);
} }
const messageText = message.content.trim();
const isEmojiMessage = useMemo(() => {
const emojiRegex = /^[\s\p{Emoji}]*$/u;
return messageText.length > 0 && emojiRegex.test(messageText);
}, [messageText]);
// Check if message has only one emoji
const isSingleEmojiMessage = useMemo(() => {
const emojiRegex = /^[\p{Emoji}]+$/u;
return messageText.length > 0 && emojiRegex.test(messageText) && messageText.length <= 4; // Most emojis are 1-4 characters
}, [messageText]);
return ( return (
<> <>
<div <div
className={`message ${isAuthor ? "sent" : "received"}`} className={`message ${isAuthor ? "sent" : "received"} ${isEmojiMessage ? "emoji-message" : ""} ${isSingleEmojiMessage ? "single-emoji" : ""}`}
data-id={message.id} data-id={message.id}
onContextMenu={handleContextMenu} onContextMenu={handleContextMenu}
> >
@@ -431,7 +443,7 @@ export function Message({ message, isAuthor, onContextMenu, onReactionClick, isD
)} )}
<div className="message-inner"> <div className="message-inner">
{!isAuthor && !isDm && ( {!isAuthor && !isDm && !isSingleEmojiMessage && (
<div <div
className="message-username" className="message-username"
onClick={handleProfileClick}> onClick={handleProfileClick}>
@@ -446,7 +458,7 @@ export function Message({ message, isAuthor, onContextMenu, onReactionClick, isD
</Quote> </Quote>
)} )}
<div className="message-content" dangerouslySetInnerHTML={formattedMessage} /> <div className={`message-content ${isEmojiMessage ? "emoji-content" : ""} ${isSingleEmojiMessage ? "single-emoji-content" : ""}`} dangerouslySetInnerHTML={formattedMessage} />
{message.files && message.files.length > 0 && ( {message.files && message.files.length > 0 && (
<mdui-list className="message-attachments"> <mdui-list className="message-attachments">