mentions in chat
parent
12b246b299
commit
20a9573440
|
|
@ -17,19 +17,19 @@ import UserIcon from "./user/UserIcon";
|
||||||
export type AutoCompleteState =
|
export type AutoCompleteState =
|
||||||
| { type: "none" }
|
| { type: "none" }
|
||||||
| ({ selected: number; within: boolean } & (
|
| ({ selected: number; within: boolean } & (
|
||||||
| {
|
| {
|
||||||
type: "emoji";
|
type: "emoji";
|
||||||
matches: (string | CustomEmoji)[];
|
matches: (string | CustomEmoji)[];
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: "user";
|
type: "user";
|
||||||
matches: User[];
|
matches: User[];
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: "channel";
|
type: "channel";
|
||||||
matches: Channel[];
|
matches: Channel[];
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
|
|
||||||
export type SearchClues = {
|
export type SearchClues = {
|
||||||
users?: { type: "channel"; id: string } | { type: "all" };
|
users?: { type: "channel"; id: string } | { type: "all" };
|
||||||
|
|
@ -89,8 +89,8 @@ export function useAutoComplete(
|
||||||
current === "#"
|
current === "#"
|
||||||
? "channel"
|
? "channel"
|
||||||
: current === ":"
|
: current === ":"
|
||||||
? "emoji"
|
? "emoji"
|
||||||
: "user",
|
: "user",
|
||||||
search.toLowerCase(),
|
search.toLowerCase(),
|
||||||
current === ":" ? j + 1 : j,
|
current === ":" ? j + 1 : j,
|
||||||
];
|
];
|
||||||
|
|
@ -177,8 +177,8 @@ export function useAutoComplete(
|
||||||
const matches = (
|
const matches = (
|
||||||
search.length > 0
|
search.length > 0
|
||||||
? users.filter((user) =>
|
? users.filter((user) =>
|
||||||
user.username.toLowerCase().match(regex),
|
user.username.toLowerCase().match(regex),
|
||||||
)
|
)
|
||||||
: users
|
: users
|
||||||
)
|
)
|
||||||
.splice(0, 5)
|
.splice(0, 5)
|
||||||
|
|
@ -209,8 +209,8 @@ export function useAutoComplete(
|
||||||
const matches = (
|
const matches = (
|
||||||
search.length > 0
|
search.length > 0
|
||||||
? channels.filter((channel) =>
|
? channels.filter((channel) =>
|
||||||
channel.name!.toLowerCase().match(regex),
|
channel.name!.toLowerCase().match(regex),
|
||||||
)
|
)
|
||||||
: channels
|
: channels
|
||||||
)
|
)
|
||||||
.splice(0, 5)
|
.splice(0, 5)
|
||||||
|
|
@ -255,12 +255,13 @@ export function useAutoComplete(
|
||||||
": ",
|
": ",
|
||||||
);
|
);
|
||||||
} else if (state.type === "user") {
|
} else if (state.type === "user") {
|
||||||
|
const selectedUser = state.matches[state.selected];
|
||||||
content.splice(
|
content.splice(
|
||||||
index,
|
index,
|
||||||
search.length + 1,
|
search.length + 1,
|
||||||
"<@",
|
"@",
|
||||||
state.matches[state.selected]._id,
|
selectedUser.username,
|
||||||
"> ",
|
" ",
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
content.splice(
|
content.splice(
|
||||||
|
|
@ -460,11 +461,10 @@ export default function AutoComplete({
|
||||||
size={20}
|
size={20}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<span style={{ paddingLeft: "4px" }}>{`:${
|
<span style={{ paddingLeft: "4px" }}>{`:${match instanceof CustomEmoji
|
||||||
match instanceof CustomEmoji
|
|
||||||
? match.name
|
? match.name
|
||||||
: match
|
: match
|
||||||
}:`}</span>
|
}:`}</span>
|
||||||
</div>
|
</div>
|
||||||
{match instanceof CustomEmoji &&
|
{match instanceof CustomEmoji &&
|
||||||
match.parent.type == "Server" && (
|
match.parent.type == "Server" && (
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import { IconButton, Picker } from "@revoltchat/ui";
|
||||||
|
|
||||||
import TextAreaAutoSize from "../../../lib/TextAreaAutoSize";
|
import TextAreaAutoSize from "../../../lib/TextAreaAutoSize";
|
||||||
import { debounce } from "../../../lib/debounce";
|
import { debounce } from "../../../lib/debounce";
|
||||||
import { defer } from "../../../lib/defer";
|
import { defer, chainedDefer } from "../../../lib/defer";
|
||||||
import { internalEmit, internalSubscribe } from "../../../lib/eventEmitter";
|
import { internalEmit, internalSubscribe } from "../../../lib/eventEmitter";
|
||||||
import { useTranslation } from "../../../lib/i18n";
|
import { useTranslation } from "../../../lib/i18n";
|
||||||
import { isTouchscreenDevice } from "../../../lib/isTouchscreenDevice";
|
import { isTouchscreenDevice } from "../../../lib/isTouchscreenDevice";
|
||||||
|
|
@ -325,10 +325,29 @@ export default observer(({ channel }: Props) => {
|
||||||
if (uploadState.type === "uploading" || uploadState.type === "sending")
|
if (uploadState.type === "uploading" || uploadState.type === "sending")
|
||||||
return;
|
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 (uploadState.type !== "none") return sendFile(content);
|
||||||
if (content.length === 0) return;
|
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");
|
internalEmit("NewMessages", "hide");
|
||||||
stopTyping();
|
stopTyping();
|
||||||
setMessage();
|
setMessage();
|
||||||
|
|
@ -366,7 +385,7 @@ export default observer(({ channel }: Props) => {
|
||||||
content: newContent.substr(0, 2000),
|
content: newContent.substr(0, 2000),
|
||||||
})
|
})
|
||||||
.then(() =>
|
.then(() =>
|
||||||
defer(() =>
|
chainedDefer(() =>
|
||||||
renderer.jumpToBottom(
|
renderer.jumpToBottom(
|
||||||
SMOOTH_SCROLL_ON_RECEIVE,
|
SMOOTH_SCROLL_ON_RECEIVE,
|
||||||
),
|
),
|
||||||
|
|
@ -388,7 +407,8 @@ export default observer(({ channel }: Props) => {
|
||||||
replies,
|
replies,
|
||||||
});
|
});
|
||||||
|
|
||||||
defer(() => renderer.jumpToBottom(SMOOTH_SCROLL_ON_RECEIVE));
|
// Use chainedDefer for more reliable scrolling
|
||||||
|
chainedDefer(() => renderer.jumpToBottom(SMOOTH_SCROLL_ON_RECEIVE));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await channel.sendMessage({
|
await channel.sendMessage({
|
||||||
|
|
@ -396,6 +416,9 @@ export default observer(({ channel }: Props) => {
|
||||||
nonce,
|
nonce,
|
||||||
replies,
|
replies,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Add another scroll to bottom after the message is sent
|
||||||
|
chainedDefer(() => renderer.jumpToBottom(SMOOTH_SCROLL_ON_RECEIVE));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
state.queue.fail(nonce, takeError(error));
|
state.queue.fail(nonce, takeError(error));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue