diff options
-rw-r--r-- | src/plugins/apiNotices.ts | 4 | ||||
-rw-r--r-- | src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx | 269 | ||||
-rw-r--r-- | src/plugins/showHiddenChannels/index.tsx | 118 | ||||
-rw-r--r-- | src/plugins/showHiddenChannels/style.css | 34 | ||||
-rw-r--r-- | src/plugins/typingTweaks.tsx | 12 | ||||
-rw-r--r-- | src/utils/text.ts | 67 | ||||
-rw-r--r-- | src/webpack/patchWebpack.ts | 6 |
7 files changed, 365 insertions, 145 deletions
diff --git a/src/plugins/apiNotices.ts b/src/plugins/apiNotices.ts index c362f76..8922ace 100644 --- a/src/plugins/apiNotices.ts +++ b/src/plugins/apiNotices.ts @@ -34,8 +34,8 @@ export default definePlugin({ ";if(Vencord.Api.Notices.currentNotice)return false$&" }, { - match: /(?<=NOTICE_DISMISS:function.+?){(?=if\(null==(.+?)\))/, - replace: '{if($1?.id=="VencordNotice")return ($1=null,Vencord.Api.Notices.nextNotice(),true);' + match: /(?<=,NOTICE_DISMISS:function\(\i\){)(?=if\(null==(\i)\))/, + replace: 'if($1?.id=="VencordNotice")return($1=null,Vencord.Api.Notices.nextNotice(),true);' } ] } diff --git a/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx b/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx index e5c5ee2..7e66a6a 100644 --- a/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx +++ b/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx @@ -18,18 +18,17 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { LazyComponent } from "@utils/misc"; -import { proxyLazy } from "@utils/proxyLazy"; import { formatDuration } from "@utils/text"; -import { find, findByCode, findByPropsLazy, findLazy } from "@webpack"; -import { moment, Parser, SnowflakeUtils, Text, Timestamp, Tooltip } from "@webpack/common"; +import { find, findByCode, findByPropsLazy } from "@webpack"; +import { FluxDispatcher, GuildMemberStore, GuildStore, moment, Parser, SnowflakeUtils, Text, Timestamp, Tooltip } from "@webpack/common"; import { Channel } from "discord-types/general"; -enum SortOrderTypesTyping { +enum SortOrderTypes { LATEST_ACTIVITY = 0, CREATION_DATE = 1 } -enum ForumLayoutTypesTyping { +enum ForumLayoutTypes { DEFAULT = 0, LIST = 1, GRID = 2 @@ -50,18 +49,31 @@ interface Tag { interface ExtendedChannel extends Channel { defaultThreadRateLimitPerUser?: number; - defaultSortOrder?: SortOrderTypesTyping | null; - defaultForumLayout?: ForumLayoutTypesTyping; + defaultSortOrder?: SortOrderTypes | null; + defaultForumLayout?: ForumLayoutTypes; defaultReactionEmoji?: DefaultReaction | null; availableTags?: Array<Tag>; } -const ChatClasses = findByPropsLazy("chat", "chatContent"); -const TagClasses = findLazy(m => typeof m.tags === "string" && Object.entries(m).length === 1); // Object exported with a single key called tags -const ChannelTypes = findByPropsLazy("GUILD_TEXT", "GUILD_FORUM"); -const SortOrderTypes = findLazy(m => typeof m.LATEST_ACTIVITY === "number"); -const ForumLayoutTypes = findLazy(m => typeof m.LIST === "number"); -const ChannelFlags = findLazy(m => typeof m.REQUIRE_TAG === "number"); +enum ChannelTypes { + GUILD_TEXT = 0, + GUILD_VOICE = 2, + GUILD_ANNOUNCEMENT = 5, + GUILD_STAGE_VOICE = 13, + GUILD_FORUM = 15 +} + +enum VideoQualityModes { + AUTO = 1, + FULL = 2 +} + +enum ChannelFlags { + PINNED = 1 << 1, + REQUIRE_TAG = 1 << 4 +} + +const ChatScrollClasses = findByPropsLazy("auto", "content", "scrollerBase"); const TagComponent = LazyComponent(() => find(m => { if (typeof m !== "function") return false; @@ -70,23 +82,32 @@ const TagComponent = LazyComponent(() => find(m => { return code.includes(".Messages.FORUM_TAG_A11Y_FILTER_BY_TAG") && !code.includes("increasedActivityPill"); })); const EmojiComponent = LazyComponent(() => findByCode('.jumboable?"jumbo":"default"')); +// The component for the beggining of a channel, but we patched it so it only returns the allowed users and roles components for hidden channels +const ChannelBeginHeader = LazyComponent(() => findByCode(".Messages.ROLE_REQUIRED_SINGLE_USER_MESSAGE")); -const ChannelTypesToChannelNames = proxyLazy(() => ({ +const ChannelTypesToChannelNames = { [ChannelTypes.GUILD_TEXT]: "text", [ChannelTypes.GUILD_ANNOUNCEMENT]: "announcement", - [ChannelTypes.GUILD_FORUM]: "forum" -})); + [ChannelTypes.GUILD_FORUM]: "forum", + [ChannelTypes.GUILD_VOICE]: "voice", + [ChannelTypes.GUILD_STAGE_VOICE]: "stage" +}; -const SortOrderTypesToNames = proxyLazy(() => ({ +const SortOrderTypesToNames = { [SortOrderTypes.LATEST_ACTIVITY]: "Latest activity", [SortOrderTypes.CREATION_DATE]: "Creation date" -})); +}; -const ForumLayoutTypesToNames = proxyLazy(() => ({ +const ForumLayoutTypesToNames = { [ForumLayoutTypes.DEFAULT]: "Not set", [ForumLayoutTypes.LIST]: "List view", [ForumLayoutTypes.GRID]: "Gallery view" -})); +}; + +const VideoQualityModesToNames = { + [VideoQualityModes.AUTO]: "Automatic", + [VideoQualityModes.FULL]: "720p" +}; // Icon from the modal when clicking a message link you don't have access to view const HiddenChannelLogo = "/assets/433e3ec4319a9d11b0cbe39342614982.svg"; @@ -104,97 +125,137 @@ function HiddenChannelLockScreen({ channel }: { channel: ExtendedChannel; }) { rateLimitPerUser, defaultThreadRateLimitPerUser, defaultSortOrder, - defaultReactionEmoji + defaultReactionEmoji, + bitrate, + rtcRegion, + videoQualityMode, + permissionOverwrites } = channel; - return ( - <div className={ChatClasses.chat + " " + "shc-lock-screen-container"}> - <img className="shc-lock-screen-logo" src={HiddenChannelLogo} /> - - <div className="shc-lock-screen-heading-container"> - <Text variant="heading-xxl/bold">This is a hidden {ChannelTypesToChannelNames[type]} channel.</Text> - {channel.isNSFW() && - <Tooltip text="NSFW"> - {({ onMouseLeave, onMouseEnter }) => ( - <svg - onMouseLeave={onMouseLeave} - onMouseEnter={onMouseEnter} - className="shc-lock-screen-heading-nsfw-icon" - width="32" - height="32" - viewBox="0 0 48 48" - aria-hidden={true} - role="img" - > - <path d="M.7 43.05 24 2.85l23.3 40.2Zm23.55-6.25q.75 0 1.275-.525.525-.525.525-1.275 0-.75-.525-1.3t-1.275-.55q-.8 0-1.325.55-.525.55-.525 1.3t.55 1.275q.55.525 1.3.525Zm-1.85-6.1h3.65V19.4H22.4Z" /> - </svg> - )} - </Tooltip> - } - </div> + const membersToFetch: Array<string> = []; - <Text variant="text-lg/normal"> - You can not see the {channel.isForumChannel() ? "posts" : "messages"} of this channel. - {channel.isForumChannel() && topic && topic.length > 0 && "However you may see its guidelines:"} - </Text > + const guildOwnerId = GuildStore.getGuild(channel.guild_id).ownerId; + if (!GuildMemberStore.getMember(channel.guild_id, guildOwnerId)) membersToFetch.push(guildOwnerId); - {channel.isForumChannel() && topic && topic.length > 0 && ( - <div className="shc-lock-screen-topic-container"> - {Parser.parseTopic(topic, false, { channelId })} - </div> - )} - - {lastMessageId && - <Text variant="text-md/normal"> - Last {channel.isForumChannel() ? "post" : "message"} created: - <Timestamp timestamp={moment(SnowflakeUtils.extractTimestamp(lastMessageId))} /> - </Text> - } - - {lastPinTimestamp && - <Text variant="text-md/normal">Last message pin: <Timestamp timestamp={moment(lastPinTimestamp)} /></Text> - } - {(rateLimitPerUser ?? 0) > 0 && - <Text variant="text-md/normal">Slowmode: {formatDuration(rateLimitPerUser! * 1000)}</Text> - } - {(defaultThreadRateLimitPerUser ?? 0) > 0 && - <Text variant="text-md/normal"> - Default thread slowmode: {formatDuration(defaultThreadRateLimitPerUser! * 1000)} - </Text> - } - {(defaultAutoArchiveDuration ?? 0) > 0 && - <Text variant="text-md/normal"> - Default inactivity duration before archiving {channel.isForumChannel() ? "posts" : "threads"}: - {formatDuration(defaultAutoArchiveDuration! * 1000 * 60)} - </Text> - } - {defaultForumLayout != null && - <Text variant="text-md/normal">Default layout: {ForumLayoutTypesToNames[defaultForumLayout]}</Text> - } - {defaultSortOrder != null && - <Text variant="text-md/normal">Default sort order: {SortOrderTypesToNames[defaultSortOrder]}</Text> - } - {defaultReactionEmoji != null && - <div className="shc-lock-screen-default-emoji-container"> - <Text variant="text-md/normal">Default reaction emoji:</Text> - <EmojiComponent node={{ - type: defaultReactionEmoji.emojiName ? "emoji" : "customEmoji", - name: defaultReactionEmoji.emojiName ?? "", - emojiId: defaultReactionEmoji.emojiId - }} /> + Object.values(permissionOverwrites).forEach(({ type, id: userId }) => { + if (type === 1) { + if (!GuildMemberStore.getMember(channel.guild_id, userId)) membersToFetch.push(userId); + } + }); + + if (membersToFetch.length > 0) { + FluxDispatcher.dispatch({ + type: "GUILD_MEMBERS_REQUEST", + guildIds: [channel.guild_id], + userIds: membersToFetch + }); + } + + return ( + <div className={ChatScrollClasses.auto + " " + "shc-lock-screen-outer-container"}> + <div className="shc-lock-screen-container"> + <img className="shc-lock-screen-logo" src={HiddenChannelLogo} /> + + <div className="shc-lock-screen-heading-container"> + <Text variant="heading-xxl/bold">This is a hidden {ChannelTypesToChannelNames[type]} channel.</Text> + {channel.isNSFW() && + <Tooltip text="NSFW"> + {({ onMouseLeave, onMouseEnter }) => ( + <svg + onMouseLeave={onMouseLeave} + onMouseEnter={onMouseEnter} + className="shc-lock-screen-heading-nsfw-icon" + width="32" + height="32" + viewBox="0 0 48 48" + aria-hidden={true} + role="img" + > + <path d="M.7 43.05 24 2.85l23.3 40.2Zm23.55-6.25q.75 0 1.275-.525.525-.525.525-1.275 0-.75-.525-1.3t-1.275-.55q-.8 0-1.325.55-.525.55-.525 1.3t.55 1.275q.55.525 1.3.525Zm-1.85-6.1h3.65V19.4H22.4Z" /> + </svg> + )} + </Tooltip> + } </div> - } - {channel.hasFlag(ChannelFlags.REQUIRE_TAG) && - <Text variant="text-md/normal">Posts on this forum require a tag to be set.</Text> - } - {availableTags && availableTags.length > 0 && - <div className="shc-lock-screen-tags-container"> - <Text variant="text-lg/bold">Available tags:</Text> - <div className={TagClasses.tags}> - {availableTags.map(tag => <TagComponent tag={tag} />)} + + {(!channel.isGuildVoice() && !channel.isGuildStageVoice()) && ( + <Text variant="text-lg/normal"> + You can not see the {channel.isForumChannel() ? "posts" : "messages"} of this channel. + {channel.isForumChannel() && topic && topic.length > 0 && "However you may see its guidelines:"} + </Text > + )} + + {channel.isForumChannel() && topic && topic.length > 0 && ( + <div className="shc-lock-screen-topic-container"> + {Parser.parseTopic(topic, false, { channelId })} </div> + )} + + {lastMessageId && + <Text variant="text-md/normal"> + Last {channel.isForumChannel() ? "post" : "message"} created: + <Timestamp timestamp={moment(SnowflakeUtils.extractTimestamp(lastMessageId))} /> + </Text> + } + + {lastPinTimestamp && + <Text variant="text-md/normal">Last message pin: <Timestamp timestamp={moment(lastPinTimestamp)} /></Text> + } + {(rateLimitPerUser ?? 0) > 0 && + <Text variant="text-md/normal">Slowmode: {formatDuration(rateLimitPerUser!, "seconds")}</Text> + } + {(defaultThreadRateLimitPerUser ?? 0) > 0 && + <Text variant="text-md/normal"> + Default thread slowmode: {formatDuration(defaultThreadRateLimitPerUser!, "seconds")} + </Text> + } + {((channel.isGuildVoice() || channel.isGuildStageVoice()) && bitrate != null) && + <Text variant="text-md/normal">Bitrate: {bitrate} bits</Text> + } + {rtcRegion !== undefined && + <Text variant="text-md/normal">Region: {rtcRegion ?? "Automatic"}</Text> + } + {(channel.isGuildVoice() || channel.isGuildStageVoice()) && + <Text variant="text-md/normal">Video quality mode: {VideoQualityModesToNames[videoQualityMode ?? VideoQualityModes.AUTO]}</Text> + } + {(defaultAutoArchiveDuration ?? 0) > 0 && + <Text variant="text-md/normal"> + Default inactivity duration before archiving {channel.isForumChannel() ? "posts" : "threads"}: + {" " + formatDuration(defaultAutoArchiveDuration!, "minutes")} + </Text> + } + {defaultForumLayout != null && + <Text variant="text-md/normal">Default layout: {ForumLayoutTypesToNames[defaultForumLayout]}</Text> + } + {defaultSortOrder != null && + <Text variant="text-md/normal">Default sort order: {SortOrderTypesToNames[defaultSortOrder]}</Text> + } + {defaultReactionEmoji != null && + <div className="shc-lock-screen-default-emoji-container"> + <Text variant="text-md/normal">Default reaction emoji:</Text> + <EmojiComponent node={{ + type: defaultReactionEmoji.emojiName ? "emoji" : "customEmoji", + name: defaultReactionEmoji.emojiName ?? "", + emojiId: defaultReactionEmoji.emojiId + }} /> + </div> + } + {channel.hasFlag(ChannelFlags.REQUIRE_TAG) && + <Text variant="text-md/normal">Posts on this forum require a tag to be set.</Text> + } + {availableTags && availableTags.length > 0 && + <div className="shc-lock-screen-tags-container"> + <Text variant="text-lg/bold">Available tags:</Text> + <div className="shc-lock-screen-tags"> + {availableTags.map(tag => <TagComponent tag={tag} />)} + </div> + </div> + } + <div className="shc-lock-screen-allowed-users-and-roles-container"> + <Text variant="text-lg/bold">Allowed users and roles:</Text> + <ChannelBeginHeader channel={channel} /> </div> - } + </div> </div> ); } diff --git a/src/plugins/showHiddenChannels/index.tsx b/src/plugins/showHiddenChannels/index.tsx index 9a448d5..a4c7dec 100644 --- a/src/plugins/showHiddenChannels/index.tsx +++ b/src/plugins/showHiddenChannels/index.tsx @@ -22,14 +22,15 @@ import { definePluginSettings } from "@api/settings"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; -import { findByPropsLazy, findLazy } from "@webpack"; +import { findByPropsLazy } from "@webpack"; import { ChannelStore, PermissionStore, Tooltip } from "@webpack/common"; import { Channel } from "discord-types/general"; import HiddenChannelLockScreen from "./components/HiddenChannelLockScreen"; const ChannelListClasses = findByPropsLazy("channelName", "subtitle", "modeMuted", "iconContainer"); -const Permissions = findLazy(m => typeof m.VIEW_CHANNEL === "bigint"); + +const VIEW_CHANNEL = 1n << 10n; enum ShowMode { LockIcon, @@ -90,13 +91,28 @@ export default definePlugin({ ] }, { - find: "VoiceChannel.renderPopout: There must always be something to render", + find: "VoiceChannel, transitionTo: Channel does not have a guildId", replacement: [ - // Do nothing when trying to join a voice channel if the channel is hidden { - match: /(?<=handleClick=function\(\){)(?=.{1,80}(?<this>\i)\.handleVoiceConnect\(\))/, - replace: "if($self.isHiddenChannel($<this>.props.channel))return;" + // Do not show confirmation to join a voice channel when already connected to another if clicking on a hidden voice channel + match: /(?<=getCurrentClientVoiceChannelId\(\i\.guild_id\);if\()(?=.+?\((?<channel>\i)\))/, + replace: "!$self.isHiddenChannel($<channel>)&&" }, + { + // Make Discord think we are connected to a voice channel so it shows us inside it + match: /(?=\|\|\i\.default\.selectVoiceChannel\((?<channel>\i)\.id\))/, + replace: "||$self.isHiddenChannel($<channel>)" + }, + { + // Make Discord think we are connected to a voice channel so it shows us inside it + match: /(?<=\|\|\i\.default\.selectVoiceChannel\((?<channel>\i)\.id\);!__OVERLAY__&&\()/, + replace: "$self.isHiddenChannel($<channel>)||" + } + ] + }, + { + find: "VoiceChannel.renderPopout: There must always be something to render", + replacement: [ // Render null instead of the buttons if the channel is hidden ...[ "renderEditButton", @@ -120,11 +136,11 @@ export default definePlugin({ { find: ".UNREAD_HIGHLIGHT", predicate: () => settings.store.hideUnreads === true, - replacement: [{ + replacement: { // Hide unreads match: /(?<=\i\.connected,\i=)(?=(?<props>\i)\.unread)/, replace: "$self.isHiddenChannel($<props>.channel)?false:" - }] + } }, { find: ".UNREAD_HIGHLIGHT", @@ -160,7 +176,7 @@ export default definePlugin({ // Hide New unreads box for hidden channels find: '.displayName="ChannelListUnreadsStore"', replacement: { - match: /(?<=return null!=(?<channel>\i))(?=.{1,130}hasRelevantUnread\(\i\))/, + match: /(?<=return null!=(?<channel>\i))(?=.{1,130}hasRelevantUnread\(\i\))/g, // Global because Discord has multiple methods like that in the same module replace: "&&!$self.isHiddenChannel($<channel>)" } }, @@ -191,7 +207,7 @@ export default definePlugin({ { match: /(?<=renderChat=function\(\){)/, replace: "if($self.isHiddenChannel(this.props.channel))return $self.HiddenChannelLockScreen(this.props.channel);" - }, + } ] }, // Avoid trying to fetch messages from hidden channels @@ -226,6 +242,86 @@ export default definePlugin({ match: /(?<=\i:\(\)=>\i)(?=}.+?(?<component>\i)=function.{1,20}node,\i=\i.isInteracting)/, replace: ",hc1:()=>$<component>" // Blame Ven length check for the small name :pensive_cry: } + }, + { + find: ".Messages.ROLE_REQUIRED_SINGLE_USER_MESSAGE", + replacement: [ + { + // Export the channel beggining header + match: /(?<=\i:\(\)=>\i)(?=}.+?function (?<component>\i).{1,600}computePermissionsForRoles)/, + replace: ",hc2:()=>$<component>" + }, + { + // Patch the header to only return allowed users and roles if it's a hidden channel (Like when it's used on the HiddenChannelLockScreen) + match: /(?<=MANAGE_ROLES.{1,60}return)(?=\(.+?(?<component>\(0,\i\.jsxs\)\("div",{className:\i\(\)\.members.+?guildId:(?<channel>\i)\.guild_id.+?roleColor.+?]}\)))/, + replace: " $self.isHiddenChannel($<channel>)?$<component>:" + } + ] + }, + { + find: ".Messages.SHOW_CHAT", + replacement: [ + { + // Remove the divider and the open chat button for the HiddenChannelLockScreen + match: /(?<=function \i\((?<props>\i)\).{1,1800}"more-options-popout"\)\);if\()/, + replace: "(!$self.isHiddenChannel($<props>.channel)||$<props>.inCall)&&" + }, + { + // Render our HiddenChannelLockScreen component instead of the main voice channel component + match: /(?<=renderContent=function.{1,1700}children:)/, + replace: "!this.props.inCall&&$self.isHiddenChannel(this.props.channel)?$self.HiddenChannelLockScreen(this.props.channel):" + }, + { + // Disable gradients for the HiddenChannelLockScreen of voice channels + match: /(?<=renderContent=function.{1,1600}disableGradients:)/, + replace: "!this.props.inCall&&$self.isHiddenChannel(this.props.channel)||" + }, + { + // Disable useless components for the HiddenChannelLockScreen of voice channels + match: /(?<=renderContent=function.{1,800}render(?!Header).{0,30}:)(?!void)/g, + replace: "!this.props.inCall&&$self.isHiddenChannel(this.props.channel)?null:" + } + ] + }, + { + find: "Guild voice channel without guild id.", + replacement: [ + { + // Render our HiddenChannelLockScreen component instead of the main stage channel component + match: /(?<=(?<channel>\i)\.getGuildId\(\).{1,30}Guild voice channel without guild id\..{1,1400}children:)(?=.{1,20}}\)}function)/, + replace: "$self.isHiddenChannel($<channel>)?$self.HiddenChannelLockScreen($<channel>):" + }, + { + // Disable useless components for the HiddenChannelLockScreen of stage channels + match: /(?<=(?<channel>\i)\.getGuildId\(\).{1,30}Guild voice channel without guild id\..{1,1000}render(?!Header).{0,30}:)/g, + replace: "$self.isHiddenChannel($<channel>)?null:" + }, + // Prevent Discord from replacing our route if we aren't connected to the stage channel + { + match: /(?<=if\()(?=!\i&&!\i&&!\i.{1,80}(?<channel>\i)\.getGuildId\(\).{1,50}Guild voice channel without guild id\.)/, + replace: "!$self.isHiddenChannel($<channel>)&&" + }, + { + // Disable gradients for the HiddenChannelLockScreen of stage channels + match: /(?<=(?<channel>\i)\.getGuildId\(\).{1,30}Guild voice channel without guild id\..{1,600}disableGradients:)/, + replace: "$self.isHiddenChannel($<channel>)||" + }, + { + // Disable strange styles applied to the header for the HiddenChannelLockScreen of stage channels + match: /(?<=(?<channel>\i)\.getGuildId\(\).{1,30}Guild voice channel without guild id\..{1,600}style:)/, + replace: "$self.isHiddenChannel($<channel>)?undefined:" + }, + { + // Remove the divider and amount of users in stage channel components for the HiddenChannelLockScreen + match: /\(0,\i\.jsx\)\(\i\.\i\.Divider.+?}\)]}\)(?=.+?:(?<channel>\i)\.guild_id)/, + replace: "$self.isHiddenChannel($<channel>)?null:($&)" + }, + { + // Remove the open chat button for the HiddenChannelLockScreen + match: /(?<=null,)(?=.{1,120}channelId:(?<channel>\i)\.id,.+?toggleRequestToSpeakSidebar:\i,iconClassName:\i\(\)\.buttonIcon)/, + replace: "!$self.isHiddenChannel($<channel>)&&" + } + ], } ], @@ -235,7 +331,7 @@ export default definePlugin({ if (channel.channelId) channel = ChannelStore.getChannel(channel.channelId); if (!channel || channel.isDM() || channel.isGroupDM() || channel.isMultiUserDM()) return false; - return !PermissionStore.can(Permissions.VIEW_CHANNEL, channel); + return !PermissionStore.can(VIEW_CHANNEL, channel); }, HiddenChannelLockScreen: (channel: any) => <HiddenChannelLockScreen channel={channel} />, diff --git a/src/plugins/showHiddenChannels/style.css b/src/plugins/showHiddenChannels/style.css index 73957ef..0f85b50 100644 --- a/src/plugins/showHiddenChannels/style.css +++ b/src/plugins/showHiddenChannels/style.css @@ -1,9 +1,18 @@ +.shc-lock-screen-outer-container { + background-color: var(--background-primary); + overflow: hidden scroll; + flex: 1 1 auto; + height: 100%; + width: 100%; +} + .shc-lock-screen-container { display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; + min-height: 100%; } .shc-lock-screen-container > * { @@ -34,14 +43,14 @@ color: var(--text-normal); background-color: var(--background-secondary); border-radius: 5px; - padding: 5px; + padding: 10px; max-width: 70vw; } .shc-lock-screen-tags-container { background-color: var(--background-secondary); border-radius: 5px; - padding: 5px; + padding: 10px; max-width: 70vw; } @@ -49,8 +58,12 @@ margin: inherit; } -.shc-lock-screen-tags-container > [class^="tags"] { +.shc-lock-screen-tags { + display: flex; + align-items: center; + justify-content: center; flex-wrap: wrap; + gap: 8px; } .shc-evenodd-fill-current-color { @@ -76,3 +89,18 @@ padding: 3px 4px; margin-left: 5px; } + +.shc-lock-screen-allowed-users-and-roles-container { + display: flex; + flex-direction: column; + align-items: center; + background-color: var(--background-secondary); + border-radius: 5px; + padding: 10px; + max-width: 70vw; +} + +.shc-lock-screen-allowed-users-and-roles-container > [class^="members"] { + margin-left: 10px; + flex-wrap: wrap; +} diff --git a/src/plugins/typingTweaks.tsx b/src/plugins/typingTweaks.tsx index 96c04b9..f03779c 100644 --- a/src/plugins/typingTweaks.tsx +++ b/src/plugins/typingTweaks.tsx @@ -21,7 +21,7 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; import { findByCodeLazy } from "@webpack"; -import { GuildMemberStore, React } from "@webpack/common"; +import { GuildMemberStore, React, RelationshipStore } from "@webpack/common"; const Avatar = findByCodeLazy(".Positions.TOP,spacing:"); @@ -76,8 +76,8 @@ export default definePlugin({ { find: ",\"SEVERAL_USERS_TYPING\",\"", replacement: { - match: /(\i)\((\i),("SEVERAL_USERS_TYPING"),".+?"\)/, - replace: "$1($2,$3,\"**!!{a}!!**, **!!{b}!!**, and {c} others are typing...\")" + match: /(?<="SEVERAL_USERS_TYPING",)".+?"/, + replace: '"**!!{a}!!**, **!!{b}!!**, and {c} others are typing..."' }, predicate: () => settings.store.alternativeFormatting }, @@ -98,7 +98,7 @@ export default definePlugin({ let element = 0; - return children.map(c => c.type === "strong" ? <this.TypingUser {...props} user={users[element++]}/> : c); + return children.map(c => c.type === "strong" ? <this.TypingUser {...props} user={users[element++]} /> : c); }, TypingUser: ErrorBoundary.wrap(({ user, guildId }) => { @@ -111,9 +111,9 @@ export default definePlugin({ {settings.store.showAvatars && <div style={{ marginTop: "4px" }}> <Avatar size={Avatar.Sizes.SIZE_16} - src={user.getAvatarURL(guildId, 128)}/> + src={user.getAvatarURL(guildId, 128)} /> </div>} - {GuildMemberStore.getNick(guildId!, user.id) || user.username} + {GuildMemberStore.getNick(guildId!, user.id) || !guildId && RelationshipStore.getNickname(user.id) || user.username} </strong>; }, { noop: true }) }); diff --git a/src/utils/text.ts b/src/utils/text.ts index fae3343..115b3e2 100644 --- a/src/utils/text.ts +++ b/src/utils/text.ts @@ -37,25 +37,58 @@ export const wordsToPascal = (words: string[]) => export const wordsToTitle = (words: string[]) => words.map(w => w[0].toUpperCase() + w.slice(1)).join(" "); +const units = ["years", "months", "weeks", "days", "hours", "minutes", "seconds"] as const; +type Units = typeof units[number]; + +function getUnitStr(unit: Units, isOne: boolean, short: boolean) { + if (short === false) return isOne ? unit.slice(0, -1) : unit; + + return unit[0]; +} + /** - * Forms milliseconds into a human readable string link "1 day, 2 hours, 3 minutes and 4 seconds" - * @param ms Milliseconds + * Forms time into a human readable string link "1 day, 2 hours, 3 minutes and 4 seconds" + * @param time The time on the specified unit + * @param unit The unit the time is on * @param short Whether to use short units like "d" instead of "days" */ -export function formatDuration(ms: number, short: boolean = false) { - const dur = moment.duration(ms); - return (["years", "months", "weeks", "days", "hours", "minutes", "seconds"] as const).reduce((res, unit) => { - const x = dur[unit](); - if (x > 0 || res.length) { - if (res.length) - res += unit === "seconds" ? " and " : ", "; - - const unitStr = short - ? unit[0] - : x === 1 ? unit.slice(0, -1) : unit; - - res += `${x} ${unitStr}`; +export function formatDuration(time: number, unit: Units, short: boolean = false) { + const dur = moment.duration(time, unit); + + let unitsAmounts = units.map(unit => ({ amount: dur[unit](), unit })); + + let amountsToBeRemoved = 0; + + outer: + for (let i = 0; i < unitsAmounts.length; i++) { + if (unitsAmounts[i].amount === 0 || !(i + 1 < unitsAmounts.length)) continue; + for (let v = i + 1; v < unitsAmounts.length; v++) { + if (unitsAmounts[v].amount !== 0) continue outer; } - return res; - }, "").replace(/((,|and) \b0 \w+)+$/, "") || "now"; + + amountsToBeRemoved = unitsAmounts.length - (i + 1); + } + unitsAmounts = amountsToBeRemoved === 0 ? unitsAmounts : unitsAmounts.slice(0, -amountsToBeRemoved); + + const daysAmountIndex = unitsAmounts.findIndex(({ unit }) => unit === "days"); + if (daysAmountIndex !== -1) { + const daysAmount = unitsAmounts[daysAmountIndex]; + + const daysMod = daysAmount.amount % 7; + if (daysMod === 0) unitsAmounts.splice(daysAmountIndex, 1); + else daysAmount.amount = daysMod; + } + + let res: string = ""; + while (unitsAmounts.length) { + const { amount, unit } = unitsAmounts.shift()!; + + if (res.length) res += unitsAmounts.length ? ", " : " and "; + + if (amount > 0 || res.length) { + res += `${amount} ${getUnitStr(unit, amount === 1, short)}`; + } + } + + return res.length ? res : `0 ${getUnitStr(unit, false, short)}`; } diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts index 7b318b2..19ca951 100644 --- a/src/webpack/patchWebpack.ts +++ b/src/webpack/patchWebpack.ts @@ -21,6 +21,7 @@ import Logger from "@utils/Logger"; import { canonicalizeReplacement } from "@utils/patches"; import { PatchReplacement } from "@utils/types"; +import { traceFunction } from "../debug/Tracer"; import { _initWebpack } from "."; let webpackChunk: any[]; @@ -132,6 +133,7 @@ function patchPush() { for (let i = 0; i < patches.length; i++) { const patch = patches[i]; + const executePatch = traceFunction(`patch by ${patch.plugin}`, (match: string | RegExp, replace: string) => code.replace(match, replace)); if (patch.predicate && !patch.predicate()) continue; if (code.includes(patch.find)) { @@ -146,7 +148,7 @@ function patchPush() { canonicalizeReplacement(replacement, patch.plugin); try { - const newCode = code.replace(replacement.match, replacement.replace as string); + const newCode = executePatch(replacement.match, replacement.replace as string); if (newCode === code && !patch.noWarn) { logger.warn(`Patch by ${patch.plugin} had no effect (Module id is ${id}): ${replacement.match}`); if (IS_DEV) { @@ -187,7 +189,7 @@ function patchPush() { } logger.errorCustomFmt(...Logger.makeTitle("white", "Before"), context); - logger.errorCustomFmt(...Logger.makeTitle("white", "After"), context); + logger.errorCustomFmt(...Logger.makeTitle("white", "After"), patchedContext); const [titleFmt, ...titleElements] = Logger.makeTitle("white", "Diff"); logger.errorCustomFmt(titleFmt + fmt, ...titleElements, ...elements); } |