added auto complete

pull/1154/head
NanoAim 2025-07-10 11:04:05 +08:00
parent 252d33f001
commit 7f098d059d
3 changed files with 72 additions and 35 deletions

View File

@ -174,7 +174,7 @@ export function useAutoComplete(
(x) => x._id !== "00000000000000000000000000",
);
const matches = (
let matches = (
search.length > 0
? users.filter((user) =>
user.username.toLowerCase().match(regex),
@ -184,6 +184,13 @@ export function useAutoComplete(
.splice(0, 5)
.filter((x) => typeof x !== "undefined");
// Add @everyone if it matches the search and we're in a channel context
if (searchClues.users.type === "channel" &&
(search.length === 0 || "everyone".match(regex))) {
// Add a special "everyone" entry at the beginning
matches = [{ _id: "@everyone", username: "everyone" } as any, ...matches].slice(0, 5);
}
if (matches.length > 0) {
const currentPosition =
state.type !== "none" ? state.selected : 0;
@ -256,13 +263,22 @@ export function useAutoComplete(
);
} else if (state.type === "user") {
const selectedUser = state.matches[state.selected];
content.splice(
index,
search.length + 1,
"@",
selectedUser.username,
" ",
);
// Handle @everyone special case
if (selectedUser._id === "@everyone") {
content.splice(
index,
search.length + 1,
"@everyone ",
);
} else {
content.splice(
index,
search.length + 1,
"@",
selectedUser.username,
" ",
);
}
} else {
content.splice(
index,
@ -492,7 +508,7 @@ export default function AutoComplete({
{state.type === "user" &&
state.matches.map((match, i) => (
<button
key={match}
key={match._id || match}
className={i === state.selected ? "active" : ""}
onMouseEnter={() =>
(i !== state.selected || !state.within) &&
@ -510,8 +526,28 @@ export default function AutoComplete({
})
}
onClick={onClick}>
<UserIcon size={24} target={match} status={true} />
{match.username}
{match._id === "@everyone" ? (
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
<span style={{
width: "24px",
height: "24px",
display: "flex",
alignItems: "center",
justifyContent: "center",
fontSize: "14px",
background: "var(--accent)",
color: "var(--accent-contrast)",
borderRadius: "50%",
fontWeight: "600"
}}>@</span>
<span>everyone</span>
</div>
) : (
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
<UserIcon size={24} target={match} status={true} />
<span>{match.username}</span>
</div>
)}
</button>
))}
{state.type === "channel" &&

View File

@ -48,12 +48,12 @@ export function RenderMention({ match }: CustomComponentProps) {
}
const EveryoneMention = styled.span`
padding: 0 4px;
padding: 0 6px;
flex-shrink: 0;
font-weight: 600;
cursor: default;
color: var(--foreground);
cursor: pointer;
color: var(--accent);
background: var(--secondary-background);
border-radius: calc(var(--border-radius) * 2);

View File

@ -22,25 +22,25 @@ export default observer(({ channel }: Props) => {
const currentRoles =
channel.channel_type === "Group"
? ([
{
id: "default",
name: "Default",
permissions:
channel.permissions ??
DEFAULT_PERMISSION_DIRECT_MESSAGE,
},
] as RoleOrDefault[])
{
id: "default",
name: "Default",
permissions:
channel.permissions ??
DEFAULT_PERMISSION_DIRECT_MESSAGE,
},
] as RoleOrDefault[])
: (useRoles(channel.server! as any).map((role) => {
return {
...role,
permissions: (role.id === "default"
? channel.default_permissions
: channel.role_permissions?.[role.id]) ?? {
a: 0,
d: 0,
},
};
}) as RoleOrDefault[]);
return {
...role,
permissions: (role.id === "default"
? channel.default_permissions
: channel.role_permissions?.[role.id]) ?? {
a: 0,
d: 0,
},
};
}) as RoleOrDefault[]);
return (
<PermissionsLayout
@ -69,9 +69,9 @@ export default observer(({ channel }: Props) => {
typeof currentValue === "number"
? currentValue
: ({
allow: currentValue.a,
deny: currentValue.d,
} as any),
allow: currentValue.a,
deny: currentValue.d,
} as any),
);
}
@ -109,6 +109,7 @@ export default observer(({ channel }: Props) => {
"UploadFiles",
"Masquerade",
"React",
"MentionEveryone",
"ManageChannel",
"ManagePermissions",
]}