diff --git a/src/components/common/AutoComplete.tsx b/src/components/common/AutoComplete.tsx index 72994842..2529cf36 100644 --- a/src/components/common/AutoComplete.tsx +++ b/src/components/common/AutoComplete.tsx @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ import { Link } from "react-router-dom"; -import { Channel, User } from "revolt.js"; +import { Channel, User, Role } from "revolt.js"; import { Emoji as CustomEmoji } from "revolt.js/esm/maps/Emojis"; import styled, { css } from "styled-components/macro"; @@ -29,11 +29,16 @@ export type AutoCompleteState = type: "channel"; matches: Channel[]; } + | { + type: "role"; + matches: Role[]; + } )); export type SearchClues = { users?: { type: "channel"; id: string } | { type: "all" }; channels?: { server: string }; + roles?: { server: string }; }; export type AutoCompleteProps = { @@ -59,7 +64,7 @@ export function useAutoComplete( function findSearchString( el: HTMLTextAreaElement, - ): ["emoji" | "user" | "channel", string, number] | undefined { + ): ["emoji" | "user" | "channel" | "role", string, number] | undefined { if (el.selectionStart === el.selectionEnd) { const cursor = el.selectionStart; const content = el.value.slice(0, cursor); @@ -71,6 +76,8 @@ export function useAutoComplete( return ["user", "", j]; } else if (content[j] === "#") { return ["channel", "", j]; + } else if (content[j] === "%") { + return ["role", "", j]; } while (j >= 0 && valid.test(content[j])) { @@ -80,7 +87,12 @@ export function useAutoComplete( if (j === -1) return; const current = content[j]; - if (current === ":" || current === "@" || current === "#") { + if ( + current === ":" || + current === "@" || + current === "#" || + current === "%" + ) { const search = content.slice(j + 1, content.length); const minLen = current === ":" ? 2 : 1; @@ -90,6 +102,8 @@ export function useAutoComplete( ? "channel" : current === ":" ? "emoji" + : current === "%" + ? "role" : "user", search.toLowerCase(), current === ":" ? j + 1 : j, @@ -230,6 +244,42 @@ export function useAutoComplete( return; } } + + if (type === "role" && searchClues?.roles) { + const server = client.servers.get(searchClues.roles.server); + + let roles: (Role & { id: string })[] = []; + if (server?.roles) { + roles = Object.entries(server.roles).map(([id, role]) => ({ + ...role, + id, + })); + } + + const matches = ( + search.length > 0 + ? roles.filter((role) => + role.name.toLowerCase().match(regex), + ) + : roles + ) + .splice(0, 5) + .filter((x) => typeof x !== "undefined"); + + if (matches.length > 0) { + const currentPosition = + state.type !== "none" ? state.selected : 0; + + setState({ + type: "role", + matches, + selected: Math.min(currentPosition, matches.length - 1), + within: false, + }); + + return; + } + } } if (state.type !== "none") { @@ -262,6 +312,14 @@ export function useAutoComplete( state.matches[state.selected]._id, "> ", ); + } else if (state.type === "role") { + content.splice( + index, + search.length + 1, + "<%", + state.matches[state.selected].id, + "> ", + ); } else { content.splice( index, @@ -492,7 +550,7 @@ export default function AutoComplete({ {state.type === "user" && state.matches.map((match, i) => ( ))} + {state.type === "role" && + state.matches.map((match, i) => ( + + ))} ); diff --git a/src/components/common/messaging/MessageBox.tsx b/src/components/common/messaging/MessageBox.tsx index 1c69546a..91707b15 100644 --- a/src/components/common/messaging/MessageBox.tsx +++ b/src/components/common/messaging/MessageBox.tsx @@ -567,6 +567,7 @@ export default observer(({ channel }: Props) => { channel.channel_type === "TextChannel" ? { server: channel.server_id! } : undefined, + roles: { server: channel.server_id! }, }); return (