mirror of
https://github.com/Vendicated/Vencord.git
synced 2025-01-10 09:56:24 +00:00
bleh
This commit is contained in:
parent
20ed7dc96b
commit
63eb6358fb
7 changed files with 277 additions and 61 deletions
|
@ -220,6 +220,15 @@ export function migratePluginSettings(name: string, ...oldNames: string[]) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function migrateSettingsToArrays(pluginName: string, settings: Array<string>, stringSeparator: string = ",") {
|
||||||
|
for (const setting of settings) {
|
||||||
|
if (typeof SettingsStore.plain.plugins[pluginName][setting] !== "string") continue;
|
||||||
|
logger.info(`Migrating setting ${setting} from ${pluginName} to list`);
|
||||||
|
if (SettingsStore.plain.plugins[pluginName][setting] === "") SettingsStore.plain.plugins[pluginName][setting] = [];
|
||||||
|
else SettingsStore.plain.plugins[pluginName][setting] = SettingsStore.plain.plugins[pluginName][setting].split(stringSeparator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function definePluginSettings<
|
export function definePluginSettings<
|
||||||
Def extends SettingsDefinition,
|
Def extends SettingsDefinition,
|
||||||
Checks extends SettingsChecks<Def>,
|
Checks extends SettingsChecks<Def>,
|
||||||
|
|
|
@ -43,7 +43,8 @@ import {
|
||||||
SettingNumericComponent,
|
SettingNumericComponent,
|
||||||
SettingSelectComponent,
|
SettingSelectComponent,
|
||||||
SettingSliderComponent,
|
SettingSliderComponent,
|
||||||
SettingTextComponent
|
SettingTextComponent,
|
||||||
|
SettingListComponent,
|
||||||
} from "./components";
|
} from "./components";
|
||||||
import { openContributorModal } from "./ContributorModal";
|
import { openContributorModal } from "./ContributorModal";
|
||||||
import { GithubButton, WebsiteButton } from "./LinkIconButton";
|
import { GithubButton, WebsiteButton } from "./LinkIconButton";
|
||||||
|
@ -81,7 +82,11 @@ const Components: Record<OptionType, React.ComponentType<ISettingElementProps<an
|
||||||
[OptionType.BOOLEAN]: SettingBooleanComponent,
|
[OptionType.BOOLEAN]: SettingBooleanComponent,
|
||||||
[OptionType.SELECT]: SettingSelectComponent,
|
[OptionType.SELECT]: SettingSelectComponent,
|
||||||
[OptionType.SLIDER]: SettingSliderComponent,
|
[OptionType.SLIDER]: SettingSliderComponent,
|
||||||
[OptionType.COMPONENT]: SettingCustomComponent
|
[OptionType.COMPONENT]: SettingCustomComponent,
|
||||||
|
/* [OptionType.LIST]: SettingListComponent,
|
||||||
|
[OptionType.USERS]: SettingListComponent,
|
||||||
|
[OptionType.CHANNELS]: SettingListComponent,
|
||||||
|
[OptionType.GUILDS]: SettingListComponent */
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function PluginModal({ plugin, onRestartNeeded, onClose, transitionState }: PluginModalProps) {
|
export default function PluginModal({ plugin, onRestartNeeded, onClose, transitionState }: PluginModalProps) {
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Margins } from "@utils/margins";
|
||||||
|
import { wordsFromCamel, wordsToTitle } from "@utils/text";
|
||||||
|
import { PluginOptionSelect } from "@utils/types";
|
||||||
|
import { Forms, useState, useEffect, ChannelStore, UserStore, GuildStore } from "@webpack/common";
|
||||||
|
|
||||||
|
import { ISettingElementProps } from ".";
|
||||||
|
|
||||||
|
export function SettingListComponent({ option, pluginSettings, definedSettings, onChange, onError, id }: ISettingElementProps<PluginOptionSelect>) {
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const [items, setItems] = useState<string[]>([]);
|
||||||
|
const [newItem, setNewItem] = useState<string>("");
|
||||||
|
|
||||||
|
const addItem = () => {
|
||||||
|
if (newItem.trim() !== "") {
|
||||||
|
setItems([...items, newItem]);
|
||||||
|
setNewItem("");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeItem = (index: number) => {
|
||||||
|
setItems(items.filter((_, i) => i !== index));
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
onError(error !== null);
|
||||||
|
}, [error]);
|
||||||
|
|
||||||
|
function handleChange(newValue) {
|
||||||
|
const isValid = option.isValid?.call(definedSettings, newValue) ?? true;
|
||||||
|
if (typeof isValid === "string") setError(isValid);
|
||||||
|
else if (!isValid) setError("Invalid input provided.");
|
||||||
|
else {
|
||||||
|
setError(null);
|
||||||
|
onChange(newValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Forms.FormSection>
|
||||||
|
<Forms.FormTitle>{wordsToTitle(wordsFromCamel(id))}</Forms.FormTitle>
|
||||||
|
<Forms.FormText className={Margins.bottom16} type="description">{option.description}</Forms.FormText>
|
||||||
|
|
||||||
|
{error && <Forms.FormText style={{ color: "var(--text-danger)" }}>{error}</Forms.FormText>}
|
||||||
|
</Forms.FormSection>
|
||||||
|
);
|
||||||
|
}
|
|
@ -33,6 +33,7 @@ export interface ISettingElementProps<T extends PluginOptionBase> {
|
||||||
export * from "../../Badge";
|
export * from "../../Badge";
|
||||||
export * from "./SettingBooleanComponent";
|
export * from "./SettingBooleanComponent";
|
||||||
export * from "./SettingCustomComponent";
|
export * from "./SettingCustomComponent";
|
||||||
|
export * from "./SettingListComponent";
|
||||||
export * from "./SettingNumericComponent";
|
export * from "./SettingNumericComponent";
|
||||||
export * from "./SettingSelectComponent";
|
export * from "./SettingSelectComponent";
|
||||||
export * from "./SettingSliderComponent";
|
export * from "./SettingSliderComponent";
|
||||||
|
|
121
src/plugins/_api/settingLists.tsx
Normal file
121
src/plugins/_api/settingLists.tsx
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2025 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||||
|
import { Devs } from "@utils/constants";
|
||||||
|
import { getIntlMessage } from "@utils/discord";
|
||||||
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
|
import { Menu, useState } from "@webpack/common";
|
||||||
|
|
||||||
|
function createContextMenu(name: "Guild" | "User" | "Channel", value: any) {
|
||||||
|
return (
|
||||||
|
<Menu.MenuItem
|
||||||
|
id="vc-plugin-settings"
|
||||||
|
label="Plugins"
|
||||||
|
>
|
||||||
|
{renderRegisteredPlugins(name, value)}
|
||||||
|
</Menu.MenuItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function renderRegisteredPlugins(name: "Guild" | "User" | "Channel", value: any) {
|
||||||
|
const type = name === "Guild" ? OptionType.GUILDS : name === "User" ? OptionType.USERS : OptionType.CHANNELS;
|
||||||
|
const plugins = registeredPlugins[type];
|
||||||
|
|
||||||
|
const [checkedItems, setCheckedItems] = useState<Record<string, boolean>>({});
|
||||||
|
|
||||||
|
const handleCheckboxClick = (plugin: string, setting: string) => {
|
||||||
|
const key = `${plugin}-${setting}`;
|
||||||
|
setCheckedItems(prevState => ({
|
||||||
|
...prevState,
|
||||||
|
[key]: !prevState[key]
|
||||||
|
}));
|
||||||
|
// @ts-ignore (cannot be undefined because settings have to exist for this to be called in the first place)
|
||||||
|
const s = Vencord.Plugins.plugins[plugin].settings.store[setting];
|
||||||
|
Vencord.Plugins.plugins[plugin].settings.store[setting] = s.includes(value.id)
|
||||||
|
? s.filter(id => id !== value.id)
|
||||||
|
: [...s, value.id];
|
||||||
|
|
||||||
|
};
|
||||||
|
return Object.keys(plugins).map(plugin => (
|
||||||
|
<Menu.MenuItem
|
||||||
|
id={`vc-plugin-settings-${plugin}`}
|
||||||
|
label={plugin}
|
||||||
|
>
|
||||||
|
{plugins[plugin].map(setting => (
|
||||||
|
<Menu.MenuCheckboxItem
|
||||||
|
id={`vc-plugin-settings-${plugin}-${setting}`}
|
||||||
|
// @ts-ignore
|
||||||
|
label={Vencord.Plugins.plugins[plugin].settings.def[setting].popoutText ?? setting}
|
||||||
|
action={() => handleCheckboxClick(plugin, setting)}
|
||||||
|
checked={checkedItems[`${plugin}-${setting}`]}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Menu.MenuItem>
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function MakeContextCallback(name: "Guild" | "User" | "Channel"): NavContextMenuPatchCallback {
|
||||||
|
return (children, props) => {
|
||||||
|
const value = props[name.toLowerCase()];
|
||||||
|
if (!value) return;
|
||||||
|
if (props.label === getIntlMessage("CHANNEL_ACTIONS_MENU_LABEL")) return; // random shit like notification settings
|
||||||
|
|
||||||
|
const lastChild = children.at(-1);
|
||||||
|
if (lastChild?.key === "developer-actions") {
|
||||||
|
const p = lastChild.props;
|
||||||
|
if (!Array.isArray(p.children))
|
||||||
|
p.children = [p.children];
|
||||||
|
|
||||||
|
children = p.children;
|
||||||
|
}
|
||||||
|
children.splice(-1, 0,
|
||||||
|
createContextMenu(name, value)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// {type: {plugin: [setting, setting, setting]}}
|
||||||
|
const registeredPlugins: Record<OptionType.USERS | OptionType.GUILDS | OptionType.CHANNELS, Record<string, Array<string>>> = {
|
||||||
|
[OptionType.USERS]: {},
|
||||||
|
[OptionType.GUILDS]: {},
|
||||||
|
[OptionType.CHANNELS]: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// TODO find a better name
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "SettingListsAPI",
|
||||||
|
description: "API to automatically add context menus for settings",
|
||||||
|
authors: [Devs.Elvyra],
|
||||||
|
contextMenus: {
|
||||||
|
"channel-context": MakeContextCallback("Channel"),
|
||||||
|
"thread-context": MakeContextCallback("Channel"),
|
||||||
|
"guild-context": MakeContextCallback("Guild"),
|
||||||
|
"user-context": MakeContextCallback("User")
|
||||||
|
},
|
||||||
|
|
||||||
|
start() {
|
||||||
|
for (const plugin of Object.values(Vencord.Plugins.plugins)) {
|
||||||
|
if (!Vencord.Plugins.isPluginEnabled(plugin.name) || !plugin.settings) continue;
|
||||||
|
const settings = plugin.settings.def;
|
||||||
|
for (const settingKey of Object.keys(settings)) {
|
||||||
|
const setting = settings[settingKey];
|
||||||
|
if (setting.type === OptionType.USERS || setting.type === OptionType.GUILDS || setting.type === OptionType.CHANNELS) {
|
||||||
|
if (!registeredPlugins[setting.type][plugin.name]) {
|
||||||
|
registeredPlugins[setting.type][plugin.name] = [];
|
||||||
|
}
|
||||||
|
registeredPlugins[setting.type][plugin.name].push(settingKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
|
@ -20,7 +20,7 @@ import "./messageLogger.css";
|
||||||
|
|
||||||
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
|
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||||
import { updateMessage } from "@api/MessageUpdater";
|
import { updateMessage } from "@api/MessageUpdater";
|
||||||
import { Settings } from "@api/Settings";
|
import { definePluginSettings, migrateSettingsToArrays, Settings } from "@api/Settings";
|
||||||
import { disableStyle, enableStyle } from "@api/Styles";
|
import { disableStyle, enableStyle } from "@api/Styles";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
|
@ -36,6 +36,66 @@ import overlayStyle from "./deleteStyleOverlay.css?managed";
|
||||||
import textStyle from "./deleteStyleText.css?managed";
|
import textStyle from "./deleteStyleText.css?managed";
|
||||||
import { openHistoryModal } from "./HistoryModal";
|
import { openHistoryModal } from "./HistoryModal";
|
||||||
|
|
||||||
|
migrateSettingsToArrays("MessageLogger", ["ignoreChannels", "ignoreGuilds", "ignoreUsers"]);
|
||||||
|
|
||||||
|
|
||||||
|
const settings = definePluginSettings({
|
||||||
|
deleteStyle: {
|
||||||
|
type: OptionType.SELECT,
|
||||||
|
description: "The style of deleted messages",
|
||||||
|
options: [
|
||||||
|
{ label: "Red text", value: "text", default: true },
|
||||||
|
{ label: "Red overlay", value: "overlay" }
|
||||||
|
],
|
||||||
|
onChange: () => addDeleteStyle()
|
||||||
|
},
|
||||||
|
logDeletes: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Whether to log deleted messages",
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
collapseDeleted: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Whether to collapse deleted messages, similar to blocked messages",
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
logEdits: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Whether to log edited messages",
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
inlineEdits: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Whether to display edit history as part of message content",
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
ignoreBots: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Whether to ignore messages by bots",
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
ignoreSelf: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Whether to ignore messages by yourself",
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
ignoreUsers: {
|
||||||
|
type: OptionType.USERS,
|
||||||
|
description: "List of users to ignore",
|
||||||
|
popoutText: "Ignore deleted messages from this user",
|
||||||
|
},
|
||||||
|
ignoreChannels: {
|
||||||
|
type: OptionType.CHANNELS,
|
||||||
|
description: "List of channel IDs to ignore",
|
||||||
|
popoutText: "Ignore deleted messages in this channel"
|
||||||
|
},
|
||||||
|
ignoreGuilds: {
|
||||||
|
type: OptionType.GUILDS,
|
||||||
|
description: "List of guild IDs to ignore",
|
||||||
|
popoutText: "Ignore deleted messages in this guild",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
interface MLMessage extends Message {
|
interface MLMessage extends Message {
|
||||||
deleted?: boolean;
|
deleted?: boolean;
|
||||||
editHistory?: { timestamp: Date; content: string; }[];
|
editHistory?: { timestamp: Date; content: string; }[];
|
||||||
|
@ -145,7 +205,7 @@ export default definePlugin({
|
||||||
name: "MessageLogger",
|
name: "MessageLogger",
|
||||||
description: "Temporarily logs deleted and edited messages.",
|
description: "Temporarily logs deleted and edited messages.",
|
||||||
authors: [Devs.rushii, Devs.Ven, Devs.AutumnVN, Devs.Nickyux, Devs.Kyuuhachi],
|
authors: [Devs.rushii, Devs.Ven, Devs.AutumnVN, Devs.Nickyux, Devs.Kyuuhachi],
|
||||||
dependencies: ["MessageUpdaterAPI"],
|
dependencies: ["MessageUpdaterAPI", "SettingListsAPI"],
|
||||||
|
|
||||||
contextMenus: {
|
contextMenus: {
|
||||||
"message": patchMessageContextMenu,
|
"message": patchMessageContextMenu,
|
||||||
|
@ -192,63 +252,7 @@ export default definePlugin({
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
options: {
|
settings: settings,
|
||||||
deleteStyle: {
|
|
||||||
type: OptionType.SELECT,
|
|
||||||
description: "The style of deleted messages",
|
|
||||||
default: "text",
|
|
||||||
options: [
|
|
||||||
{ label: "Red text", value: "text", default: true },
|
|
||||||
{ label: "Red overlay", value: "overlay" }
|
|
||||||
],
|
|
||||||
onChange: () => addDeleteStyle()
|
|
||||||
},
|
|
||||||
logDeletes: {
|
|
||||||
type: OptionType.BOOLEAN,
|
|
||||||
description: "Whether to log deleted messages",
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
collapseDeleted: {
|
|
||||||
type: OptionType.BOOLEAN,
|
|
||||||
description: "Whether to collapse deleted messages, similar to blocked messages",
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
logEdits: {
|
|
||||||
type: OptionType.BOOLEAN,
|
|
||||||
description: "Whether to log edited messages",
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
inlineEdits: {
|
|
||||||
type: OptionType.BOOLEAN,
|
|
||||||
description: "Whether to display edit history as part of message content",
|
|
||||||
default: true
|
|
||||||
},
|
|
||||||
ignoreBots: {
|
|
||||||
type: OptionType.BOOLEAN,
|
|
||||||
description: "Whether to ignore messages by bots",
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
ignoreSelf: {
|
|
||||||
type: OptionType.BOOLEAN,
|
|
||||||
description: "Whether to ignore messages by yourself",
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
ignoreUsers: {
|
|
||||||
type: OptionType.STRING,
|
|
||||||
description: "Comma-separated list of user IDs to ignore",
|
|
||||||
default: ""
|
|
||||||
},
|
|
||||||
ignoreChannels: {
|
|
||||||
type: OptionType.STRING,
|
|
||||||
description: "Comma-separated list of channel IDs to ignore",
|
|
||||||
default: ""
|
|
||||||
},
|
|
||||||
ignoreGuilds: {
|
|
||||||
type: OptionType.STRING,
|
|
||||||
description: "Comma-separated list of guild IDs to ignore",
|
|
||||||
default: ""
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
handleDelete(cache: any, data: { ids: string[], id: string; mlDeleted?: boolean; }, isBulk: boolean) {
|
handleDelete(cache: any, data: { ids: string[], id: string; mlDeleted?: boolean; }, isBulk: boolean) {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -167,6 +167,10 @@ export const enum OptionType {
|
||||||
SELECT,
|
SELECT,
|
||||||
SLIDER,
|
SLIDER,
|
||||||
COMPONENT,
|
COMPONENT,
|
||||||
|
LIST,
|
||||||
|
USERS, // List of users
|
||||||
|
CHANNELS, // List of channels
|
||||||
|
GUILDS, // List of guilds
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SettingsDefinition = Record<string, PluginSettingDef>;
|
export type SettingsDefinition = Record<string, PluginSettingDef>;
|
||||||
|
@ -183,6 +187,7 @@ export type PluginSettingDef = (
|
||||||
| PluginSettingSliderDef
|
| PluginSettingSliderDef
|
||||||
| PluginSettingComponentDef
|
| PluginSettingComponentDef
|
||||||
| PluginSettingBigIntDef
|
| PluginSettingBigIntDef
|
||||||
|
| PluginSettingListDef
|
||||||
) & PluginSettingCommon;
|
) & PluginSettingCommon;
|
||||||
|
|
||||||
export interface PluginSettingCommon {
|
export interface PluginSettingCommon {
|
||||||
|
@ -259,6 +264,11 @@ export interface PluginSettingSliderDef {
|
||||||
stickToMarkers?: boolean;
|
stickToMarkers?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PluginSettingListDef{
|
||||||
|
type: OptionType.LIST | OptionType.CHANNELS | OptionType.GUILDS | OptionType.USERS;
|
||||||
|
popoutText?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IPluginOptionComponentProps {
|
export interface IPluginOptionComponentProps {
|
||||||
/**
|
/**
|
||||||
* Run this when the value changes.
|
* Run this when the value changes.
|
||||||
|
|
Loading…
Reference in a new issue