Fix various bugs

- Show public servers on startup
- Update plugin tab on load/unload
- Finish switching searchGetters to searchExports
- Add disabled class to addon cards
- Fix/rebuild changelog modal
This commit is contained in:
Zack Rauen 2022-10-09 18:22:07 -04:00
parent a9220996b5
commit af1a26c333
6 changed files with 73 additions and 53 deletions

View File

@ -1,5 +1,5 @@
import Builtin from "../../structs/builtin";
import {DiscordModules, WebpackModules, Strings, DOMManager, React} from "modules";
import {DiscordModules, WebpackModules, Strings, DOMManager, React, ReactDOM} from "modules";
import PublicServersMenu from "../../ui/publicservers/menu";
import Globe from "../../ui/icons/globe";
@ -48,16 +48,17 @@ export default new class PublicServers extends Builtin {
if (!PrivateChannelList || !PrivateChannelList.Z) return this.warn("Could not find PrivateChannelList", PrivateChannelList);
const PrivateChannelButton = WebpackModules.getModule(m => m?.prototype?.render?.toString().includes("linkButton"), {searchExports: true});
if (!PrivateChannelButton) return this.warn("Could not find PrivateChannelButton", PrivateChannelButton);
this.after(PrivateChannelList, "Z", (_, __, returnValue) => {
const destination = returnValue?.props?.children?.props?.children;
if (!destination || !Array.isArray(destination)) return;
if (destination.find(b => b?.props?.children?.props?.id === "public-server-button")) return;
if (destination.find(b => b?.props?.children?.props?.id === "public-servers-button")) return; // If it exists, don't try to add again
destination.push(
React.createElement(ErrorBoundary, null,
React.createElement(PrivateChannelButton,
{
id: "public-server-button",
id: "public-servers-button",
onClick: () => this.openPublicServers(),
text: "Public Servers",
icon: () => React.createElement(Globe, {color: "currentColor"})
@ -66,6 +67,42 @@ export default new class PublicServers extends Builtin {
)
);
});
/**
* On being first enabled, we have no way of forceUpdating the list,
* so clone and modify an existing button and add it to the end
* of the button list.
*/
const header = document.querySelector(`[class*="privateChannelsHeaderContainer-"]`);
if (!header) return; // No known element
const oldButton = header.previousElementSibling;
if (!oldButton.className.includes("channel-")) return; // Not what we expected to be there
// Clone existing button and set click handler
const newButton = oldButton.cloneNode(true);
newButton.addEventListener("click", (event) => {
event.stopImmediatePropagation();
event.stopPropagation();
event.preventDefault();
this.openPublicServers();
});
// Remove existing route and id
const aSlot = newButton.querySelector("a");
aSlot.href = "";
aSlot.dataset.listItemId = "public-servers";
// Render our icon in the avatar slot
const avatarSlot = newButton.querySelector(`[class*="avatar-"]`);
avatarSlot.replaceChildren();
ReactDOM.render(React.createElement(Globe, {color: "currentColor"}), avatarSlot);
// Replace the existing name
const nameSlot = newButton.querySelector(`[class*="name-"]`);
nameSlot.textContent = "Public Servers";
// Insert before the header, end of the list
header.parentNode.insertBefore(newButton, header);
}
disabled() {

View File

@ -206,6 +206,7 @@ export default class AddonManager {
if (partialAddon) {
partialAddon.partial = true;
this.state[partialAddon.id] = false;
this.emit("loaded", partialAddon.id);
}
return e;
}
@ -215,6 +216,7 @@ export default class AddonManager {
if (error) {
this.state[addon.id] = false;
addon.partial = true;
this.emit("loaded", addon.id);
return error;
}

View File

@ -15,7 +15,7 @@ const MenuComponents = (() => {
};
let ContextMenuIndex = null;
const ContextMenuModule = WebpackModules.getModule((m, _, id) => Object.values(m).some(v => v?.FLEXIBLE) && (ContextMenuIndex = id), {searchGetters: false});
const ContextMenuModule = WebpackModules.getModule((m, _, id) => Object.values(m).some(v => v?.FLEXIBLE) && (ContextMenuIndex = id), {searchExports: false});
const rawMatches = WebpackModules.require.m[ContextMenuIndex].toString().matchAll(/if\(\w+\.type===\w+\.(\w+)\).+?type:"(.+?)"/g);
out.Menu = Object.values(ContextMenuModule).find(v => v.toString().includes(".isUsingKeyboardNavigation"));
@ -30,7 +30,7 @@ const MenuComponents = (() => {
const ContextMenuActions = (() => {
const out = {};
const ActionsModule = WebpackModules.getModule(m => Object.values(m).some(m => typeof m === "function" && m.toString().includes("CONTEXT_MENU_CLOSE")), {searchGetters: false});
const ActionsModule = WebpackModules.getModule(m => Object.values(m).some(m => typeof m === "function" && m.toString().includes("CONTEXT_MENU_CLOSE")), {searchExports: false});
for (const key of Object.keys(ActionsModule)) {
if (ActionsModule[key].toString().includes("CONTEXT_MENU_CLOSE")) {
@ -50,7 +50,7 @@ class MenuPatcher {
static initialize() {
const {module, key} = (() => {
const module = WebpackModules.getModule(m => Object.values(m).some(v => typeof v === "function" && v.toString().includes("CONTEXT_MENU_CLOSE")), {searchGetters: false});
const module = WebpackModules.getModule(m => Object.values(m).some(v => typeof v === "function" && v.toString().includes("CONTEXT_MENU_CLOSE")), {searchExports: false});
const key = Object.keys(module).find(key => module[key].length === 3);
return {module, key};

View File

@ -74,7 +74,7 @@ const Webpack = {
getModule(filter, options = {}) {
if (("first" in options) && typeof(options.first) !== "boolean") return Logger.error("BdApi.Webpack~getModule", "Unsupported type used for options.first", options.first, "boolean expected.");
if (("defaultExport" in options) && typeof(options.defaultExport) !== "boolean") return Logger.error("BdApi.Webpack~getModule", "Unsupported type used for options.defaultExport", options.defaultExport, "boolean expected.");
if (("searchGetters" in options) && typeof(options.searchGetters) !== "boolean") return Logger.error("BdApi.Webpack~getModule", "Unsupported type used for options.searchGetters", options.searchGetters, "boolean expected.");
if (("searchExports" in options) && typeof(options.searchExports) !== "boolean") return Logger.error("BdApi.Webpack~getModule", "Unsupported type used for options.searchExports", options.searchExports, "boolean expected.");
return WebpackModules.getModule(filter, options);
},
@ -103,7 +103,7 @@ const Webpack = {
waitForModule(filter, options = {}) {
if (("defaultExport" in options) && typeof(options.defaultExport) !== "boolean") return Logger.error("BdApi.Webpack~waitForModule", "Unsupported type used for options.defaultExport", options.defaultExport, "boolean expected.");
if (("signal" in options) && !(options.signal instanceof AbortSignal)) return Logger.error("BdApi.Webpack~waitForModule", "Unsupported type used for options.signal", options.signal, "AbortSignal expected.");
if (("searchGetters" in options) && typeof(options.searchGetters) !== "boolean") return Logger.error("BdApi.Webpack~getModule", "Unsupported type used for options.searchGetters", options.searchGetters, "boolean expected.");
if (("searchExports" in options) && typeof(options.searchExports) !== "boolean") return Logger.error("BdApi.Webpack~getModule", "Unsupported type used for options.searchExports", options.searchExports, "boolean expected.");
return WebpackModules.getLazy(filter, options);
},
};

View File

@ -1,6 +1,6 @@
import {Config} from "data";
import Logger from "common/logger";
import {WebpackModules, React, ReactDOM, Settings, Strings, DOMManager, DiscordModules} from "modules";
import {WebpackModules, React, ReactDOM, Settings, Strings, DOMManager, DiscordModules, DiscordClasses} from "modules";
import FormattableString from "../structs/string";
import AddonErrorModal from "./addonerrormodal";
import ErrorBoundary from "./errorboundary";
@ -212,26 +212,16 @@ export default class Modals {
}))));
}
static showChangelogModal(changelog) {
const md = [changelog.description];
for (const type of changelog.changes) {
md.push(`**${type.title}**`);
for (const entry of type.items) {
md.push(` - ${entry}`);
}
}
Modals.showConfirmationModal(`BetterDiscord v${Config.version}`, md, {cancelText: ""});
}
static BROKEN_showChangelogModal(options = {}) {
const ModalStack = WebpackModules.getByProps("push", "update", "pop", "popWithKey");
static showChangelogModal(options = {}) {
const OriginalModalClasses = WebpackModules.getByProps("hideOnFullscreen", "root");
const ChangelogModalClasses = WebpackModules.getModule(m => m.modal && m.maxModalWidth);
const ChangelogClasses = WebpackModules.getByProps("fixed", "improved");
const TextElement = WebpackModules.getByDisplayName("LegacyText");
const FlexChild = WebpackModules.getByProps("Child");
const Titles = WebpackModules.getByProps("Tags", "default");
const Changelog = WebpackModules.getModule(m => m.defaultProps && m.defaultProps.selectable == false);
const TextElement = this.TextElement;
const FlexChild = this.FlexElements;
const Titles = this.FormTitle;
const MarkdownParser = WebpackModules.getByProps("defaultRules", "parse");
if (!Changelog || !ModalStack || !ChangelogClasses || !TextElement || !FlexChild || !Titles || !MarkdownParser) return Logger.warn("Modals", "showChangelogModal missing modules");
if (!OriginalModalClasses || !ChangelogModalClasses || !ChangelogClasses || !TextElement || !FlexChild || !Titles || !MarkdownParser) return Logger.warn("Modals", "showChangelogModal missing modules");
const {image = "https://i.imgur.com/wuh5yMK.png", description = "", changes = [], title = "BetterDiscord", subtitle = `v${Config.version}`, footer} = options;
const ce = React.createElement;
@ -247,47 +237,38 @@ export default class Modals {
changelogItems.push(list);
}
const renderHeader = function() {
return ce(FlexChild.Child, {grow: 1, shrink: 1},
ce(Titles.default, {tag: Titles.Tags.H4}, title),
ce(TextElement, {size: TextElement.Sizes.SMALL, color: TextElement.Colors.STANDARD, className: ChangelogClasses.date}, subtitle)
return ce(FlexChild, {className: OriginalModalClasses.header, grow: 0, shrink: 0, direction: FlexChild.Direction.VERTICAL},
ce(Titles, {tag: Titles.Tags.H1, size: TextElement.Sizes.SIZE_20}, title),
ce(TextElement, {size: TextElement.Sizes.SIZE_12, color: TextElement.Colors.STANDARD, className: ChangelogClasses.date}, subtitle)
);
};
const renderFooter = () => {
const Anchor = WebpackModules.getModule(m => m.displayName == "Anchor");
const AnchorClasses = WebpackModules.getByProps("anchorUnderlineOnHover") || {anchor: "anchor-3Z-8Bb", anchorUnderlineOnHover: "anchorUnderlineOnHover-2ESHQB"};
const joinSupportServer = (click) => {
click.preventDefault();
click.stopPropagation();
ModalStack.pop();
DiscordModules.InviteActions.acceptInviteAndTransitionToInviteChannel("0Tmfo5ZbORCRqbAd");
};
const supportLink = Anchor ? ce(Anchor, {onClick: joinSupportServer}, "Join our Discord Server.") : ce("a", {className: `${AnchorClasses.anchor} ${AnchorClasses.anchorUnderlineOnHover}`, onClick: joinSupportServer}, "Join our Discord Server.");
const defaultFooter = ce(TextElement, {size: TextElement.Sizes.SMALL, color: TextElement.Colors.STANDARD}, "Need support? ", supportLink);
return ce(FlexChild.Child, {grow: 1, shrink: 1}, footer ? footer : defaultFooter);
const supportLink = ce("a", {className: `${AnchorClasses.anchor} ${AnchorClasses.anchorUnderlineOnHover}`, onClick: joinSupportServer}, "Join our Discord Server.");
const defaultFooter = ce(TextElement, {size: TextElement.Sizes.SIZE_12, color: TextElement.Colors.STANDARD}, "Need support? ", supportLink);
return ce(FlexChild, {className: OriginalModalClasses.footer + " " + OriginalModalClasses.footerSeparator},
ce(FlexChild.Child, {grow: 1, shrink: 1}, footer ? footer : defaultFooter)
);
};
const ModalActions = this.ModalActions;
const OriginalModalClasses = WebpackModules.getByProps("hideOnFullscreen", "root");
const originalRoot = OriginalModalClasses.root;
if (originalRoot) OriginalModalClasses.root = `${originalRoot} bd-changelog-modal`;
const key = ModalActions.openModal(props => {
return React.createElement(ErrorBoundary, null, React.createElement(Changelog, Object.assign({
className: `bd-changelog ${ChangelogClasses.container}`,
const body = ce("div", {
className: `${OriginalModalClasses.content} ${ChangelogClasses.container} ${ChangelogModalClasses.content} ${DiscordClasses.Scrollers.thin}`
}, changelogItems);
const key = this.ModalActions.openModal(props => {
return React.createElement(ErrorBoundary, null, React.createElement(this.ModalRoot, Object.assign({
className: `bd-changelog-modal ${OriginalModalClasses.root} ${OriginalModalClasses.small} ${ChangelogModalClasses.modal}`,
selectable: true,
onScroll: _ => _,
onClose: _ => _,
renderHeader: renderHeader,
renderFooter: renderFooter,
}, props), changelogItems));
}, props), renderHeader(), body, renderFooter()));
});
const closeModal = ModalActions.closeModal;
ModalActions.closeModal = function(k) {
Reflect.apply(closeModal, this, arguments);
setTimeout(() => {if (originalRoot && k === key) OriginalModalClasses.root = originalRoot;}, 1000);
ModalActions.closeModal = closeModal;
};
return key;
}

View File

@ -171,7 +171,7 @@ export default class AddonCard extends React.Component {
const description = this.getString(addon.description);
const version = this.getString(addon.version);
return <div id={`${addon.id}-card`} className="bd-addon-card settings-closed">
return <div id={`${addon.id}-card`} className={"bd-addon-card" + (this.props.disabled ? " bd-addon-card-disabled" : "")}>
<div className="bd-addon-header">
{this.props.type === "plugin" ? <ExtIcon size="18px" className="bd-icon" /> : <ThemeIcon size="18px" className="bd-icon" />}
<div className="bd-title">{this.buildTitle(name, version, {name: author, id: this.props.addon.authorId, link: this.props.addon.authorLink})}</div>