diff --git a/src/components/common/AutoComplete.tsx b/src/components/common/AutoComplete.tsx index 72994842..a44e047b 100644 --- a/src/components/common/AutoComplete.tsx +++ b/src/components/common/AutoComplete.tsx @@ -17,19 +17,19 @@ import UserIcon from "./user/UserIcon"; export type AutoCompleteState = | { type: "none" } | ({ selected: number; within: boolean } & ( - | { - type: "emoji"; - matches: (string | CustomEmoji)[]; - } - | { - type: "user"; - matches: User[]; - } - | { - type: "channel"; - matches: Channel[]; - } - )); + | { + type: "emoji"; + matches: (string | CustomEmoji)[]; + } + | { + type: "user"; + matches: User[]; + } + | { + type: "channel"; + matches: Channel[]; + } + )); export type SearchClues = { users?: { type: "channel"; id: string } | { type: "all" }; @@ -89,8 +89,8 @@ export function useAutoComplete( current === "#" ? "channel" : current === ":" - ? "emoji" - : "user", + ? "emoji" + : "user", search.toLowerCase(), current === ":" ? j + 1 : j, ]; @@ -177,8 +177,8 @@ export function useAutoComplete( const matches = ( search.length > 0 ? users.filter((user) => - user.username.toLowerCase().match(regex), - ) + user.username.toLowerCase().match(regex), + ) : users ) .splice(0, 5) @@ -209,8 +209,8 @@ export function useAutoComplete( const matches = ( search.length > 0 ? channels.filter((channel) => - channel.name!.toLowerCase().match(regex), - ) + channel.name!.toLowerCase().match(regex), + ) : channels ) .splice(0, 5) @@ -255,12 +255,13 @@ export function useAutoComplete( ": ", ); } else if (state.type === "user") { + const selectedUser = state.matches[state.selected]; content.splice( index, search.length + 1, - "<@", - state.matches[state.selected]._id, - "> ", + "@", + selectedUser.username, + " ", ); } else { content.splice( @@ -460,11 +461,10 @@ export default function AutoComplete({ size={20} /> )} - {`:${ - match instanceof CustomEmoji + {`:${match instanceof CustomEmoji ? match.name : match - }:`} + }:`} {match instanceof CustomEmoji && match.parent.type == "Server" && ( diff --git a/src/components/common/messaging/MessageBox.tsx b/src/components/common/messaging/MessageBox.tsx index c38ed11f..2e7294e0 100644 --- a/src/components/common/messaging/MessageBox.tsx +++ b/src/components/common/messaging/MessageBox.tsx @@ -13,7 +13,7 @@ import { IconButton, Picker } from "@revoltchat/ui"; import TextAreaAutoSize from "../../../lib/TextAreaAutoSize"; import { debounce } from "../../../lib/debounce"; -import { defer } from "../../../lib/defer"; +import { defer, chainedDefer } from "../../../lib/defer"; import { internalEmit, internalSubscribe } from "../../../lib/eventEmitter"; import { useTranslation } from "../../../lib/i18n"; import { isTouchscreenDevice } from "../../../lib/isTouchscreenDevice"; @@ -325,10 +325,29 @@ export default observer(({ channel }: Props) => { if (uploadState.type === "uploading" || uploadState.type === "sending") return; - const content = state.draft.get(channel._id)?.content?.trim() ?? ""; + let content = state.draft.get(channel._id)?.content?.trim() ?? ""; if (uploadState.type !== "none") return sendFile(content); if (content.length === 0) return; + // Convert @username mentions to <@USER_ID> format + const mentionRegex = /@([a-zA-Z0-9_]+)/g; + const mentionMatches = content.match(mentionRegex); + + if (mentionMatches) { + for (const mention of mentionMatches) { + const username = mention.substring(1); // Remove the @ symbol + // Find the user with this username + const user = Array.from(client.users.values()).find( + (u) => u.username.toLowerCase() === username.toLowerCase() + ); + + if (user) { + // Replace @username with <@USER_ID> + content = content.replace(mention, `<@${user._id}>`); + } + } + } + internalEmit("NewMessages", "hide"); stopTyping(); setMessage(); @@ -366,7 +385,7 @@ export default observer(({ channel }: Props) => { content: newContent.substr(0, 2000), }) .then(() => - defer(() => + chainedDefer(() => renderer.jumpToBottom( SMOOTH_SCROLL_ON_RECEIVE, ), @@ -388,7 +407,8 @@ export default observer(({ channel }: Props) => { replies, }); - defer(() => renderer.jumpToBottom(SMOOTH_SCROLL_ON_RECEIVE)); + // Use chainedDefer for more reliable scrolling + chainedDefer(() => renderer.jumpToBottom(SMOOTH_SCROLL_ON_RECEIVE)); try { await channel.sendMessage({ @@ -396,6 +416,9 @@ export default observer(({ channel }: Props) => { nonce, replies, }); + + // Add another scroll to bottom after the message is sent + chainedDefer(() => renderer.jumpToBottom(SMOOTH_SCROLL_ON_RECEIVE)); } catch (error) { state.queue.fail(nonce, takeError(error)); }