diff --git a/src/components/common/messaging/Message.tsx b/src/components/common/messaging/Message.tsx index 8b740c8d..21f6f4c2 100644 --- a/src/components/common/messaging/Message.tsx +++ b/src/components/common/messaging/Message.tsx @@ -25,6 +25,7 @@ import Attachment from "./attachments/Attachment"; import { MessageReply } from "./attachments/MessageReply"; import Embed from "./embed/Embed"; import InviteList from "./embed/EmbedInvite"; +import { internalEmit } from "../../../lib/eventEmitter"; interface Props { attachContext?: boolean; @@ -61,15 +62,28 @@ const Message = observer( // bree: Fatal please... const userContext = attachContext ? (attachContextMenu("Menu", { - user: message.author_id, - contextualChannel: message.channel_id, - // eslint-disable-next-line - }) as any) + user: message.author_id, + contextualChannel: message.channel_id, + // eslint-disable-next-line + }) as any) : undefined; const openProfile = () => openScreen({ id: "profile", user_id: message.author_id }); + const handleUserClick = (e: MouseEvent) => { + if (e.shiftKey && user?._id) { + internalEmit( + "MessageBox", + "append", + `<@${user._id}>`, + "mention", + ); + } else { + openProfile() + } + } + // ! FIXME(?): animate on hover const [animate, setAnimate] = useState(false); @@ -91,11 +105,11 @@ const Message = observer( hideReply ? false : (head && - !( - message.reply_ids && - message.reply_ids.length > 0 - )) ?? - false + !( + message.reply_ids && + message.reply_ids.length > 0 + )) ?? + false } contrast={contrast} sending={typeof queued !== "undefined"} @@ -104,10 +118,10 @@ const Message = observer( onContextMenu={ attachContext ? attachContextMenu("Menu", { - message, - contextualChannel: message.channel_id, - queued, - }) + message, + contextualChannel: message.channel_id, + queued, + }) : undefined } onMouseEnter={() => setAnimate(true)} @@ -118,7 +132,7 @@ const Message = observer( target={user} size={36} onContextMenu={userContext} - onClick={openProfile} + onClick={handleUserClick} animate={animate} showServerIdentity /> @@ -133,7 +147,7 @@ const Message = observer( className="author" user={user} onContextMenu={userContext} - onClick={openProfile} + onClick={handleUserClick} showServerIdentity /> & { + user?: User; + prefixAt?: boolean; + showServerIdentity?: boolean; +} export const Username = observer( ({ user, prefixAt, showServerIdentity, ...otherProps - }: { - user?: User; - prefixAt?: boolean; - showServerIdentity?: boolean; - } & JSX.HTMLAttributes) => { + }: UsernameProps) => { let username = user?.username; let color; @@ -108,18 +110,32 @@ export default function UserShort({ const openProfile = () => user && openScreen({ id: "profile", user_id: user._id }); + const handleUserClick = (e: MouseEvent) => { + if (e.shiftKey && user?._id) { + e.preventDefault() + internalEmit( + "MessageBox", + "append", + `<@${user?._id}>`, + "mention", + ); + } else { + openProfile() + } + } + return ( <> diff --git a/src/components/markdown/Renderer.tsx b/src/components/markdown/Renderer.tsx index d484cc2d..c845390c 100644 --- a/src/components/markdown/Renderer.tsx +++ b/src/components/markdown/Renderer.tsx @@ -44,7 +44,7 @@ if (typeof window !== "undefined") { if (code) { navigator.clipboard.writeText(code.textContent?.trim() ?? ""); } - } catch (e) {} + } catch (e) { } }; } @@ -140,7 +140,35 @@ export default function Renderer({ content, disallowBigEmoji }: MarkdownProps) { (ev: MouseEvent) => { if (ev.currentTarget) { const element = ev.currentTarget as HTMLAnchorElement; - if (openLink(element.href)) ev.preventDefault(); + + if (ev.shiftKey) { + switch (element.dataset.type) { + case "mention": { + internalEmit( + "MessageBox", + "append", + `<@${element.dataset.mentionId}>`, + "mention", + ); + ev.preventDefault() + return + } + case "channel_mention": { + internalEmit( + "MessageBox", + "append", + `<#${element.dataset.mentionId}>`, + "channel_mention", + ); + ev.preventDefault() + return + } + } + } + + if (openLink(element.href)) { + ev.preventDefault(); + } } }, [openLink], @@ -162,15 +190,34 @@ export default function Renderer({ content, disallowBigEmoji }: MarkdownProps) { element.removeEventListener("click", handleLink); element.addEventListener("click", handleLink); element.removeAttribute("data-type"); + element.removeAttribute("data-mention-id"); element.removeAttribute("target"); const link = determineLink(element.href); + console.log(link) switch (link.type) { case "profile": { element.setAttribute( "data-type", "mention", ); + element.setAttribute( + "data-mention-id", + link.id + ) + break; + } + case "navigate": { + if (link.navigation_type === 'channel') { + element.setAttribute( + "data-type", + "channel_mention", + ); + element.setAttribute( + "data-mention-id", + link.channel_id + ) + } break; } case "external": { diff --git a/src/components/navigation/left/ServerSidebar.tsx b/src/components/navigation/left/ServerSidebar.tsx index ad4b1d28..c07601df 100644 --- a/src/components/navigation/left/ServerSidebar.tsx +++ b/src/components/navigation/left/ServerSidebar.tsx @@ -22,6 +22,7 @@ import { mapChannelWithUnread, useUnreads } from "./common"; import { ChannelButton } from "../items/ButtonItem"; import ConnectionStatus from "../items/ConnectionStatus"; +import { internalEmit } from "../../../lib/eventEmitter"; interface Props { unreads: Unreads; @@ -90,6 +91,17 @@ const ServerSidebar = observer((props: Props) => { return ( { + if (e.shiftKey) { + internalEmit( + "MessageBox", + "append", + `<#${entry._id}>`, + "channel_mention", + ); + e.preventDefault() + } + }} key={entry._id} active={active} to={`/server/${server!._id}/channel/${entry._id}`}> diff --git a/src/components/navigation/right/MemberList.tsx b/src/components/navigation/right/MemberList.tsx index 3b8c1b63..6840ee22 100644 --- a/src/components/navigation/right/MemberList.tsx +++ b/src/components/navigation/right/MemberList.tsx @@ -12,6 +12,7 @@ import { } from "../../../context/intermediate/Intermediate"; import { UserButton } from "../items/ButtonItem"; +import { internalEmit } from "../../../lib/eventEmitter"; export type MemberListGroup = { type: "online" | "offline" | "role"; @@ -53,12 +54,21 @@ const ItemContent = memo( user={item} margin context={context} - onClick={() => - openScreen({ - id: "profile", - user_id: item._id, - }) - } + onClick={e => { + if (e.shiftKey) { + internalEmit( + "MessageBox", + "append", + `<@${item._id}>`, + "mention", + ); + } else[ + openScreen({ + id: "profile", + user_id: item._id, + }) + ] + }} /> ), ); diff --git a/src/lib/ConditionalLink.tsx b/src/lib/ConditionalLink.tsx index b2057c4d..fbe4fdd9 100644 --- a/src/lib/ConditionalLink.tsx +++ b/src/lib/ConditionalLink.tsx @@ -9,7 +9,7 @@ export default function ConditionalLink(props: Props) { const { active, ...linkProps } = props; if (active) { - return {props.children}; + return {props.children}; } return ; } diff --git a/src/lib/links.ts b/src/lib/links.ts index d915a43c..c41b5bf5 100644 --- a/src/lib/links.ts +++ b/src/lib/links.ts @@ -1,6 +1,7 @@ type LinkType = | { type: "profile"; id: string } - | { type: "navigate"; path: string } + | { type: "navigate"; path: string; navigation_type?: null } + | { type: "navigate"; path: string; navigation_type: 'channel'; channel_id: string } | { type: "external"; href: string; url: URL } | { type: "none" }; @@ -11,6 +12,8 @@ const ALLOWED_ORIGINS = [ "local.revolt.chat", ]; +const CHANNEL_PATH_RE = /^\/server\/[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26}\/channel\/[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26}$/ + export function determineLink(href?: string): LinkType { let internal, url: URL | null = null; @@ -27,6 +30,10 @@ export function determineLink(href?: string): LinkType { return { type: "profile", id }; } } else { + console.log(path) + if(CHANNEL_PATH_RE.test(path)) { + return { type: 'navigate', path, navigation_type: 'channel', channel_id: path.slice(43) } + } return { type: "navigate", path }; }