From db6915f53f30a848d2f282ec47e5a4bffdd97b13 Mon Sep 17 00:00:00 2001 From: MarekWo Date: Fri, 6 Feb 2026 20:42:27 +0100 Subject: [PATCH] feat: Improve chat message display - Add sender name above outgoing messages in group chat - Display emoji-only messages in larger font size - Fix leading space before text in quoted replies Co-Authored-By: Claude Opus 4.5 --- app/static/css/style.css | 13 ++++++++ app/static/js/app.js | 1 + app/static/js/message-utils.js | 56 ++++++++++++++++++++++++++++++++-- 3 files changed, 68 insertions(+), 2 deletions(-) diff --git a/app/static/css/style.css b/app/static/css/style.css index 6eeee94..aaa2e83 100644 --- a/app/static/css/style.css +++ b/app/static/css/style.css @@ -129,6 +129,13 @@ main { color: #6c757d; } +.message-footer .message-sender.own { + font-weight: 600; + font-size: 0.875rem; + color: #084298; + margin-right: 0.5rem; +} + /* Message action buttons container */ .message-actions { display: flex; @@ -680,6 +687,12 @@ main { border-left-color: #084298; } +/* Large Emoji for emoji-only messages */ +.emoji-large { + font-size: 2.5rem; + line-height: 1.2; +} + /* Clickable Links in Messages */ .message-link { color: #0d6efd; diff --git a/app/static/js/app.js b/app/static/js/app.js index e4610ea..94e6bf7 100644 --- a/app/static/js/app.js +++ b/app/static/js/app.js @@ -728,6 +728,7 @@ function createMessageElement(msg) { wrapper.innerHTML = `
diff --git a/app/static/js/message-utils.js b/app/static/js/message-utils.js index d57c11f..40d0d58 100644 --- a/app/static/js/message-utils.js +++ b/app/static/js/message-utils.js @@ -11,6 +11,9 @@ function processMessageContent(content) { if (!content) return ''; + // Check if content (minus mentions) is emoji-only BEFORE any processing + const emojiOnlyInfo = checkEmojiOnlyContent(content); + // First escape HTML to prevent XSS let processed = escapeHtml(content); @@ -27,9 +30,58 @@ function processMessageContent(content) { // 4. Convert URLs to links (and images to thumbnails) processed = processUrls(processed); + // 5. If emoji-only, enlarge the emoji + if (emojiOnlyInfo.isEmojiOnly) { + processed = enlargeEmoji(processed, emojiOnlyInfo.hasMention); + } + return processed; } +/** + * Check if content is emoji-only (excluding @[mentions]) + * @param {string} text - Raw message content + * @returns {object} - { isEmojiOnly: boolean, hasMention: boolean } + */ +function checkEmojiOnlyContent(text) { + const hasMention = /@\[[^\]]+\]/.test(text); + + // Remove @[...] patterns + const withoutMentions = text.replace(/@\[[^\]]+\]/g, '').trim(); + + if (!withoutMentions) { + return { isEmojiOnly: false, hasMention }; + } + + // Check if remaining is only emoji (using Unicode Extended_Pictographic) + // Matches emoji, modifiers, skin tones, ZWJ sequences, variation selectors, and whitespace + const emojiRegex = /^[\p{Extended_Pictographic}\p{Emoji_Modifier}\p{Emoji_Modifier_Base}\p{Emoji_Component}\uFE0F\u200D\s]+$/u; + const isEmojiOnly = emojiRegex.test(withoutMentions); + + return { isEmojiOnly, hasMention }; +} + +/** + * Enlarge emoji in processed HTML + * @param {string} html - Processed HTML with mention badges + * @param {boolean} hasMention - Whether content has mentions + * @returns {string} - HTML with enlarged emoji + */ +function enlargeEmoji(html, hasMention) { + if (hasMention) { + // Add line break after mention badge, then wrap emoji in large class + // Pattern: closing of mention badge, optional whitespace, then emoji + html = html.replace( + /(<\/span>)\s*([\p{Extended_Pictographic}\p{Emoji_Modifier}\p{Emoji_Modifier_Base}\p{Emoji_Component}\uFE0F\u200D\s]+)$/u, + '$1
$2' + ); + } else { + // Just wrap everything in large emoji class + html = `${html}`; + } + return html; +} + /** * Convert @[Username] mentions to styled badges * @param {string} text - HTML-escaped text @@ -77,8 +129,8 @@ function processChannelLinks(text) { * @returns {string} - Text with styled quotes */ function processQuotes(text) { - // Match »...« pattern (guillemets) - const quotePattern = /»([^«]+)«/g; + // Match »...« pattern (guillemets) including optional trailing whitespace + const quotePattern = /»([^«]+)«\s*/g; return text.replace(quotePattern, (_match, quoted) => { // Display without guillemets (styling is enough) + line break after