Skip to content

Commit

Permalink
Add role mentions
Browse files Browse the repository at this point in the history
  • Loading branch information
SupertigerDev committed Dec 9, 2024
1 parent d3a4707 commit 70cf93d
Show file tree
Hide file tree
Showing 9 changed files with 119 additions and 28 deletions.
6 changes: 6 additions & 0 deletions src/chat-api/Bitwise.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,12 @@ export const ROLE_PERMISSIONS = {
bit: 128,
icon: "edit",
},
MENTION_ROLES: {
name: "Mention Roles",
bit: 256,
description: "Allow users to mention roles",
icon: "alternate_email",
},
};

export const hasBit = (permissions: number, bit: number) => {
Expand Down
1 change: 1 addition & 0 deletions src/chat-api/RawData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export interface RawMessage {
}[];

buttons: RawMessageButton[];
roleMentions: RawServerRole[];
}

export interface RawMessageButton {
Expand Down
18 changes: 14 additions & 4 deletions src/chat-api/events/messageEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import useServerMembers from "../store/useServerMembers";
import { ROLE_PERMISSIONS } from "../Bitwise";
import useFriends from "../store/useFriends";
import { pushMessageNotification } from "@/components/in-app-notification-previews/useInAppNotificationPreviews";
import useServers from "../store/useServers";

export function onMessageCreated(payload: {
socketId: string;
Expand Down Expand Up @@ -42,13 +43,18 @@ export function onMessageCreated(payload: {
const users = useUsers();
const account = useAccount();
const friends = useFriends();
const servers = useServers();
const { hasFocus } = useWindowProperties();

const accountUser = account.user();

const hasBlockedRecipient =
friends.get(payload.message.createdBy.id)?.status === FriendStatus.BLOCKED;

const member = members.get(channel?.serverId!, payload.message.createdBy.id);
const selfMember = members.get(channel?.serverId!, accountUser?.id!);
const server = servers.get(channel?.serverId!);

batch(() => {
channel?.updateLastMessaged(payload.message.createdAt);

Expand All @@ -68,10 +74,6 @@ export function onMessageCreated(payload: {
if (hasBlockedRecipient) return false;
const everyoneMentioned = payload.message.content?.includes("[@:e]");
if (everyoneMentioned && channel?.serverId) {
const member = members.get(
channel.serverId,
payload.message.createdBy.id
);
const hasPerm =
member?.isServerCreator() ||
member?.hasPermission(ROLE_PERMISSIONS.MENTION_EVERYONE);
Expand All @@ -87,6 +89,14 @@ export function onMessageCreated(payload: {

if (quoteMention) return true;

const isRoleMentioned =
member?.hasPermission(ROLE_PERMISSIONS.MENTION_ROLES) &&
payload.message.roleMentions.find(
(r) => server?.defaultRoleId !== r.id && selfMember?.hasRole(r.id)
);

if (isRoleMentioned) return true;

const replyMention =
payload.message.mentionReplies &&
payload.message.replyMessages.find(
Expand Down
15 changes: 12 additions & 3 deletions src/common/Sound.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,13 @@ function getCustomSound(type: "MESSAGE" | "MESSAGE_MENTION") {
}

export function isMentioned(message: RawMessage, serverId?: string) {
const { account, serverMembers } = useStore();
const { account, serverMembers, servers } = useStore();
const userId = account.user()?.id;

const member = serverMembers.get(serverId!, message.createdBy.id);
const selfMember = serverMembers.get(serverId!, userId!);
const server = servers.get(serverId!);

const mentionedMe = message.mentions?.find(
(m) => m.id === account.user()?.id
);
Expand All @@ -104,13 +108,18 @@ export function isMentioned(message: RawMessage, serverId?: string) {
(m) => m.replyToMessage?.createdBy?.id === userId
);

if (quoteMention || replyMention) {
const isRoleMentioned =
member?.hasPermission(ROLE_PERMISSIONS.MENTION_ROLES) &&
message.roleMentions.find(
(r) => r.id !== server?.defaultRoleId && selfMember?.hasRole(r.id)
);

if (quoteMention || replyMention || isRoleMentioned) {
return true;
}

const everyoneMentioned = message.content?.includes("[@:e]");
if (everyoneMentioned && serverId) {
const member = serverMembers.get(serverId, message.createdBy.id);
const hasPerm =
member?.isServerCreator() ||
member?.hasPermission(ROLE_PERMISSIONS.MENTION_EVERYONE);
Expand Down
19 changes: 19 additions & 0 deletions src/components/Markup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { GenericMention } from "./markup/GenericMention";
import { TimestampMention, TimestampType } from "./markup/TimestampMention";
import { Dynamic } from "solid-js/web";
import { Post } from "@/chat-api/store/usePosts";
import useServerRoles from "@/chat-api/store/useServerRoles";

export interface Props {
text: string;
Expand All @@ -43,6 +44,7 @@ export interface Props {
isQuote?: boolean;
animateEmoji?: boolean;
class?: string;
serverId?: string;
}

type RenderContext = {
Expand Down Expand Up @@ -72,6 +74,7 @@ type CustomEntity = Entity & { type: "custom" };
function transformCustomEntity(entity: CustomEntity, ctx: RenderContext) {
const channels = useChannels();
const users = useUsers();
const serverRoles = useServerRoles();
const type = entity.params.type;
const expr = sliceText(ctx, entity.innerSpan, { countText: false });
switch (type) {
Expand All @@ -83,6 +86,22 @@ function transformCustomEntity(entity: CustomEntity, ctx: RenderContext) {
}
break;
}
// Role mentions
case "r": {
const serverId = ctx.props().serverId;

if (!serverId) {
break;
}

const role = serverRoles.get(serverId, expr);
if (role) {
ctx.textCount += expr.length;
return <GenericMention name={role.name} color={role.hexColor} />;
}

break;
}
case "@": {
const message = ctx.props().message;
const user =
Expand Down
7 changes: 3 additions & 4 deletions src/components/markup/GenericMention.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
export function GenericMention(props: { name: string }) {
export function GenericMention(props: { name: string; color?: string }) {
return (
<div
class="mention">
<div class="mention" style={{ color: props.color }}>
@{props.name}
</div>
);
}
}
46 changes: 37 additions & 9 deletions src/components/message-pane/MessagePane.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ import DropDown, { DropDownItem } from "../ui/drop-down/DropDown";
import { useCustomScrollbar } from "../custom-scrollbar/CustomScrollbar";
import { t } from "i18next";
import { RemindersModal } from "../reminders-modal/RemindersModal";
import useServerRoles from "@/chat-api/store/useServerRoles";

const DeleteMessageModal = lazy(
() => import("./message-delete-modal/MessageDeleteModal")
Expand Down Expand Up @@ -1195,6 +1196,7 @@ function Floating(props: {
const emojiRegex = /:[\w+-]+:/g;
const channelMentionRegex = /#([^#]+)#/g;
const userMentionRegex = /@([^@:]+):([a-zA-Z0-9]+)/g;
const roleMentionRegex = /@([^#]+)@/g;

function randomIndex(arrLength: number) {
return Math.floor(Math.random() * arrLength);
Expand All @@ -1213,6 +1215,9 @@ export function formatMessage(
const serverMembers = useServerMembers();
const account = useAccount();
const servers = useServers();
const roles = useServerRoles();

const serverRoles = roles.getAllByServerId(serverId!);

const serverChannels = channels.getChannelsByServerId(serverId!);
const members = serverMembers.array(serverId!);
Expand Down Expand Up @@ -1277,6 +1282,11 @@ export function formatMessage(
return `[@:${member.user().id}]`;
}
);
finalString = finalString.replace(roleMentionRegex, (match, group) => {
const channel = serverRoles.find((c) => c!.name === group);
if (!channel) return match;
return `[r:${channel.id}]`;
});
// replace channel mentions
finalString = finalString.replaceAll(
channelMentionRegex,
Expand Down Expand Up @@ -1557,20 +1567,33 @@ function FloatingUserSuggestions(props: {
textArea?: HTMLTextAreaElement;
}) {
const params = useParams<{ serverId?: string; channelId: string }>();
const { serverMembers, channels, account } = useStore();
const { serverMembers, channels, account, serverRoles, servers } = useStore();

const server = createMemo(() => servers.get(params.serverId!));

const members = () => serverMembers.array(params.serverId!);
const roles = () =>
serverRoles
.getAllByServerId(params.serverId!)
.filter((r) => r.id !== server()?.defaultRoleId);

const hasPermissionToMentionEveryone = () => {
if (!params.serverId) return false;
const member = serverMembers.get(params.serverId, account.user()?.id!);
return member?.hasPermission?.(ROLE_PERMISSIONS.MENTION_EVERYONE);
};

const hasPermissionToMentionRoles = () => {
if (!params.serverId) return false;
const member = serverMembers.get(params.serverId, account.user()?.id!);
return member?.hasPermission?.(ROLE_PERMISSIONS.MENTION_ROLES);
};

const searchedServerUsers = () =>
matchSorter(
[
...members(),
...(hasPermissionToMentionRoles() ? roles() : []),
...(hasPermissionToMentionEveryone()
? [
{
Expand Down Expand Up @@ -1599,14 +1622,14 @@ function FloatingUserSuggestions(props: {
] as any[],
props.search,
{
keys: [(e) => e.user().username, (e) => e.nickname],
keys: [(e) => e.user?.().username, (e) => e.nickname, (e) => e.name],
}
)
.slice(0, 10)
.sort((a, b) => {
// move all special users to the bottom
if (a.user().special && !b.user().special) return 1;
if (!a.user().special && b.user().special) return -1;
if (a.user?.().special && !b.user?.().special) return 1;
if (!a.user?.().special && b.user?.().special) return -1;
return 0;
});

Expand All @@ -1628,14 +1651,15 @@ function FloatingUserSuggestions(props: {
)
);

const onUserClick = (user: User) => {
const onUserClick = (user: User & { name?: string }) => {
console.log(user);
if (!props.textArea) return;
if (!user.tag) {
appendText(
params.channelId,
props.textArea,
props.search,
`${user.username} `
`${user.username || user.name}${user.name ? "@" : ""} `
);
return;
}
Expand Down Expand Up @@ -1680,7 +1704,7 @@ function UserSuggestionItem(props: {
nickname?: string;
onHover: () => void;
selected: boolean;
user: User & { special?: boolean };
user: User & { special?: boolean; name?: string };
onclick(user: User): void;
}) {
return (
Expand All @@ -1689,12 +1713,16 @@ function UserSuggestionItem(props: {
selected={props.selected}
class={styles.suggestionItem}
onclick={() => props.onclick(props.user)}
style={{ color: props.user.name ? props.user.hexColor : undefined }}
>
<Show when={!props.user?.special} fallback={<div>@</div>}>
<Show
when={props.user.username && !props.user?.special}
fallback={<div>@</div>}
>
<Avatar user={props.user} animate={props.selected} size={15} />
</Show>
<div class={styles.suggestLabel}>
{props.nickname || props.user.username}
{props.nickname || props.user.username || props.user.name}
</div>
<Show when={props.nickname}>
<div class={styles.suggestionInfo}>{props.user.username}</div>
Expand Down
33 changes: 26 additions & 7 deletions src/components/message-pane/message-item/MessageItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -249,15 +249,16 @@ const MessageItem = (props: MessageItemProps) => {
const params = useParams();
const { serverMembers, servers, account, friends } = useStore();
const [hovered, setHovered] = createSignal(false);
const serverMember = () =>
const serverMember = createMemo(() =>
params.serverId
? serverMembers.get(params.serverId, props.message.createdBy.id)
: undefined;
: undefined
);
const server = createMemo(() => servers.get(params.serverId!));

const isServerCreator = () =>
params.serverId
? servers.get(params.serverId)?.createdById === props.message.createdBy.id
: undefined;
server()?.createdById === props.message.createdBy.id;

const { createPortal } = useCustomPortal();

const currentTime = props.message?.createdAt;
Expand Down Expand Up @@ -294,11 +295,15 @@ const MessageItem = (props: MessageItemProps) => {
return member.hasPermission?.(ROLE_PERMISSIONS.MENTION_EVERYONE);
};

const selfMember = createMemo(() =>
serverMembers.get(params.serverId!, account.user()?.id!)
);
createEffect(
on(
[
() => props.message.mentions?.length,
() => props.message.quotedMessages?.length,
() => props.message.roleMentions?.length,
],
() => {
setTimeout(() => {
Expand All @@ -313,10 +318,19 @@ const MessageItem = (props: MessageItemProps) => {
const isReplied = props.message.replyMessages?.find(
(m) => m.replyToMessage?.createdBy?.id === account.user()?.id
);
const isRoleMentioned =
serverMember()?.hasPermission(ROLE_PERMISSIONS.MENTION_ROLES) &&
props.message.roleMentions.find(
(r) =>
r.id !== server()?.defaultRoleId && selfMember()?.hasRole(r.id)
);
const isMentioned =
isEveryoneMentioned ||
props.message.mentions?.find((u) => u.id === account.user()?.id);
setIsMentioned(!!isQuoted || !!isMentioned || !!isReplied);

setIsMentioned(
!!isQuoted || !!isMentioned || !!isReplied || !!isRoleMentioned
);
setIsSomeoneMentioned(isSomeoneMentioned);
});
}
Expand Down Expand Up @@ -442,9 +456,14 @@ const MessageItem = (props: MessageItemProps) => {
};

const Content = (props: { message: Message; hovered: boolean }) => {
const params = useParams<{ serverId?: string }>();
return (
<div class={styles.content}>
<Markup message={props.message} text={props.message.content || ""} />
<Markup
message={props.message}
text={props.message.content || ""}
serverId={params.serverId}
/>
<Show
when={
!props.message.uploadingAttachment || props.message.content?.trim()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ export const MessageLogArea = (props: {
bottomSkeletonHeight
);

const channel = () => channels.get(params.channelId!)!;
const channel = createMemo(() => channels.get(params.channelId!));

const properties = () => channelProperties.get(params.channelId);

Expand Down

0 comments on commit 70cf93d

Please sign in to comment.