ShowHiddenChannels: better ui, alternative display mode (#446)

Co-authored-by: Ven <vendicated@riseup.net>
This commit is contained in:
Nuckyz 2023-01-23 18:04:50 -03:00 committed by GitHub
parent 8a43e9b25f
commit 75050e74ca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 141 additions and 83 deletions

View File

@ -23,8 +23,7 @@ import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import Logger from "@utils/Logger";
import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy } from "@webpack";
import { Parser, UserStore } from "@webpack/common";
import { moment, Parser, Timestamp, UserStore } from "@webpack/common";
function addDeleteStyleClass() {
if (Settings.plugins.MessageLogger.deleteStyle === "text") {
@ -41,13 +40,7 @@ export default definePlugin({
description: "Temporarily logs deleted and edited messages.",
authors: [Devs.rushii, Devs.Ven],
timestampModule: null as any,
moment: null as Function | null,
start() {
this.moment = findByPropsLazy("relativeTimeRounding", "relativeTimeThreshold");
this.timestampModule = findByPropsLazy("messageLogger_TimestampComponent");
addDeleteStyleClass();
},
@ -59,7 +52,6 @@ export default definePlugin({
},
renderEdit(edit: { timestamp: any, content: string; }) {
const Timestamp = this.timestampModule.messageLogger_TimestampComponent;
return (
<ErrorBoundary noop>
<div className="messageLogger-edited">
@ -78,7 +70,7 @@ export default definePlugin({
makeEdit(newMessage: any, oldMessage: any): any {
return {
timestamp: this.moment?.call(newMessage.edited_timestamp),
timestamp: moment?.call(newMessage.edited_timestamp),
content: oldMessage.content
};
},
@ -312,17 +304,6 @@ export default definePlugin({
]
},
{
// Message "(edited)" timestamp component
// Module 23552
find: "Messages.MESSAGE_EDITED_TIMESTAMP_A11Y_LABEL.format",
replacement: {
// Re-export the timestamp component under a findable name
match: /{(\w{1,2}:\(\)=>(\w{1,2}))}/,
replace: "{$1,messageLogger_TimestampComponent:()=>$2}"
}
},
{
// Message context base menu
// Module 600300

View File

@ -23,125 +23,177 @@ import { Flex } from "@components/Flex";
import { Devs } from "@utils/constants";
import { ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal";
import definePlugin, { OptionType } from "@utils/types";
import { Button, ChannelStore, PermissionStore, SnowflakeUtils, Text } from "@webpack/common";
import { findByPropsLazy } from "@webpack";
import { Button, ChannelStore, moment, Parser, PermissionStore, SnowflakeUtils, Text, Timestamp, Tooltip } from "@webpack/common";
const ChannelListClasses = findByPropsLazy("channelName", "subtitle", "modeMuted", "iconContainer");
const CONNECT = 1048576n;
const VIEW_CHANNEL = 1024n;
enum ChannelTypes {
GUILD_TEXT = 0,
GUILD_ANNOUNCEMENT = 5,
GUILD_FORUM = 15
}
const ChannelTypesToChannelName = {
[ChannelTypes.GUILD_TEXT]: "TEXT",
[ChannelTypes.GUILD_ANNOUNCEMENT]: "ANNOUNCEMENT",
[ChannelTypes.GUILD_FORUM]: "FORUM"
};
enum ShowMode {
LockIcon,
HiddenIconWithMutedStyle
}
export default definePlugin({
name: "ShowHiddenChannels",
description: "Show hidden channels",
authors: [Devs.BigDuck, Devs.AverageReactEnjoyer, Devs.D3SOX, Devs.Ven],
description: "Show channels that you do not have access to view.",
authors: [Devs.BigDuck, Devs.AverageReactEnjoyer, Devs.D3SOX, Devs.Ven, Devs.Nuckyz, Devs.Nickyux],
options: {
hideUnreads: {
description: "Hide unreads",
description: "Hide Unreads",
type: OptionType.BOOLEAN,
default: true,
restartNeeded: true // Restart is needed to refresh channel list
restartNeeded: true
},
showMode: {
description: "The mode used to display hidden channels.",
type: OptionType.SELECT,
options: [
{ label: "Plain style with Lock Icon instead", value: ShowMode.LockIcon, default: true },
{ label: "Muted style with hidden eye icon on the right", value: ShowMode.HiddenIconWithMutedStyle },
],
restartNeeded: true
}
},
patches: [
{
// RenderLevel defines if a channel is hidden, collapsed in category, visible, etc
find: ".CannotShow",
replacement: {
match: /renderLevel:(\w+)\.CannotShow/g,
replace: "renderLevel:Vencord.Plugins.plugins.ShowHiddenChannels.shouldShow(this.record, this.category, this.isMuted)?$1.Show:$1.CannotShow"
}
// These replacements only change the necessary CannotShow's
replacement: [
{
match: /(?<restOfFunction>renderLevel:(?<renderLevelExpression>\i\(this,\i\)\?\i\.Show:\i\.WouldShowIfUncollapsed).+?renderLevel:).+?,/,
replace: "$<restOfFunction>$<renderLevelExpression>,"
},
{
match: /(?<restOfFunction>activeJoinedRelevantThreads.{1,100}renderLevel:(?<RenderLevels>\i)\.Show.+?renderLevel:).+?,/,
replace: "$<restOfFunction>$<RenderLevels>.Show,"
},
{
match: /(?<restOfFunction>isChannelGatedAndVisible\(this\.record\.guild_id,this\.record\.id\).+?renderLevel:)(?<RenderLevels>\i)\.CannotShow/,
replace: "$<restOfFunction>this.category.isCollapsed?$<RenderLevels>.WouldShowIfUncollapsed:$<RenderLevels>.Show"
},
{
match: /(?<restOfFunction>getRenderLevel=function.+?return).+?\?(?<renderLevelExpression>.+?):\i\.CannotShow}/,
replace: "$<restOfFunction> $<renderLevelExpression>}"
}
]
},
{
// inside the onMouseClick handler, we check if the channel is hidden and open the modal if it is
find: ".handleThreadsPopoutClose();",
replacement: {
match: /((\w)\.handleThreadsPopoutClose\(\);)/g,
replace: "if(arguments[0].button===0&&Vencord.Plugins.plugins.ShowHiddenChannels.channelSelected($2?.props?.channel))return;$1"
match: /(?<this>\i)\.handleThreadsPopoutClose\(\);/,
replace: "if(arguments[0].button===0&&$self.channelSelected($<this>?.props?.channel))return;$&"
}
},
{
// Prevent categories from disappearing when they're collapsed
find: ".prototype.shouldShowEmptyCategory=function(){",
replacement: {
match: /(\.prototype\.shouldShowEmptyCategory=function\(\){)/g,
replace: "$1return true;"
}
},
{
// Hide unreads
find: "?\"button\":\"link\"",
find: ".UNREAD_HIGHLIGHT",
predicate: () => Settings.plugins.ShowHiddenChannels.hideUnreads === true,
replacement: [{
// Hide unreads
match: /(?<restOfFunction>\i\.connected,)(?<hasUnread>\i)=(?<props>\i).unread/,
replace: "$<restOfFunction>$<hasUnread>=$self.isHiddenChannel($<props>.channel)?false:$<props>.unread"
}]
},
{
find: ".Messages.CHANNEL_TOOLTIP_DIRECTORY",
predicate: () => Settings.plugins.ShowHiddenChannels.showMode === ShowMode.LockIcon,
replacement: {
match: /(\w)\.connected,(\w)=(\w\.unread),(\w=\w\.canHaveDot)/g,
replace: "$1.connected,$2=Vencord.Plugins.plugins.ShowHiddenChannels.isHiddenChannel($1.channel)?false:$3,$4"
// Lock Icon
match: /switch\((?<channel>\i)\.type\).{1,30}\.GUILD_ANNOUNCEMENT.{1,30}\(0,\i\.\i\)\(\i\)/,
replace: "if($self.isHiddenChannel($<channel>))return $self.LockIcon;$&"
}
},
{
find: ".UNREAD_HIGHLIGHT",
predicate: () => Settings.plugins.ShowHiddenChannels.showMode === ShowMode.HiddenIconWithMutedStyle,
replacement: [
// Make the channel appear as muted if it's hidden
{
match: /(?<restOfFunction>\i\.name,)(?<isMuted>\i)=(?<props>\i).muted/,
replace: "$<restOfFunction>$<isMuted>=$self.isHiddenChannel($<props>.channel)?true:$<props>.muted"
},
// Add the hidden eye icon if the channel is hidden
{
match: /channel:(?<channel>\i),.+?\.channelName.+?\.children.+?:null/,
replace: "$&,$self.isHiddenChannel($<channel>)?$self.HiddenChannelIcon():null"
},
// Make voice channels also appear as muted if they are muted
{
match: /(?<restOfFunction>.wrapper:\i\(\).notInteractive,)(?<secondRestOfFunction>.+?)(?<isMutedClassExpression>(?<isMuted>\i)\?\i\.MUTED:)/,
replace: "$<restOfFunction>$<isMutedClassExpression>\"\",$<secondRestOfFunction>$<isMuted>?\"\":"
}
]
},
{
// Hide New unreads box for hidden channels
find: '.displayName="ChannelListUnreadsStore"',
replacement: {
match: /((.)\.getGuildId\(\))(&&\(!\(.\.isThread.{1,100}\.hasRelevantUnread\()/,
replace: "$1&&!$2._isHiddenChannel$3"
match: /(?<restOfFunction>return null!=(?<channel>\i))(?<secondRestOfFunction>&&null!=\i\.getGuildId\(\).{1,120}hasRelevantUnread\(\i\)\))/,
replace: "$<restOfFunction>&&!$self.isHiddenChannel($<channel>)$<secondRestOfFunction>"
}
},
// Lock Icon
{
find: ".rulesChannelId))",
replacement: {
match: /(\.locked.{0,400})(switch\((\i)\.type\))/,
replace: "$1 if($3._isHiddenChannel)return $self.LockIcon;$2"
}
}
],
shouldShow(channel, category, isMuted) {
if (!this.isHiddenChannel(channel)) return false;
if (!category) return false;
if (channel.type === 0 && category.guild?.hideMutedChannels && isMuted) return false;
return !category.isCollapsed;
},
isHiddenChannel(channel) {
if (!channel) return false;
if (channel.channelId)
channel = ChannelStore.getChannel(channel.channelId);
if (!channel || channel.isDM() || channel.isGroupDM() || channel.isMultiUserDM())
return false;
// check for disallowed voice channels too so that they get hidden when collapsing the category
channel._isHiddenChannel = !PermissionStore.can(VIEW_CHANNEL, channel) || (channel.type === 2 && !PermissionStore.can(CONNECT, channel));
return channel._isHiddenChannel;
if (channel.channelId) channel = ChannelStore.getChannel(channel.channelId);
if (!channel || channel.isDM() || channel.isGroupDM() || channel.isMultiUserDM()) return false;
return !PermissionStore.can(VIEW_CHANNEL, channel);
},
channelSelected(channel) {
if (!channel) return false;
const isHidden = this.isHiddenChannel(channel);
// check for type again, otherwise it would show it for hidden stage channels
if (channel.type === 0 && isHidden) {
const lastMessageDate = channel.lastMessageId ? new Date(SnowflakeUtils.extractTimestamp(channel.lastMessageId)).toLocaleString() : null;
// Check for type, otherwise it would attempt to show the modal for stage channels
if ([ChannelTypes.GUILD_TEXT, ChannelTypes.GUILD_ANNOUNCEMENT, ChannelTypes.GUILD_FORUM].includes(channel.type) && isHidden) {
openModal(modalProps => (
<ModalRoot size={ModalSize.SMALL} {...modalProps}>
<ModalHeader>
<Flex>
<Text variant="heading-md/bold">{channel.name}</Text>
<Text variant="heading-md/bold">#{channel.name}</Text>
{<Badge text={ChannelTypesToChannelName[channel.type]} color="var(--brand-experiment)" />}
{channel.isNSFW() && <Badge text="NSFW" color="var(--status-danger)" />}
</Flex>
</ModalHeader>
<ModalContent style={{ marginBottom: 10, marginTop: 10, marginRight: 8, marginLeft: 8 }}>
<Text variant="text-md/normal">You don't have the permission to view the messages in this channel.</Text>
{(channel.topic || "").length > 0 && (
<Text variant="text-md/normal">You don't have permission to view {channel.type === ChannelTypes.GUILD_FORUM ? "posts" : "messages"} in this channel.</Text>
{(channel.topic ?? "").length > 0 && (
<>
<Text variant="text-md/bold" style={{ marginTop: 10 }}>
Topic:
{channel.type === ChannelTypes.GUILD_FORUM ? "Guidelines:" : "Topic:"}
</Text>
<Text variant="code">{channel.topic}</Text>
<div style={{ color: "var(--text-normal)", marginTop: 10 }}>
{Parser.parseTopic(channel.topic, true, { channelId: channel.id })}
</div>
</>
)}
{lastMessageDate && (
{channel.lastMessageId && (
<>
<Text variant="text-md/bold" style={{ marginTop: 10 }}>
Last message sent:
{channel.type === ChannelTypes.GUILD_FORUM ? "Last Post Created" : "Last Message Sent:"}
</Text>
<Text variant="code">{lastMessageDate}</Text>
<div style={{ color: "var(--text-normal)", marginTop: 10 }}>
<Timestamp timestamp={moment(SnowflakeUtils.extractTimestamp(channel.lastMessageId))} />
</div>
</>
)}
</ModalContent>
@ -164,11 +216,34 @@ export default definePlugin({
LockIcon: () => (
<svg
className={ChannelListClasses.icon}
height="18"
width="20"
viewBox="0 0 24 24"
aria-hidden={true}
role="img"
>
<path fill="var(--channel-icon)" d="M17 11V7C17 4.243 14.756 2 12 2C9.242 2 7 4.243 7 7V11C5.897 11 5 11.896 5 13V20C5 21.103 5.897 22 7 22H17C18.103 22 19 21.103 19 20V13C19 11.896 18.103 11 17 11ZM12 18C11.172 18 10.5 17.328 10.5 16.5C10.5 15.672 11.172 15 12 15C12.828 15 13.5 15.672 13.5 16.5C13.5 17.328 12.828 18 12 18ZM15 11H9V7C9 5.346 10.346 4 12 4C13.654 4 15 5.346 15 7V11Z" />
<path fillRule="evenodd" fill="currentColor" d="M17 11V7C17 4.243 14.756 2 12 2C9.242 2 7 4.243 7 7V11C5.897 11 5 11.896 5 13V20C5 21.103 5.897 22 7 22H17C18.103 22 19 21.103 19 20V13C19 11.896 18.103 11 17 11ZM12 18C11.172 18 10.5 17.328 10.5 16.5C10.5 15.672 11.172 15 12 15C12.828 15 13.5 15.672 13.5 16.5C13.5 17.328 12.828 18 12 18ZM15 11H9V7C9 5.346 10.346 4 12 4C13.654 4 15 5.346 15 7V11Z" />
</svg>
),
HiddenChannelIcon: () => (
<Tooltip text="Hidden Channel">
{({ onMouseLeave, onMouseEnter }) => (
<svg
onMouseLeave={onMouseLeave}
onMouseEnter={onMouseEnter}
className={ChannelListClasses.icon}
width="24"
height="24"
viewBox="0 0 24 24"
aria-hidden={true}
role="img"
style={{ marginLeft: 6, zIndex: 0, cursor: "not-allowed" }}
>
<path fillRule="evenodd" fill="currentColor" d="m19.8 22.6-4.2-4.15q-.875.275-1.762.413Q12.95 19 12 19q-3.775 0-6.725-2.087Q2.325 14.825 1 11.5q.525-1.325 1.325-2.463Q3.125 7.9 4.15 7L1.4 4.2l1.4-1.4 18.4 18.4ZM12 16q.275 0 .512-.025.238-.025.513-.1l-5.4-5.4q-.075.275-.1.513-.025.237-.025.512 0 1.875 1.312 3.188Q10.125 16 12 16Zm7.3.45-3.175-3.15q.175-.425.275-.862.1-.438.1-.938 0-1.875-1.312-3.188Q13.875 7 12 7q-.5 0-.938.1-.437.1-.862.3L7.65 4.85q1.025-.425 2.1-.638Q10.825 4 12 4q3.775 0 6.725 2.087Q21.675 8.175 23 11.5q-.575 1.475-1.512 2.738Q20.55 15.5 19.3 16.45Zm-4.625-4.6-3-3q.7-.125 1.288.112.587.238 1.012.688.425.45.613 1.038.187.587.087 1.162Z" />
</svg>
)}
</Tooltip>
)
});

View File

@ -75,6 +75,7 @@ export let Button: any;
export const ButtonLooks = findByPropsLazy("BLANK", "FILLED", "INVERTED") as Record<"FILLED" | "INVERTED" | "OUTLINED" | "LINK" | "BLANK", string>;
export let Switch: any;
export let Tooltip: Components.Tooltip;
export let Timestamp: any;
export let Router: any;
export let TextInput: any;
export let Text: (props: TextProps) => JSX.Element;
@ -185,6 +186,8 @@ waitFor(["Hovers", "Looks", "Sizes"], m => Button = m);
waitFor(filters.byCode("tooltipNote", "ringTarget"), m => Switch = m);
waitFor(filters.byCode(".Messages.MESSAGE_EDITED_TIMESTAMP_A11Y_LABEL.format"), m => Timestamp = m);
waitFor(["Positions", "Colors"], m => Tooltip = m);
waitFor(m => m.Types?.PRIMARY === "cardPrimary", m => Card = m);
@ -307,4 +310,3 @@ export const ContextMenu = mapMangledModuleLazy('type:"CONTEXT_MENU_OPEN"', {
export const MaskedLinkStore = mapMangledModuleLazy('"MaskedLinkStore"', {
openUntrustedLink: filters.byCode(".apply(this,arguments)")
});