diff --git a/TypingUsersAvatars/TypingUsersAvatars.plugin.js b/TypingUsersAvatars/TypingUsersAvatars.plugin.js index 9d8b7ba..1bfbf23 100644 --- a/TypingUsersAvatars/TypingUsersAvatars.plugin.js +++ b/TypingUsersAvatars/TypingUsersAvatars.plugin.js @@ -3,7 +3,7 @@ * @source https://github.com/QWERTxD/BetterDiscordPlugins/blob/main/TypingUsersAvatars/TypingUsersAvatars.plugin.js * @updateUrl https://raw.githubusercontent.com/QWERTxD/BetterDiscordPlugins/main/TypingUsersAvatars/TypingUsersAvatars.plugin.js * @website https://github.com/QWERTxD/BetterDiscordPlugins/tree/main/TypingUsersAvatars -* @version 1.0.5 +* @version 1.0.6 * @description Shows avatars of typing users. */ @@ -19,16 +19,15 @@ const config = { name: 'QWERT' } ], - version: '1.0.5', + version: '1.0.6', description: 'Shows avatars of typing users.', - github_raw: "https://raw.githubusercontent.com/QWERTxD/BetterDiscordPlugins/main/TypingUsersAvatars/TypingUsersAvatars.plugin.js", }, changelog: [ { - title: 'What\'s new?', - type: 'added', + title: 'Fixed', + type: 'fixed', items: [ - 'Added option to show guild avatars of typing users', + 'The Plugin works again.' ] } ], @@ -73,38 +72,21 @@ module.exports = !global.ZeresPluginLibrary ? class { stop() { } } : (([Plugin, Library]) => { - const { DiscordModules, WebpackModules, PluginUtilities, Patcher, ReactComponents, Popouts, Utilities, DiscordSelectors } = Library; - const { React, UserStore, RelationshipStore, UserStatusStore, Strings } = DiscordModules; - const AssetUtils = WebpackModules.getByProps("getUserBannerURL"); - const Avatar = WebpackModules.getByProps('AnimatedAvatar'); - const VoiceUserSummary = WebpackModules.findByDisplayName("VoiceUserSummaryItem") - - class AvatarComponent extends React.Component { - render() { - const { user, status, guildId } = this.props; - const guildAvatar = AssetUtils.getGuildMemberAvatarURL({ guildId: guildId, userId: user.id, avatar: user.guildMemberAvatars[guildId] }); - return React.createElement(Avatar.default, { - src: user?.guildMemberAvatars[guildId] ? guildAvatar : user.getAvatarURL(), - status: status, - size: Avatar.Sizes.SIZE_16, - onClick() { - Popouts.showUserPopout(document.getElementById(`typing-user-${user.id}`), user) - } - }) - } - } + const { DiscordModules, PluginUtilities, Popouts } = Library; + const { UserStore, RelationshipStore, UserStatusStore, UserTypingStore, SelectedChannelStore, ChannelStore } = DiscordModules; + + const avatarSize = 20; class plugin extends Plugin { constructor() { super(); + this.element = null; this.getSettingsPanel = () => { return this.buildSettingsPanel().getElement(); }; } onStart() { - Utilities.suppressErrors(this.patch.bind(this))(); - PluginUtilities.addStyle('TypingUsersAvatars', ` .typing-2J1mQU .text-3S7XCz { margin: 0; @@ -128,66 +110,131 @@ module.exports = !global.ZeresPluginLibrary ? class { .several-users .avatarSize-1KpZ5E { margin: 0; } + + .user-overflow-count { + height: ${avatarSize}px; + border-radius: ${avatarSize/2}px; + font-size: 12px; + background-color: var(--channeltextarea-background); + padding: 0 7px 0 7px; + display: flex; + } + + .user-overflow-count > strong { + margin-top: -1px; + } + + .typing-user { + width: ${avatarSize}px; + height: ${avatarSize}px; + border-radius: 50%; + background-size: contain; + pointer-events: auto !important; + } + + .typing-user:hover { + cursor: pointer; + } `) + + UserTypingStore.addChangeListener(this.inject.bind(this)); } onStop() { - Patcher.unpatchAll(); PluginUtilities.removeStyle('TypingUsersAvatars'); + UserTypingStore._changeCallbacks.listeners.clear() } - filter(users) { - return Object.keys(users).filter((user) => { - return user != UserStore.getCurrentUser().id && !RelationshipStore.isBlocked(user); - }) + statusToColor(status) { + switch (status) { + case 'online': + return '#3ba55c'; + case 'idle': + return '#faa61a'; + case 'dnd': + return '#ed4245'; + case 'offline': + return '#747f8d'; + default: + return '#00000000'; + } } - /* code highly inspired by https://github.com/rauenzi/BetterDiscordAddons/blob/master/Plugins/BetterRoleColors/BetterRoleColors.plugin.js */ - async patch() { - const TypingUsers = await ReactComponents.getComponentByName('TypingUsers', DiscordSelectors.Typing.typing); - Patcher.after(TypingUsers.component.prototype, 'render', (thisObject, [props], ret) => { - const typingUsers = this.filter({ ...thisObject.props.typingUsers }); - const guildId = thisObject.props?.guildId; - - for (let u = 0; u < typingUsers.length; u++) { - const user = UserStore.getUser(typingUsers[u]); - const status = this.settings.showStatus ? UserStatusStore.getStatus(user.id) : null; - - if (ret.props.children[0].props.children[1]?.props.children !== Strings.Messages.SEVERAL_USERS_TYPING) { - const usersComponent = ret.props.children[0].props.children[1].props.children.filter(user => user.props); - - usersComponent[u].props.children.unshift(React.createElement("div", { - id: `typing-user-${user.id}`, - children: React.createElement(AvatarComponent, { user, status, guildId }) - })); - } else { - ret.props.children[0].props.children = [ - React.createElement(VoiceUserSummary, { - className: "several-users", - users: typingUsers.map(UserStore.getUser), - max: 3 - }), - ret.props.children[0].props.children - ] - } - } + avatarElement(user, masked, guildId) { + const status = this.settings.showStatus ? UserStatusStore.getStatus(user.id) : null; + const statusColor = this.statusToColor(status); + + const avatarURL = this.settings.showGuildAvatar ? user.getAvatarURL(guildId) : user.getAvatarURL(); + const avatar = document.createElement('div'); + avatar.id = `typing-user-${user.id}`; + avatar.className = 'typing-user mask-1FEkla'; + + // `showUserPopout` is broken right now. + // avatar.addEventListener('click', () => Popouts.showUserPopout(document.getElementById(`typing-user-${user.id}`), user), {align: "top"}); + + avatar.innerHTML = ` + + + + + `; + + return avatar; + } + + inject() { + if (!this.element) return; + + const guildId = ChannelStore.getChannel(SelectedChannelStore.getChannelId()).guild_id; - if (!ret) return; - const tree = ret.props.children; - if (!tree) return; + this.element.querySelector('#typing-users-avatars')?.remove(); + let avatars = document.createElement('div'); + avatars.className = 'wrapper-1VLyxH avatarStack-3vfSFa'; + avatars.id = 'typing-users-avatars'; - tree.map((child) => { - const children = child?.props?.children; - if (!children || typeof children !== 'object') return; + const users = Object.keys(UserTypingStore.getTypingUsers(SelectedChannelStore.getChannelId())) + .filter(user => user != UserStore.getCurrentUser().id && !RelationshipStore.isBlocked(user)) + .map((user) => UserStore.getUser(user)); + + if (users.length == 0) return; + + const severalThreshold = 2; + const severalUsers = users.length > severalThreshold; + for (let i = 0; i < (severalUsers ? severalThreshold : users.length - 1); i++) { + avatars.appendChild(this.avatarElement(users[i], true, guildId)); + } + if (severalUsers) { + const severalUsersElement = document.createElement('div'); + severalUsersElement.className = 'user-overflow-count'; + severalUsersElement.innerHTML = `+${users.length - severalThreshold}`; + avatars.appendChild(severalUsersElement); + } else { + avatars.appendChild(this.avatarElement(users[users.length - 1], false, guildId)); + } - child.props.style = { - display: 'flex', - alignItems: 'center' - }; - }) - }) + this.element.insertBefore(avatars, this.element.querySelector("span")); } + observer({addedNodes, removedNodes}) { + const dotsClass = 'typingDots-1Y8dki'; + for(const node of addedNodes) { + if (Node.TEXT_NODE == node.nodeType) continue; + Array.from(node.getElementsByClassName(dotsClass)).forEach((element) => { + this.element = element; + this.inject(); + }); + } + + for(const node of removedNodes) { + if (Node.TEXT_NODE == node.nodeType) continue; + Array.from(node.getElementsByClassName(dotsClass)).forEach((element) => { + this.element = null; + }); + } + } } return plugin;