PermViewer: Fix context menu for roleless users & muted channels (#1138)

Co-authored-by: V <vendicated@riseup.net>
Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
This commit is contained in:
V 2023-05-16 00:19:20 +02:00 committed by GitHub
parent bb83c0b672
commit 263884cbd8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 33 additions and 20 deletions

@ -25,14 +25,14 @@ type ContextMenuPatchCallbackReturn = (() => void) | void;
* @param args Any arguments passed into making the context menu, like the guild, channel, user or message for example * @param args Any arguments passed into making the context menu, like the guild, channel, user or message for example
* @returns A callback which is only ran once used to modify the context menu elements (Use to avoid duplicates) * @returns A callback which is only ran once used to modify the context menu elements (Use to avoid duplicates)
*/ */
export type NavContextMenuPatchCallback = (children: Array<React.ReactElement>, ...args: Array<any>) => ContextMenuPatchCallbackReturn; export type NavContextMenuPatchCallback = (children: Array<ReactElement | null>, ...args: Array<any>) => ContextMenuPatchCallbackReturn;
/** /**
* @param navId The navId of the context menu being patched * @param navId The navId of the context menu being patched
* @param children The rendered context menu elements * @param children The rendered context menu elements
* @param args Any arguments passed into making the context menu, like the guild, channel, user or message for example * @param args Any arguments passed into making the context menu, like the guild, channel, user or message for example
* @returns A callback which is only ran once used to modify the context menu elements (Use to avoid duplicates) * @returns A callback which is only ran once used to modify the context menu elements (Use to avoid duplicates)
*/ */
export type GlobalContextMenuPatchCallback = (navId: string, children: Array<React.ReactElement>, ...args: Array<any>) => ContextMenuPatchCallbackReturn; export type GlobalContextMenuPatchCallback = (navId: string, children: Array<ReactElement | null>, ...args: Array<any>) => ContextMenuPatchCallbackReturn;
const ContextMenuLogger = new Logger("ContextMenu"); const ContextMenuLogger = new Logger("ContextMenu");
@ -89,15 +89,18 @@ export function removeGlobalContextMenuPatch(patch: GlobalContextMenuPatchCallba
} }
/** /**
* A helper function for finding the children array of a group nested inside a context menu based on the id of one of its childs * A helper function for finding the children array of a group nested inside a context menu based on the id(s) of its children
* @param id The id of the child * @param id The id of the child. If an array is specified, all ids will be tried
* @param children The context menu children * @param children The context menu children
*/ */
export function findGroupChildrenByChildId(id: string, children: Array<React.ReactElement>, _itemsArray?: Array<React.ReactElement>): Array<React.ReactElement> | null { export function findGroupChildrenByChildId(id: string | string[], children: Array<ReactElement | null>, _itemsArray?: Array<ReactElement | null>): Array<ReactElement | null> | null {
for (const child of children) { for (const child of children) {
if (child == null) continue; if (child == null) continue;
if (child.props?.id === id) return _itemsArray ?? null; if (
(Array.isArray(id) && id.some(id => child.props?.id === id))
|| child.props?.id === id
) return _itemsArray ?? null;
let nextChildren = child.props?.children; let nextChildren = child.props?.children;
if (nextChildren) { if (nextChildren) {
@ -117,7 +120,7 @@ export function findGroupChildrenByChildId(id: string, children: Array<React.Rea
interface ContextMenuProps { interface ContextMenuProps {
contextMenuApiArguments?: Array<any>; contextMenuApiArguments?: Array<any>;
navId: string; navId: string;
children: Array<ReactElement>; children: Array<ReactElement | null>;
"aria-label": string; "aria-label": string;
onSelect: (() => void) | undefined; onSelect: (() => void) | undefined;
onClose: (callback: (...args: Array<any>) => any) => void; onClose: (callback: (...args: Array<any>) => any) => void;

@ -57,6 +57,8 @@ export const settings = definePluginSettings({
}); });
function MenuItem(guildId: string, id?: string, type?: MenuItemParentType) { function MenuItem(guildId: string, id?: string, type?: MenuItemParentType) {
if (type === MenuItemParentType.User && !GuildMemberStore.isMember(guildId, id!)) return null;
return ( return (
<Menu.MenuItem <Menu.MenuItem
id="perm-viewer-permissions" id="perm-viewer-permissions"
@ -122,25 +124,32 @@ function MenuItem(guildId: string, id?: string, type?: MenuItemParentType) {
); );
} }
function makeContextMenuPatch(childId: string, type?: MenuItemParentType): NavContextMenuPatchCallback { function makeContextMenuPatch(childId: string | string[], type?: MenuItemParentType): NavContextMenuPatchCallback {
return (children, props) => () => { return (children, props) => () => {
if (!props) return children; if (!props) return children;
const group = findGroupChildrenByChildId(childId, children); const group = findGroupChildrenByChildId(childId, children);
if (group) { const item = (() => {
switch (type) { switch (type) {
case MenuItemParentType.User: case MenuItemParentType.User:
group.push(MenuItem(props.guildId, props.user.id, type)); return MenuItem(props.guildId, props.user.id, type);
break;
case MenuItemParentType.Channel: case MenuItemParentType.Channel:
group.push(MenuItem(props.guild.id, props.channel.id, type)); return MenuItem(props.guild.id, props.channel.id, type);
break;
case MenuItemParentType.Guild: case MenuItemParentType.Guild:
group.push(MenuItem(props.guild.id)); return MenuItem(props.guild.id);
break; default:
} return null;
} }
})();
if (item == null) return;
if (group)
group.push(item);
else if (childId === "roles" && props.guildId)
// "roles" may not be present due to the member not having any roles. In that case, add it above "Copy ID"
children.splice(-1, 0, <Menu.MenuGroup>{item}</Menu.MenuGroup>);
}; };
} }
@ -160,10 +169,10 @@ export default definePlugin({
} }
], ],
UserPermissions: (guild: Guild, guildMember: GuildMember) => <UserPermissions guild={guild} guildMember={guildMember} />, UserPermissions: (guild: Guild, guildMember?: GuildMember) => !!guildMember && <UserPermissions guild={guild} guildMember={guildMember} />,
userContextMenuPatch: makeContextMenuPatch("roles", MenuItemParentType.User), userContextMenuPatch: makeContextMenuPatch("roles", MenuItemParentType.User),
channelContextMenuPatch: makeContextMenuPatch("mute-channel", MenuItemParentType.Channel), channelContextMenuPatch: makeContextMenuPatch(["mute-channel", "unmute-channel"], MenuItemParentType.Channel),
guildContextMenuPatch: makeContextMenuPatch("privacy", MenuItemParentType.Guild), guildContextMenuPatch: makeContextMenuPatch("privacy", MenuItemParentType.Guild),
start() { start() {

@ -59,7 +59,7 @@
align-items: center; align-items: center;
padding: 8px 5px; padding: 8px 5px;
cursor: pointer; cursor: pointer;
width: 165px; width: 230px;
} }
.vc-permviewer-perms-list-item > div { .vc-permviewer-perms-list-item > div {
@ -121,6 +121,7 @@
position: absolute; position: absolute;
right: 0; right: 0;
scale: 0.9; scale: 0.9;
transition: color ease-in 0.1s;
} }
.vc-permviewer-perms-perms-item .vc-info-icon:hover { .vc-permviewer-perms-perms-item .vc-info-icon:hover {

@ -38,7 +38,7 @@ const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { messag
// dms and group chats // dms and group chats
const dmGroup = findGroupChildrenByChildId("pin", children); const dmGroup = findGroupChildrenByChildId("pin", children);
if (dmGroup && !dmGroup.some(child => child?.props?.id === "reply")) { if (dmGroup && !dmGroup.some(child => child?.props?.id === "reply")) {
const pinIndex = dmGroup.findIndex(c => c.props.id === "pin"); const pinIndex = dmGroup.findIndex(c => c?.props.id === "pin");
return dmGroup.splice(pinIndex + 1, 0, ( return dmGroup.splice(pinIndex + 1, 0, (
<Menu.MenuItem <Menu.MenuItem
id="reply" id="reply"