mirror of
https://github.com/Vendicated/Vencord.git
synced 2025-02-24 15:35:11 +00:00
Merge branch 'dev' into feat/moreClonableEmotesv2
This commit is contained in:
commit
eb93253b03
107 changed files with 3154 additions and 2475 deletions
|
@ -4,10 +4,9 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
// @ts-check
|
||||
|
||||
import stylistic from "@stylistic/eslint-plugin";
|
||||
import pathAlias from "eslint-plugin-path-alias";
|
||||
import react from "eslint-plugin-react";
|
||||
import header from "eslint-plugin-simple-header";
|
||||
import simpleImportSort from "eslint-plugin-simple-import-sort";
|
||||
import unusedImports from "eslint-plugin-unused-imports";
|
||||
|
@ -15,6 +14,22 @@ import tseslint from "typescript-eslint";
|
|||
|
||||
export default tseslint.config(
|
||||
{ ignores: ["dist", "browser", "packages/vencord-types"] },
|
||||
{
|
||||
files: ["src/**/*.{tsx,ts,mts,mjs,js,jsx}", "eslint.config.mjs"],
|
||||
settings: {
|
||||
react: {
|
||||
version: "18"
|
||||
}
|
||||
},
|
||||
...react.configs.flat.recommended,
|
||||
rules: {
|
||||
...react.configs.flat.recommended.rules,
|
||||
"react/react-in-jsx-scope": "off",
|
||||
"react/prop-types": "off",
|
||||
"react/display-name": "off",
|
||||
"react/no-unescaped-entities": "off",
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ["src/**/*.{tsx,ts,mts,mjs,js,jsx}", "eslint.config.mjs"],
|
||||
plugins: {
|
||||
|
@ -23,7 +38,7 @@ export default tseslint.config(
|
|||
"@typescript-eslint": tseslint.plugin,
|
||||
"simple-import-sort": simpleImportSort,
|
||||
"unused-imports": unusedImports,
|
||||
"path-alias": pathAlias,
|
||||
"path-alias": pathAlias
|
||||
},
|
||||
settings: {
|
||||
"import/resolver": {
|
||||
|
@ -90,7 +105,13 @@ export default tseslint.config(
|
|||
"no-invalid-regexp": "error",
|
||||
"no-constant-condition": ["error", { "checkLoops": false }],
|
||||
"no-duplicate-imports": "error",
|
||||
"dot-notation": "error",
|
||||
"@typescript-eslint/dot-notation": [
|
||||
"error",
|
||||
{
|
||||
"allowPrivateClassPropertyAccess": true,
|
||||
"allowProtectedClassPropertyAccess": true
|
||||
}
|
||||
],
|
||||
"no-useless-escape": [
|
||||
"error",
|
||||
{
|
||||
|
|
53
package.json
53
package.json
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "vencord",
|
||||
"private": "true",
|
||||
"version": "1.10.9",
|
||||
"version": "1.11.2",
|
||||
"description": "The cutest Discord client mod",
|
||||
"homepage": "https://github.com/Vendicated/Vencord#readme",
|
||||
"bugs": {
|
||||
|
@ -41,48 +41,49 @@
|
|||
"@vap/shiki": "0.10.5",
|
||||
"fflate": "^0.8.2",
|
||||
"gifenc": "github:mattdesl/gifenc#64842fca317b112a8590f8fef2bf3825da8f6fe3",
|
||||
"monaco-editor": "^0.50.0",
|
||||
"nanoid": "^5.0.7",
|
||||
"monaco-editor": "^0.52.2",
|
||||
"nanoid": "^5.0.9",
|
||||
"virtual-merge": "^1.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@stylistic/eslint-plugin": "^2.6.1",
|
||||
"@types/chrome": "^0.0.269",
|
||||
"@types/diff": "^5.2.1",
|
||||
"@types/lodash": "^4.17.7",
|
||||
"@types/node": "^22.0.3",
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@stylistic/eslint-plugin": "^2.12.1",
|
||||
"@types/chrome": "^0.0.287",
|
||||
"@types/diff": "^6.0.0",
|
||||
"@types/lodash": "^4.17.14",
|
||||
"@types/node": "^22.10.5",
|
||||
"@types/react": "^19.0.2",
|
||||
"@types/react-dom": "^19.0.2",
|
||||
"@types/yazl": "^2.4.5",
|
||||
"diff": "^5.2.0",
|
||||
"diff": "^7.0.0",
|
||||
"discord-types": "^1.3.26",
|
||||
"esbuild": "^0.15.18",
|
||||
"eslint": "^9.8.0",
|
||||
"eslint": "^9.17.0",
|
||||
"eslint-import-resolver-alias": "^1.1.2",
|
||||
"eslint-plugin-path-alias": "2.1.0",
|
||||
"eslint-plugin-simple-header": "^1.1.1",
|
||||
"eslint-plugin-react": "^7.37.3",
|
||||
"eslint-plugin-simple-header": "^1.2.1",
|
||||
"eslint-plugin-simple-import-sort": "^12.1.1",
|
||||
"eslint-plugin-unused-imports": "^4.0.1",
|
||||
"highlight.js": "10.7.3",
|
||||
"eslint-plugin-unused-imports": "^4.1.4",
|
||||
"highlight.js": "11.7.0",
|
||||
"html-minifier-terser": "^7.2.0",
|
||||
"moment": "^2.30.1",
|
||||
"puppeteer-core": "^22.15.0",
|
||||
"moment": "^2.22.2",
|
||||
"puppeteer-core": "^23.11.1",
|
||||
"standalone-electron-types": "^1.0.0",
|
||||
"stylelint": "^16.8.1",
|
||||
"stylelint": "^16.12.0",
|
||||
"stylelint-config-standard": "^36.0.1",
|
||||
"ts-patch": "^3.2.1",
|
||||
"ts-pattern": "^5.3.1",
|
||||
"tsx": "^4.16.5",
|
||||
"type-fest": "^4.23.0",
|
||||
"typescript": "^5.5.4",
|
||||
"typescript-eslint": "^8.0.0",
|
||||
"typescript-transform-paths": "^3.4.7",
|
||||
"ts-patch": "^3.3.0",
|
||||
"ts-pattern": "^5.6.0",
|
||||
"tsx": "^4.19.2",
|
||||
"type-fest": "^4.31.0",
|
||||
"typescript": "^5.7.2",
|
||||
"typescript-eslint": "^8.19.0",
|
||||
"typescript-transform-paths": "^3.5.3",
|
||||
"zip-local": "^0.3.5"
|
||||
},
|
||||
"packageManager": "pnpm@9.1.0",
|
||||
"pnpm": {
|
||||
"patchedDependencies": {
|
||||
"eslint@9.8.0": "patches/eslint@9.8.0.patch",
|
||||
"eslint@9.17.0": "patches/eslint@9.17.0.patch",
|
||||
"eslint-plugin-path-alias@2.1.0": "patches/eslint-plugin-path-alias@2.1.0.patch"
|
||||
},
|
||||
"peerDependencyRules": {
|
||||
|
|
3259
pnpm-lock.yaml
generated
3259
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
@ -37,7 +37,8 @@ const CANARY = process.env.USE_CANARY === "true";
|
|||
|
||||
const browser = await pup.launch({
|
||||
headless: true,
|
||||
executablePath: process.env.CHROMIUM_BIN
|
||||
executablePath: process.env.CHROMIUM_BIN,
|
||||
args: ["--no-sandbox"]
|
||||
});
|
||||
|
||||
const page = await browser.newPage();
|
||||
|
|
|
@ -57,7 +57,7 @@ const Badges = new Set<ProfileBadge>();
|
|||
* Register a new badge with the Badges API
|
||||
* @param badge The badge to register
|
||||
*/
|
||||
export function addBadge(badge: ProfileBadge) {
|
||||
export function addProfileBadge(badge: ProfileBadge) {
|
||||
badge.component &&= ErrorBoundary.wrap(badge.component, { noop: true });
|
||||
Badges.add(badge);
|
||||
}
|
||||
|
@ -66,7 +66,7 @@ export function addBadge(badge: ProfileBadge) {
|
|||
* Unregister a badge from the Badges API
|
||||
* @param badge The badge to remove
|
||||
*/
|
||||
export function removeBadge(badge: ProfileBadge) {
|
||||
export function removeProfileBadge(badge: ProfileBadge) {
|
||||
return Badges.delete(badge);
|
||||
}
|
||||
|
||||
|
@ -100,20 +100,3 @@ export interface BadgeUserArgs {
|
|||
userId: string;
|
||||
guildId: string;
|
||||
}
|
||||
|
||||
interface ConnectedAccount {
|
||||
type: string;
|
||||
id: string;
|
||||
name: string;
|
||||
verified: boolean;
|
||||
}
|
||||
|
||||
interface Profile {
|
||||
connectedAccounts: ConnectedAccount[];
|
||||
premiumType: number;
|
||||
premiumSince: string;
|
||||
premiumGuildSince?: any;
|
||||
lastFetched: number;
|
||||
profileFetchFailed: boolean;
|
||||
application?: any;
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ import { Logger } from "@utils/Logger";
|
|||
import { waitFor } from "@webpack";
|
||||
import { Button, ButtonLooks, ButtonWrapperClasses, Tooltip } from "@webpack/common";
|
||||
import { Channel } from "discord-types/general";
|
||||
import { HTMLProps, MouseEventHandler, ReactNode } from "react";
|
||||
import { HTMLProps, JSX, MouseEventHandler, ReactNode } from "react";
|
||||
|
||||
let ChannelTextAreaClasses: Record<"button" | "buttonContainer", string>;
|
||||
waitFor(["buttonContainer", "channelTextArea"], m => ChannelTextAreaClasses = m);
|
||||
|
@ -74,9 +74,9 @@ export interface ChatBarProps {
|
|||
};
|
||||
}
|
||||
|
||||
export type ChatBarButton = (props: ChatBarProps & { isMainChat: boolean; }) => JSX.Element | null;
|
||||
export type ChatBarButtonFactory = (props: ChatBarProps & { isMainChat: boolean; }) => JSX.Element | null;
|
||||
|
||||
const buttonFactories = new Map<string, ChatBarButton>();
|
||||
const buttonFactories = new Map<string, ChatBarButtonFactory>();
|
||||
const logger = new Logger("ChatButtons");
|
||||
|
||||
export function _injectButtons(buttons: ReactNode[], props: ChatBarProps) {
|
||||
|
@ -91,7 +91,7 @@ export function _injectButtons(buttons: ReactNode[], props: ChatBarProps) {
|
|||
}
|
||||
}
|
||||
|
||||
export const addChatBarButton = (id: string, button: ChatBarButton) => buttonFactories.set(id, button);
|
||||
export const addChatBarButton = (id: string, button: ChatBarButtonFactory) => buttonFactories.set(id, button);
|
||||
export const removeChatBarButton = (id: string) => buttonFactories.delete(id);
|
||||
|
||||
export interface ChatBarButtonProps {
|
||||
|
|
|
@ -24,13 +24,13 @@ import type { ReactElement } from "react";
|
|||
* @param children The rendered context menu elements
|
||||
* @param args Any arguments passed into making the context menu, like the guild, channel, user or message for example
|
||||
*/
|
||||
export type NavContextMenuPatchCallback = (children: Array<ReactElement | null>, ...args: Array<any>) => void;
|
||||
export type NavContextMenuPatchCallback = (children: Array<ReactElement<any> | null>, ...args: Array<any>) => void;
|
||||
/**
|
||||
* @param navId The navId of the context menu being patched
|
||||
* @param children The rendered context menu elements
|
||||
* @param args Any arguments passed into making the context menu, like the guild, channel, user or message for example
|
||||
*/
|
||||
export type GlobalContextMenuPatchCallback = (navId: string, children: Array<ReactElement | null>, ...args: Array<any>) => void;
|
||||
export type GlobalContextMenuPatchCallback = (navId: string, children: Array<ReactElement<any> | null>, ...args: Array<any>) => void;
|
||||
|
||||
const ContextMenuLogger = new Logger("ContextMenu");
|
||||
|
||||
|
@ -70,7 +70,7 @@ export function addGlobalContextMenuPatch(patch: GlobalContextMenuPatchCallback)
|
|||
* @returns Whether the patch was successfully removed from the context menu(s)
|
||||
*/
|
||||
export function removeContextMenuPatch<T extends string | Array<string>>(navId: T, patch: NavContextMenuPatchCallback): T extends string ? boolean : Array<boolean> {
|
||||
const navIds = Array.isArray(navId) ? navId : [navId as string];
|
||||
const navIds: string[] = Array.isArray(navId) ? navId : [navId];
|
||||
|
||||
const results = navIds.map(id => navPatches.get(id)?.delete(patch) ?? false);
|
||||
|
||||
|
@ -92,7 +92,7 @@ export function removeGlobalContextMenuPatch(patch: GlobalContextMenuPatchCallba
|
|||
* @param children The context menu children
|
||||
* @param matchSubstring Whether to check if the id is a substring of the child id
|
||||
*/
|
||||
export function findGroupChildrenByChildId(id: string | string[], children: Array<ReactElement | null | undefined>, matchSubstring = false): Array<ReactElement | null | undefined> | null {
|
||||
export function findGroupChildrenByChildId(id: string | string[], children: Array<ReactElement<any> | null | undefined>, matchSubstring = false): Array<ReactElement<any> | null | undefined> | null {
|
||||
for (const child of children) {
|
||||
if (child == null) continue;
|
||||
|
||||
|
@ -124,7 +124,7 @@ export function findGroupChildrenByChildId(id: string | string[], children: Arra
|
|||
interface ContextMenuProps {
|
||||
contextMenuApiArguments?: Array<any>;
|
||||
navId: string;
|
||||
children: Array<ReactElement | null>;
|
||||
children: Array<ReactElement<any> | null>;
|
||||
"aria-label": string;
|
||||
onSelect: (() => void) | undefined;
|
||||
onClose: (callback: (...args: Array<any>) => any) => void;
|
||||
|
@ -162,7 +162,7 @@ export function _usePatchContextMenu(props: ContextMenuProps) {
|
|||
return props;
|
||||
}
|
||||
|
||||
function cloneMenuChildren(obj: ReactElement | Array<ReactElement | null> | null) {
|
||||
function cloneMenuChildren(obj: ReactElement<any> | Array<ReactElement<any> | null> | null) {
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.map(cloneMenuChildren);
|
||||
}
|
||||
|
|
|
@ -16,7 +16,9 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Channel, User } from "discord-types/general/index.js";
|
||||
import { JSX } from "react";
|
||||
|
||||
interface DecoratorProps {
|
||||
activities: any[];
|
||||
|
@ -38,27 +40,32 @@ interface DecoratorProps {
|
|||
user: User;
|
||||
[key: string]: any;
|
||||
}
|
||||
export type Decorator = (props: DecoratorProps) => JSX.Element | null;
|
||||
export type MemberListDecoratorFactory = (props: DecoratorProps) => JSX.Element | null;
|
||||
type OnlyIn = "guilds" | "dms";
|
||||
|
||||
export const decorators = new Map<string, { decorator: Decorator, onlyIn?: OnlyIn; }>();
|
||||
export const decorators = new Map<string, { render: MemberListDecoratorFactory, onlyIn?: OnlyIn; }>();
|
||||
|
||||
export function addDecorator(identifier: string, decorator: Decorator, onlyIn?: OnlyIn) {
|
||||
decorators.set(identifier, { decorator, onlyIn });
|
||||
export function addMemberListDecorator(identifier: string, render: MemberListDecoratorFactory, onlyIn?: OnlyIn) {
|
||||
decorators.set(identifier, { render, onlyIn });
|
||||
}
|
||||
|
||||
export function removeDecorator(identifier: string) {
|
||||
export function removeMemberListDecorator(identifier: string) {
|
||||
decorators.delete(identifier);
|
||||
}
|
||||
|
||||
export function __getDecorators(props: DecoratorProps): (JSX.Element | null)[] {
|
||||
const isInGuild = !!(props.guildId);
|
||||
return Array.from(decorators.values(), decoratorObj => {
|
||||
const { decorator, onlyIn } = decoratorObj;
|
||||
// this can most likely be done cleaner
|
||||
if (!onlyIn || (onlyIn === "guilds" && isInGuild) || (onlyIn === "dms" && !isInGuild)) {
|
||||
return decorator(props);
|
||||
}
|
||||
return Array.from(
|
||||
decorators.entries(),
|
||||
([key, { render: Decorator, onlyIn }]) => {
|
||||
if ((onlyIn === "guilds" && !isInGuild) || (onlyIn === "dms" && isInGuild))
|
||||
return null;
|
||||
});
|
||||
|
||||
return (
|
||||
<ErrorBoundary noop key={key} message={`Failed to render ${key} Member List Decorator`}>
|
||||
<Decorator {...props} />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
|
@ -16,26 +16,29 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export type AccessoryCallback = (props: Record<string, any>) => JSX.Element | null | Array<JSX.Element | null>;
|
||||
export type Accessory = {
|
||||
callback: AccessoryCallback;
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { JSX, ReactNode } from "react";
|
||||
|
||||
export type MessageAccessoryFactory = (props: Record<string, any>) => ReactNode;
|
||||
export type MessageAccessory = {
|
||||
render: MessageAccessoryFactory;
|
||||
position?: number;
|
||||
};
|
||||
|
||||
export const accessories = new Map<String, Accessory>();
|
||||
export const accessories = new Map<string, MessageAccessory>();
|
||||
|
||||
export function addAccessory(
|
||||
export function addMessageAccessory(
|
||||
identifier: string,
|
||||
callback: AccessoryCallback,
|
||||
render: MessageAccessoryFactory,
|
||||
position?: number
|
||||
) {
|
||||
accessories.set(identifier, {
|
||||
callback,
|
||||
render,
|
||||
position,
|
||||
});
|
||||
}
|
||||
|
||||
export function removeAccessory(identifier: string) {
|
||||
export function removeMessageAccessory(identifier: string) {
|
||||
accessories.delete(identifier);
|
||||
}
|
||||
|
||||
|
@ -43,15 +46,12 @@ export function _modifyAccessories(
|
|||
elements: JSX.Element[],
|
||||
props: Record<string, any>
|
||||
) {
|
||||
for (const accessory of accessories.values()) {
|
||||
let accessories = accessory.callback(props);
|
||||
if (accessories == null)
|
||||
continue;
|
||||
|
||||
if (!Array.isArray(accessories))
|
||||
accessories = [accessories];
|
||||
else if (accessories.length === 0)
|
||||
continue;
|
||||
for (const [key, accessory] of accessories.entries()) {
|
||||
const res = (
|
||||
<ErrorBoundary message={`Failed to render ${key} Message Accessory`} key={key}>
|
||||
<accessory.render {...props} />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
|
||||
elements.splice(
|
||||
accessory.position != null
|
||||
|
@ -60,7 +60,7 @@ export function _modifyAccessories(
|
|||
: accessory.position
|
||||
: elements.length,
|
||||
0,
|
||||
...accessories.filter(e => e != null) as JSX.Element[]
|
||||
res
|
||||
);
|
||||
}
|
||||
|
|
@ -16,9 +16,11 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Channel, Message } from "discord-types/general/index.js";
|
||||
import { JSX } from "react";
|
||||
|
||||
interface DecorationProps {
|
||||
export interface MessageDecorationProps {
|
||||
author: {
|
||||
/**
|
||||
* Will be username if the user has no nickname
|
||||
|
@ -44,20 +46,25 @@ interface DecorationProps {
|
|||
message: Message;
|
||||
[key: string]: any;
|
||||
}
|
||||
export type Decoration = (props: DecorationProps) => JSX.Element | null;
|
||||
export type MessageDecorationFactory = (props: MessageDecorationProps) => JSX.Element | null;
|
||||
|
||||
export const decorations = new Map<string, Decoration>();
|
||||
export const decorations = new Map<string, MessageDecorationFactory>();
|
||||
|
||||
export function addDecoration(identifier: string, decoration: Decoration) {
|
||||
export function addMessageDecoration(identifier: string, decoration: MessageDecorationFactory) {
|
||||
decorations.set(identifier, decoration);
|
||||
}
|
||||
|
||||
export function removeDecoration(identifier: string) {
|
||||
export function removeMessageDecoration(identifier: string) {
|
||||
decorations.delete(identifier);
|
||||
}
|
||||
|
||||
export function __addDecorationsToMessage(props: DecorationProps): (JSX.Element | null)[] {
|
||||
return [...decorations.values()].map(decoration => {
|
||||
return decoration(props);
|
||||
});
|
||||
export function __addDecorationsToMessage(props: MessageDecorationProps): (JSX.Element | null)[] {
|
||||
return Array.from(
|
||||
decorations.entries(),
|
||||
([key, Decoration]) => (
|
||||
<ErrorBoundary noop message={`Failed to render ${key} Message Decoration`} key={key}>
|
||||
<Decoration {...props} />
|
||||
</ErrorBoundary>
|
||||
)
|
||||
);
|
||||
}
|
|
@ -73,11 +73,11 @@ export interface MessageExtra {
|
|||
openWarningPopout: (props: any) => any;
|
||||
}
|
||||
|
||||
export type SendListener = (channelId: string, messageObj: MessageObject, extra: MessageExtra) => Promisable<void | { cancel: boolean; }>;
|
||||
export type EditListener = (channelId: string, messageId: string, messageObj: MessageObject) => Promisable<void | { cancel: boolean; }>;
|
||||
export type MessageSendListener = (channelId: string, messageObj: MessageObject, extra: MessageExtra) => Promisable<void | { cancel: boolean; }>;
|
||||
export type MessageEditListener = (channelId: string, messageId: string, messageObj: MessageObject) => Promisable<void | { cancel: boolean; }>;
|
||||
|
||||
const sendListeners = new Set<SendListener>();
|
||||
const editListeners = new Set<EditListener>();
|
||||
const sendListeners = new Set<MessageSendListener>();
|
||||
const editListeners = new Set<MessageEditListener>();
|
||||
|
||||
export async function _handlePreSend(channelId: string, messageObj: MessageObject, extra: MessageExtra, replyOptions: MessageReplyOptions) {
|
||||
extra.replyOptions = replyOptions;
|
||||
|
@ -111,29 +111,29 @@ export async function _handlePreEdit(channelId: string, messageId: string, messa
|
|||
/**
|
||||
* Note: This event fires off before a message is sent, allowing you to edit the message.
|
||||
*/
|
||||
export function addPreSendListener(listener: SendListener) {
|
||||
export function addMessagePreSendListener(listener: MessageSendListener) {
|
||||
sendListeners.add(listener);
|
||||
return listener;
|
||||
}
|
||||
/**
|
||||
* Note: This event fires off before a message's edit is applied, allowing you to further edit the message.
|
||||
*/
|
||||
export function addPreEditListener(listener: EditListener) {
|
||||
export function addMessagePreEditListener(listener: MessageEditListener) {
|
||||
editListeners.add(listener);
|
||||
return listener;
|
||||
}
|
||||
export function removePreSendListener(listener: SendListener) {
|
||||
export function removeMessagePreSendListener(listener: MessageSendListener) {
|
||||
return sendListeners.delete(listener);
|
||||
}
|
||||
export function removePreEditListener(listener: EditListener) {
|
||||
export function removeMessagePreEditListener(listener: MessageEditListener) {
|
||||
return editListeners.delete(listener);
|
||||
}
|
||||
|
||||
|
||||
// Message clicks
|
||||
type ClickListener = (message: Message, channel: Channel, event: MouseEvent) => void;
|
||||
export type MessageClickListener = (message: Message, channel: Channel, event: MouseEvent) => void;
|
||||
|
||||
const listeners = new Set<ClickListener>();
|
||||
const listeners = new Set<MessageClickListener>();
|
||||
|
||||
export function _handleClick(message: Message, channel: Channel, event: MouseEvent) {
|
||||
// message object may be outdated, so (try to) fetch latest one
|
||||
|
@ -147,11 +147,11 @@ export function _handleClick(message: Message, channel: Channel, event: MouseEve
|
|||
}
|
||||
}
|
||||
|
||||
export function addClickListener(listener: ClickListener) {
|
||||
export function addMessageClickListener(listener: MessageClickListener) {
|
||||
listeners.add(listener);
|
||||
return listener;
|
||||
}
|
||||
|
||||
export function removeClickListener(listener: ClickListener) {
|
||||
export function removeMessageClickListener(listener: MessageClickListener) {
|
||||
return listeners.delete(listener);
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ import type { ComponentType, MouseEventHandler } from "react";
|
|||
|
||||
const logger = new Logger("MessagePopover");
|
||||
|
||||
export interface ButtonItem {
|
||||
export interface MessagePopoverButtonItem {
|
||||
key?: string,
|
||||
label: string,
|
||||
icon: ComponentType<any>,
|
||||
|
@ -33,23 +33,23 @@ export interface ButtonItem {
|
|||
onContextMenu?: MouseEventHandler<HTMLButtonElement>;
|
||||
}
|
||||
|
||||
export type getButtonItem = (message: Message) => ButtonItem | null;
|
||||
export type MessagePopoverButtonFactory = (message: Message) => MessagePopoverButtonItem | null;
|
||||
|
||||
export const buttons = new Map<string, getButtonItem>();
|
||||
export const buttons = new Map<string, MessagePopoverButtonFactory>();
|
||||
|
||||
export function addButton(
|
||||
export function addMessagePopoverButton(
|
||||
identifier: string,
|
||||
item: getButtonItem,
|
||||
item: MessagePopoverButtonFactory,
|
||||
) {
|
||||
buttons.set(identifier, item);
|
||||
}
|
||||
|
||||
export function removeButton(identifier: string) {
|
||||
export function removeMessagePopoverButton(identifier: string) {
|
||||
buttons.delete(identifier);
|
||||
}
|
||||
|
||||
export function _buildPopoverElements(
|
||||
Component: React.ComponentType<ButtonItem>,
|
||||
Component: React.ComponentType<MessagePopoverButtonItem>,
|
||||
message: Message
|
||||
) {
|
||||
const items: React.ReactNode[] = [];
|
||||
|
|
|
@ -16,40 +16,36 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Logger } from "@utils/Logger";
|
||||
|
||||
const logger = new Logger("ServerListAPI");
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { ComponentType } from "react";
|
||||
|
||||
export const enum ServerListRenderPosition {
|
||||
Above,
|
||||
In,
|
||||
}
|
||||
|
||||
const renderFunctionsAbove = new Set<Function>();
|
||||
const renderFunctionsIn = new Set<Function>();
|
||||
const componentsAbove = new Set<ComponentType>();
|
||||
const componentsBelow = new Set<ComponentType>();
|
||||
|
||||
function getRenderFunctions(position: ServerListRenderPosition) {
|
||||
return position === ServerListRenderPosition.Above ? renderFunctionsAbove : renderFunctionsIn;
|
||||
return position === ServerListRenderPosition.Above ? componentsAbove : componentsBelow;
|
||||
}
|
||||
|
||||
export function addServerListElement(position: ServerListRenderPosition, renderFunction: Function) {
|
||||
export function addServerListElement(position: ServerListRenderPosition, renderFunction: ComponentType) {
|
||||
getRenderFunctions(position).add(renderFunction);
|
||||
}
|
||||
|
||||
export function removeServerListElement(position: ServerListRenderPosition, renderFunction: Function) {
|
||||
export function removeServerListElement(position: ServerListRenderPosition, renderFunction: ComponentType) {
|
||||
getRenderFunctions(position).delete(renderFunction);
|
||||
}
|
||||
|
||||
export const renderAll = (position: ServerListRenderPosition) => {
|
||||
const ret: Array<JSX.Element> = [];
|
||||
|
||||
for (const renderFunction of getRenderFunctions(position)) {
|
||||
try {
|
||||
ret.unshift(renderFunction());
|
||||
} catch (e) {
|
||||
logger.error("Failed to render server list element:", e);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
return Array.from(
|
||||
getRenderFunctions(position),
|
||||
(Component, i) => (
|
||||
<ErrorBoundary noop key={i}>
|
||||
<Component />
|
||||
</ErrorBoundary>
|
||||
)
|
||||
);
|
||||
};
|
|
@ -23,7 +23,7 @@ import { Logger } from "@utils/Logger";
|
|||
import { mergeDefaults } from "@utils/mergeDefaults";
|
||||
import { putCloudSettings } from "@utils/settingsSync";
|
||||
import { DefinedSettings, OptionType, SettingsChecks, SettingsDefinition } from "@utils/types";
|
||||
import { React } from "@webpack/common";
|
||||
import { React, useEffect } from "@webpack/common";
|
||||
|
||||
import plugins from "~plugins";
|
||||
|
||||
|
@ -192,7 +192,7 @@ export const Settings = SettingsStore.store;
|
|||
export function useSettings(paths?: UseSettings<Settings>[]) {
|
||||
const [, forceUpdate] = React.useReducer(() => ({}), {});
|
||||
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
if (paths) {
|
||||
paths.forEach(p => SettingsStore.addChangeListener(p, forceUpdate));
|
||||
return () => paths.forEach(p => SettingsStore.removeChangeListener(p, forceUpdate));
|
||||
|
@ -200,7 +200,7 @@ export function useSettings(paths?: UseSettings<Settings>[]) {
|
|||
SettingsStore.addGlobalChangeListener(forceUpdate);
|
||||
return () => SettingsStore.removeGlobalChangeListener(forceUpdate);
|
||||
}
|
||||
}, []);
|
||||
}, [paths]);
|
||||
|
||||
return SettingsStore.store;
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export function Badge({ text, color }): JSX.Element {
|
||||
export function Badge({ text, color }) {
|
||||
return (
|
||||
<div className="vc-plugins-badge" style={{
|
||||
backgroundColor: color,
|
||||
|
|
|
@ -70,8 +70,7 @@ const ErrorBoundary = LazyComponent(() => {
|
|||
|
||||
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
||||
this.props.onError?.({ error, errorInfo, props: this.props.wrappedProps });
|
||||
logger.error("A component threw an Error\n", error);
|
||||
logger.error("Component Stack", errorInfo.componentStack);
|
||||
logger.error(`${this.props.message || "A component threw an Error"}\n`, error, errorInfo.componentStack);
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -80,11 +79,14 @@ const ErrorBoundary = LazyComponent(() => {
|
|||
if (this.props.noop) return null;
|
||||
|
||||
if (this.props.fallback)
|
||||
return <this.props.fallback
|
||||
return (
|
||||
<this.props.fallback
|
||||
wrappedProps={this.props.wrappedProps}
|
||||
children={this.props.children}
|
||||
{...this.state}
|
||||
/>;
|
||||
>
|
||||
{this.props.children}
|
||||
</this.props.fallback>
|
||||
);
|
||||
|
||||
const msg = this.props.message || "An error occurred while rendering this Component. More info can be found below and in your console.";
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { CSSProperties } from "react";
|
||||
import { CSSProperties, JSX } from "react";
|
||||
|
||||
interface Props {
|
||||
columns: number;
|
||||
|
|
|
@ -27,7 +27,7 @@ export function Heart() {
|
|||
>
|
||||
<path
|
||||
fill="#db61a2"
|
||||
fill-rule="evenodd"
|
||||
fillRule="evenodd"
|
||||
d="M4.25 2.5c-1.336 0-2.75 1.164-2.75 3 0 2.15 1.58 4.144 3.365 5.682A20.565 20.565 0 008 13.393a20.561 20.561 0 003.135-2.211C12.92 9.644 14.5 7.65 14.5 5.5c0-1.836-1.414-3-2.75-3-1.373 0-2.609.986-3.029 2.456a.75.75 0 01-1.442 0C6.859 3.486 5.623 2.5 4.25 2.5zM8 14.25l-.345.666-.002-.001-.006-.003-.018-.01a7.643 7.643 0 01-.31-.17 22.075 22.075 0 01-3.434-2.414C2.045 10.731 0 8.35 0 5.5 0 2.836 2.086 1 4.25 1 5.797 1 7.153 1.802 8 3.02 8.847 1.802 10.203 1 11.75 1 13.914 1 16 2.836 16 5.5c0 2.85-2.045 5.231-3.885 6.818a22.08 22.08 0 01-3.744 2.584l-.018.01-.006.003h-.002L8 14.25zm0 0l.345.666a.752.752 0 01-.69 0L8 14.25z"
|
||||
/>
|
||||
</svg>
|
||||
|
|
|
@ -20,7 +20,7 @@ import "./iconStyles.css";
|
|||
|
||||
import { getIntlMessage } from "@utils/discord";
|
||||
import { classes } from "@utils/misc";
|
||||
import type { PropsWithChildren } from "react";
|
||||
import type { JSX, PropsWithChildren } from "react";
|
||||
|
||||
interface BaseIconProps extends IconProps {
|
||||
viewBox: string;
|
||||
|
@ -55,7 +55,7 @@ export function LinkIcon({ height = 24, width = 24, className }: IconProps) {
|
|||
className={classes(className, "vc-link-icon")}
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<g fill="none" fillRule="evenodd">
|
||||
<path fill="currentColor" d="M10.59 13.41c.41.39.41 1.03 0 1.42-.39.39-1.03.39-1.42 0a5.003 5.003 0 0 1 0-7.07l3.54-3.54a5.003 5.003 0 0 1 7.07 0 5.003 5.003 0 0 1 0 7.07l-1.49 1.49c.01-.82-.12-1.64-.4-2.42l.47-.48a2.982 2.982 0 0 0 0-4.24 2.982 2.982 0 0 0-4.24 0l-3.53 3.53a2.982 2.982 0 0 0 0 4.24zm2.82-4.24c.39-.39 1.03-.39 1.42 0a5.003 5.003 0 0 1 0 7.07l-3.54 3.54a5.003 5.003 0 0 1-7.07 0 5.003 5.003 0 0 1 0-7.07l1.49-1.49c-.01.82.12 1.64.4 2.43l-.47.47a2.982 2.982 0 0 0 0 4.24 2.982 2.982 0 0 0 4.24 0l3.53-3.53a2.982 2.982 0 0 0 0-4.24.973.973 0 0 1 0-1.42z" />
|
||||
<rect width={width} height={height} />
|
||||
</g>
|
||||
|
@ -122,8 +122,8 @@ export function InfoIcon(props: IconProps) {
|
|||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
fill-rule="evenodd"
|
||||
d="M23 12a11 11 0 1 1-22 0 11 11 0 0 1 22 0Zm-9.5-4.75a1.25 1.25 0 1 1-2.5 0 1.25 1.25 0 0 1 2.5 0Zm-.77 3.96a1 1 0 1 0-1.96-.42l-1.04 4.86a2.77 2.77 0 0 0 4.31 2.83l.24-.17a1 1 0 1 0-1.16-1.62l-.24.17a.77.77 0 0 1-1.2-.79l1.05-4.86Z" clip-rule="evenodd"
|
||||
fillRule="evenodd"
|
||||
d="M23 12a11 11 0 1 1-22 0 11 11 0 0 1 22 0Zm-9.5-4.75a1.25 1.25 0 1 1-2.5 0 1.25 1.25 0 0 1 2.5 0Zm-.77 3.96a1 1 0 1 0-1.96-.42l-1.04 4.86a2.77 2.77 0 0 0 4.31 2.83l.24-.17a1 1 0 1 0-1.16-1.62l-.24.17a.77.77 0 0 1-1.2-.79l1.05-4.86Z" clipRule="evenodd"
|
||||
/>
|
||||
</Icon>
|
||||
);
|
||||
|
@ -212,9 +212,9 @@ export function CogWheel(props: IconProps) {
|
|||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
fill-rule="evenodd"
|
||||
fillRule="evenodd"
|
||||
d="M10.56 1.1c-.46.05-.7.53-.64.98.18 1.16-.19 2.2-.98 2.53-.8.33-1.79-.15-2.49-1.1-.27-.36-.78-.52-1.14-.24-.77.59-1.45 1.27-2.04 2.04-.28.36-.12.87.24 1.14.96.7 1.43 1.7 1.1 2.49-.33.8-1.37 1.16-2.53.98-.45-.07-.93.18-.99.64a11.1 11.1 0 0 0 0 2.88c.06.46.54.7.99.64 1.16-.18 2.2.19 2.53.98.33.8-.14 1.79-1.1 2.49-.36.27-.52.78-.24 1.14.59.77 1.27 1.45 2.04 2.04.36.28.87.12 1.14-.24.7-.95 1.7-1.43 2.49-1.1.8.33 1.16 1.37.98 2.53-.07.45.18.93.64.99a11.1 11.1 0 0 0 2.88 0c.46-.06.7-.54.64-.99-.18-1.16.19-2.2.98-2.53.8-.33 1.79.14 2.49 1.1.27.36.78.52 1.14.24.77-.59 1.45-1.27 2.04-2.04.28-.36.12-.87-.24-1.14-.96-.7-1.43-1.7-1.1-2.49.33-.8 1.37-1.16 2.53-.98.45.07.93-.18.99-.64a11.1 11.1 0 0 0 0-2.88c-.06-.46-.54-.7-.99-.64-1.16.18-2.2-.19-2.53-.98-.33-.8.14-1.79 1.1-2.49.36-.27.52-.78.24-1.14a11.07 11.07 0 0 0-2.04-2.04c-.36-.28-.87-.12-1.14.24-.7.96-1.7 1.43-2.49 1.1-.8-.33-1.16-1.37-.98-2.53.07-.45-.18-.93-.64-.99a11.1 11.1 0 0 0-2.88 0ZM16 12a4 4 0 1 1-8 0 4 4 0 0 1 8 0Z"
|
||||
clip-rule="evenodd"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</Icon>
|
||||
);
|
||||
|
@ -262,7 +262,7 @@ export function PlusIcon(props: IconProps) {
|
|||
viewBox="0 0 18 18"
|
||||
>
|
||||
<polygon
|
||||
fill-rule="nonzero"
|
||||
fillRule="nonzero"
|
||||
fill="currentColor"
|
||||
points="15 10 10 10 10 15 8 15 8 10 3 10 3 8 8 8 8 3 10 3 10 8 15 8"
|
||||
/>
|
||||
|
|
|
@ -44,7 +44,7 @@ function ContributorModal({ user }: { user: User; }) {
|
|||
useEffect(() => {
|
||||
if (!profile && !user.bot && user.id)
|
||||
fetchUserProfile(user.id);
|
||||
}, [user.id]);
|
||||
}, [user.id, user.bot, profile]);
|
||||
|
||||
const githubName = profile?.connectedAccounts?.find(a => a.type === "github")?.name;
|
||||
const website = profile?.connectedAccounts?.find(a => a.type === "domain")?.name;
|
||||
|
|
|
@ -81,7 +81,8 @@ const Components: Record<OptionType, React.ComponentType<ISettingElementProps<an
|
|||
[OptionType.BOOLEAN]: SettingBooleanComponent,
|
||||
[OptionType.SELECT]: SettingSelectComponent,
|
||||
[OptionType.SLIDER]: SettingSliderComponent,
|
||||
[OptionType.COMPONENT]: SettingCustomComponent
|
||||
[OptionType.COMPONENT]: SettingCustomComponent,
|
||||
[OptionType.CUSTOM]: () => null,
|
||||
};
|
||||
|
||||
export default function PluginModal({ plugin, onRestartNeeded, onClose, transitionState }: PluginModalProps) {
|
||||
|
@ -109,7 +110,7 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
|
|||
setAuthors(a => [...a, author]);
|
||||
}
|
||||
})();
|
||||
}, []);
|
||||
}, [plugin.authors]);
|
||||
|
||||
async function saveAndClose() {
|
||||
if (!plugin.options) {
|
||||
|
@ -129,7 +130,8 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
|
|||
for (const [key, value] of Object.entries(tempSettings)) {
|
||||
const option = plugin.options[key];
|
||||
pluginSettings[key] = value;
|
||||
option?.onChange?.(value);
|
||||
|
||||
if (option.type === OptionType.CUSTOM) continue;
|
||||
if (option?.restartNeeded) restartNeeded = true;
|
||||
}
|
||||
if (restartNeeded) onRestartNeeded();
|
||||
|
@ -141,7 +143,7 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
|
|||
return <Forms.FormText>There are no settings for this plugin.</Forms.FormText>;
|
||||
} else {
|
||||
const options = Object.entries(plugin.options).map(([key, setting]) => {
|
||||
if (setting.hidden) return null;
|
||||
if (setting.type === OptionType.CUSTOM || setting.hidden) return null;
|
||||
|
||||
function onChange(newValue: any) {
|
||||
setTempSettings(s => ({ ...s, [key]: newValue }));
|
||||
|
|
|
@ -35,6 +35,7 @@ import { useAwaiter } from "@utils/react";
|
|||
import { Plugin } from "@utils/types";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { Alerts, Button, Card, Forms, lodash, Parser, React, Select, Text, TextInput, Toasts, Tooltip, useMemo } from "@webpack/common";
|
||||
import { JSX } from "react";
|
||||
|
||||
import Plugins, { ExcludedPlugins } from "~plugins";
|
||||
|
||||
|
@ -387,7 +388,7 @@ function makeDependencyList(deps: string[]) {
|
|||
return (
|
||||
<React.Fragment>
|
||||
<Forms.FormText>This plugin is required by:</Forms.FormText>
|
||||
{deps.map((dep: string) => <Forms.FormText className={cl("dep-text")}>{dep}</Forms.FormText>)}
|
||||
{deps.map((dep: string) => <Forms.FormText key={dep} className={cl("dep-text")}>{dep}</Forms.FormText>)}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -111,9 +111,9 @@ function ReplacementComponent({ module, match, replacement, setReplacementError
|
|||
}
|
||||
|
||||
function renderDiff() {
|
||||
return diff?.map(p => {
|
||||
return diff?.map((p, idx) => {
|
||||
const color = p.added ? "lime" : p.removed ? "red" : "grey";
|
||||
return <div style={{ color, userSelect: "text", wordBreak: "break-all", lineBreak: "anywhere" }}>{p.value}</div>;
|
||||
return <div key={idx} style={{ color, userSelect: "text", wordBreak: "break-all", lineBreak: "anywhere" }}>{p.value}</div>;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -61,7 +61,7 @@ function withDispatcher(dispatcher: React.Dispatch<React.SetStateAction<boolean>
|
|||
title: "Oops!",
|
||||
body: (
|
||||
<ErrorCard>
|
||||
{err.split("\n").map(line => <div>{Parser.parse(line)}</div>)}
|
||||
{err.split("\n").map((line, idx) => <div key={idx}>{Parser.parse(line)}</div>)}
|
||||
</ErrorCard>
|
||||
)
|
||||
});
|
||||
|
@ -87,7 +87,7 @@ function Changes({ updates, repo, repoPending }: CommonProps & { updates: typeof
|
|||
return (
|
||||
<Card style={{ padding: "0 0.5em" }}>
|
||||
{updates.map(({ hash, author, message }) => (
|
||||
<div style={{
|
||||
<div key={hash} style={{
|
||||
marginTop: "0.5em",
|
||||
marginBottom: "0.5em"
|
||||
}}>
|
||||
|
|
|
@ -79,7 +79,7 @@ export default definePlugin({
|
|||
replace: "...$1.props,$& $1.image??"
|
||||
},
|
||||
{
|
||||
match: /(?<=text:(\i)\.description,.{0,200})children:/,
|
||||
match: /(?<="aria-label":(\i)\.description,.{0,200})children:/,
|
||||
replace: "children:$1.component ? $self.renderBadgeComponent({ ...$1 }) :"
|
||||
},
|
||||
// conditionally override their onClick with badge.onClick if it exists
|
||||
|
@ -102,8 +102,9 @@ export default definePlugin({
|
|||
}
|
||||
},
|
||||
|
||||
userProfileBadge: ContributorBadge,
|
||||
|
||||
async start() {
|
||||
Vencord.Api.Badges.addBadge(ContributorBadge);
|
||||
await loadBadges();
|
||||
},
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ export default definePlugin({
|
|||
replace: (match, args) => "" +
|
||||
`async ${match}` +
|
||||
`if(await Vencord.Api.MessageEvents._handlePreEdit(${args}))` +
|
||||
"return Promise.resolve({shoudClear:true,shouldRefocus:true});"
|
||||
"return Promise.resolve({shouldClear:false,shouldRefocus:true});"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -39,12 +39,12 @@ export default definePlugin({
|
|||
replacement: {
|
||||
// props.chatInputType...then((function(isMessageValid)... var parsedMessage = b.c.parse(channel,... var replyOptions = f.g.getSendMessageOptionsForReply(pendingReply);
|
||||
// Lookbehind: validateMessage)({openWarningPopout:..., type: i.props.chatInputType, content: t, stickers: r, ...}).then((function(isMessageValid)
|
||||
match: /(type:this\.props\.chatInputType.+?\.then\()(\i=>\{.+?let (\i)=\i\.\i\.parse\((\i),.+?let (\i)=\i\.\i\.getSendMessageOptionsForReply\(\i\);)(?<=\)\(({.+?})\)\.then.+?)/,
|
||||
match: /(\{openWarningPopout:.{0,100}type:this.props.chatInputType.+?\.then\()(\i=>\{.+?let (\i)=\i\.\i\.parse\((\i),.+?let (\i)=\i\.\i\.getSendMessageOptions\(\{.+?\}\);)(?<=\)\(({.+?})\)\.then.+?)/,
|
||||
// props.chatInputType...then((async function(isMessageValid)... var replyOptions = f.g.getSendMessageOptionsForReply(pendingReply); if(await Vencord.api...) return { shoudClear:true, shouldRefocus:true };
|
||||
replace: (_, rest1, rest2, parsedMessage, channel, replyOptions, extra) => "" +
|
||||
`${rest1}async ${rest2}` +
|
||||
`if(await Vencord.Api.MessageEvents._handlePreSend(${channel}.id,${parsedMessage},${extra},${replyOptions}))` +
|
||||
"return{shoudClear:true,shouldRefocus:true};"
|
||||
"return{shouldClear:false,shouldRefocus:true};"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
@ -27,9 +27,9 @@ export default definePlugin({
|
|||
{
|
||||
find: "#{intl::MESSAGE_UTILITIES_A11Y_LABEL}",
|
||||
replacement: {
|
||||
match: /(?<=:null),(.{0,40}togglePopout:.+?}\))\]}\):null,(?<=\((\i\.\i),{label:.+?:null,(\i&&!\i)\?\(0,\i\.jsxs?\)\(\i\.Fragment.+?message:(\i).+?)/,
|
||||
replace: (_, ReactButton, ButtonComponent, showReactButton, message) => "" +
|
||||
`]}):null,Vencord.Api.MessagePopover._buildPopoverElements(${ButtonComponent},${message}),${showReactButton}?${ReactButton}:null,`
|
||||
match: /(?<=:null),(.{0,40}togglePopout:.+?}\)),(.+?)\]}\):null,(?<=\((\i\.\i),{label:.+?:null,(\i&&!\i)\?\(0,\i\.jsxs?\)\(\i\.Fragment.+?message:(\i).+?)/,
|
||||
replace: (_, ReactButton, PotionButton, ButtonComponent, showReactButton, message) => "" +
|
||||
`]}):null,Vencord.Api.MessagePopover._buildPopoverElements(${ButtonComponent},${message}),${showReactButton}?${ReactButton}:null,${showReactButton}&&${PotionButton},`
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
@ -34,7 +34,7 @@ export default definePlugin({
|
|||
},
|
||||
{
|
||||
match: /(?<=,NOTICE_DISMISS:function\(\i\){)return null!=(\i)/,
|
||||
replace: "if($1.id==\"VencordNotice\")return($1=null,Vencord.Api.Notices.nextNotice(),true);$&"
|
||||
replace: "if($1?.id==\"VencordNotice\")return($1=null,Vencord.Api.Notices.nextNotice(),true);$&"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { addAccessory } from "@api/MessageAccessories";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { getUserSettingLazy } from "@api/UserSettings";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
|
@ -34,6 +33,7 @@ import { makeCodeblock } from "@utils/text";
|
|||
import definePlugin from "@utils/types";
|
||||
import { checkForUpdates, isOutdated, update } from "@utils/updater";
|
||||
import { Alerts, Button, Card, ChannelStore, Forms, GuildMemberStore, Parser, RelationshipStore, showToast, Text, Toasts, UserStore } from "@webpack/common";
|
||||
import { JSX } from "react";
|
||||
|
||||
import gitHash from "~git-hash";
|
||||
import plugins, { PluginMeta } from "~plugins";
|
||||
|
@ -142,7 +142,7 @@ export default definePlugin({
|
|||
required: true,
|
||||
description: "Helps us provide support to you",
|
||||
authors: [Devs.Ven],
|
||||
dependencies: ["UserSettingsAPI", "MessageAccessoriesAPI"],
|
||||
dependencies: ["UserSettingsAPI"],
|
||||
|
||||
settings,
|
||||
|
||||
|
@ -235,23 +235,7 @@ export default definePlugin({
|
|||
}
|
||||
},
|
||||
|
||||
renderContributorDmWarningCard: ErrorBoundary.wrap(({ channel }) => {
|
||||
const userId = channel.getRecipientId();
|
||||
if (!isPluginDev(userId)) return null;
|
||||
if (RelationshipStore.isFriend(userId) || isPluginDev(UserStore.getCurrentUser()?.id)) return null;
|
||||
|
||||
return (
|
||||
<Card className={`vc-plugins-restart-card ${Margins.top8}`}>
|
||||
Please do not private message Vencord plugin developers for support!
|
||||
<br />
|
||||
Instead, use the Vencord support channel: {Parser.parse("https://discord.com/channels/1015060230222131221/1026515880080842772")}
|
||||
{!ChannelStore.getChannel(SUPPORT_CHANNEL_ID) && " (Click the link to join)"}
|
||||
</Card>
|
||||
);
|
||||
}, { noop: true }),
|
||||
|
||||
start() {
|
||||
addAccessory("vencord-debug", props => {
|
||||
renderMessageAccessory(props) {
|
||||
const buttons = [] as JSX.Element[];
|
||||
|
||||
const shouldAddUpdateButton =
|
||||
|
@ -328,6 +312,20 @@ export default definePlugin({
|
|||
return buttons.length
|
||||
? <Flex>{buttons}</Flex>
|
||||
: null;
|
||||
});
|
||||
},
|
||||
|
||||
renderContributorDmWarningCard: ErrorBoundary.wrap(({ channel }) => {
|
||||
const userId = channel.getRecipientId();
|
||||
if (!isPluginDev(userId)) return null;
|
||||
if (RelationshipStore.isFriend(userId) || isPluginDev(UserStore.getCurrentUser()?.id)) return null;
|
||||
|
||||
return (
|
||||
<Card className={`vc-plugins-restart-card ${Margins.top8}`}>
|
||||
Please do not private message Vencord plugin developers for support!
|
||||
<br />
|
||||
Instead, use the Vencord support channel: {Parser.parse("https://discord.com/channels/1015060230222131221/1026515880080842772")}
|
||||
{!ChannelStore.getChannel(SUPPORT_CHANNEL_ID) && " (Click the link to join)"}
|
||||
</Card>
|
||||
);
|
||||
}, { noop: true }),
|
||||
});
|
||||
|
|
|
@ -16,14 +16,14 @@ import { User } from "discord-types/general";
|
|||
interface UserProfileProps {
|
||||
popoutProps: Record<string, any>;
|
||||
currentUser: User;
|
||||
originalPopout: () => React.ReactNode;
|
||||
originalRenderPopout: () => React.ReactNode;
|
||||
}
|
||||
|
||||
const UserProfile = findComponentByCodeLazy("UserProfilePopoutWrapper: user cannot be undefined");
|
||||
const styles = findByPropsLazy("accountProfilePopoutWrapper");
|
||||
|
||||
let openAlternatePopout = false;
|
||||
let accountPanelRef: React.MutableRefObject<Record<PropertyKey, any> | null> = { current: null };
|
||||
let accountPanelRef: React.RefObject<Record<PropertyKey, any> | null> = { current: null };
|
||||
|
||||
const AccountPanelContextMenu = ErrorBoundary.wrap(() => {
|
||||
const { prioritizeServerProfile } = settings.use(["prioritizeServerProfile"]);
|
||||
|
@ -73,12 +73,12 @@ export default definePlugin({
|
|||
group: true,
|
||||
replacement: [
|
||||
{
|
||||
match: /(?<=\.SIZE_32\)}\);)/,
|
||||
match: /(?<=\.AVATAR_SIZE\);)/,
|
||||
replace: "$self.useAccountPanelRef();"
|
||||
},
|
||||
{
|
||||
match: /(\.AVATAR,children:.+?renderPopout:(\i)=>){(.+?)}(?=,position)(?<=currentUser:(\i).+?)/,
|
||||
replace: (_, rest, popoutProps, originalPopout, currentUser) => `${rest}$self.UserProfile({popoutProps:${popoutProps},currentUser:${currentUser},originalPopout:()=>{${originalPopout}}})`
|
||||
replace: (_, rest, popoutProps, originalPopout, currentUser) => `${rest}$self.UserProfile({popoutProps:${popoutProps},currentUser:${currentUser},originalRenderPopout:()=>{${originalPopout}}})`
|
||||
},
|
||||
{
|
||||
match: /\.AVATAR,children:.+?(?=renderPopout:)/,
|
||||
|
@ -112,17 +112,17 @@ export default definePlugin({
|
|||
openAlternatePopout = false;
|
||||
},
|
||||
|
||||
UserProfile: ErrorBoundary.wrap(({ popoutProps, currentUser, originalPopout }: UserProfileProps) => {
|
||||
UserProfile: ErrorBoundary.wrap(({ popoutProps, currentUser, originalRenderPopout }: UserProfileProps) => {
|
||||
if (
|
||||
(settings.store.prioritizeServerProfile && openAlternatePopout) ||
|
||||
(!settings.store.prioritizeServerProfile && !openAlternatePopout)
|
||||
) {
|
||||
return originalPopout();
|
||||
return originalRenderPopout();
|
||||
}
|
||||
|
||||
const currentChannel = getCurrentChannel();
|
||||
if (currentChannel?.getGuildId() == null) {
|
||||
return originalPopout();
|
||||
return originalRenderPopout();
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -41,7 +41,7 @@ export default definePlugin({
|
|||
},
|
||||
{
|
||||
// Status emojis
|
||||
find: "#{intl::GUILD_OWNER}",
|
||||
find: "#{intl::GUILD_OWNER}),children:",
|
||||
replacement: {
|
||||
match: /(?<=\.activityEmoji,.+?animate:)\i/,
|
||||
replace: "!0"
|
||||
|
|
|
@ -123,7 +123,7 @@ export default definePlugin({
|
|||
},
|
||||
// If we are rendering the Better Folders sidebar, we filter out everything but the servers and folders from the GuildsBar Guild List children
|
||||
{
|
||||
match: /lastTargetNode:\i\[\i\.length-1\].+?}\)\](?=}\))/,
|
||||
match: /lastTargetNode:\i\[\i\.length-1\].+?}\)(?::null)?\](?=}\))/,
|
||||
replace: "$&.filter($self.makeGuildsBarGuildListFilter(!!arguments[0]?.isBetterFolders))"
|
||||
},
|
||||
// If we are rendering the Better Folders sidebar, we filter out everything but the scroller for the guild list from the GuildsBar Tree children
|
||||
|
|
|
@ -99,7 +99,7 @@ export const UnknownIcon = (props: React.PropsWithChildren<SVGProps<SVGSVGElemen
|
|||
fill="currentColor"
|
||||
viewBox="0 0 16 16"
|
||||
>
|
||||
<path fill-rule="evenodd" d="M4.475 5.458c-.284 0-.514-.237-.47-.517C4.28 3.24 5.576 2 7.825 2c2.25 0 3.767 1.36 3.767 3.215 0 1.344-.665 2.288-1.79 2.973-1.1.659-1.414 1.118-1.414 2.01v.03a.5.5 0 0 1-.5.5h-.77a.5.5 0 0 1-.5-.495l-.003-.2c-.043-1.221.477-2.001 1.645-2.712 1.03-.632 1.397-1.135 1.397-2.028 0-.979-.758-1.698-1.926-1.698-1.009 0-1.71.529-1.938 1.402-.066.254-.278.461-.54.461h-.777ZM7.496 14c.622 0 1.095-.474 1.095-1.09 0-.618-.473-1.092-1.095-1.092-.606 0-1.087.474-1.087 1.091S6.89 14 7.496 14Z" />
|
||||
<path fillRule="evenodd" d="M4.475 5.458c-.284 0-.514-.237-.47-.517C4.28 3.24 5.576 2 7.825 2c2.25 0 3.767 1.36 3.767 3.215 0 1.344-.665 2.288-1.79 2.973-1.1.659-1.414 1.118-1.414 2.01v.03a.5.5 0 0 1-.5.5h-.77a.5.5 0 0 1-.5-.495l-.003-.2c-.043-1.221.477-2.001 1.645-2.712 1.03-.632 1.397-1.135 1.397-2.028 0-.979-.758-1.698-1.926-1.698-1.009 0-1.71.529-1.938 1.402-.066.254-.278.461-.54.461h-.777ZM7.496 14c.622 0 1.095-.474 1.095-1.09 0-.618-.473-1.092-1.095-1.092-.606 0-1.087.474-1.087 1.091S6.89 14 7.496 14Z" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
|
|
|
@ -114,7 +114,7 @@ export default definePlugin({
|
|||
{ // Load menu TOC eagerly
|
||||
find: "#{intl::USER_SETTINGS_WITH_BUILD_OVERRIDE}",
|
||||
replacement: {
|
||||
match: /(\i)\(this,"handleOpenSettingsContextMenu",.{0,100}?null!=\i&&.{0,100}?(await Promise\.all[^};]*?\)\)).*?,(?=\1\(this)/,
|
||||
match: /(\i)\(this,"handleOpenSettingsContextMenu",.{0,100}?null!=\i&&.{0,100}?(await [^};]*?\)\)).*?,(?=\1\(this)/,
|
||||
replace: "$&(async ()=>$2)(),"
|
||||
},
|
||||
predicate: () => settings.store.eagerLoad
|
||||
|
@ -173,7 +173,7 @@ export default definePlugin({
|
|||
}
|
||||
return this;
|
||||
},
|
||||
map(render: (item: SettingsEntry) => ReactElement) {
|
||||
map(render: (item: SettingsEntry) => ReactElement<any>) {
|
||||
return items
|
||||
.filter(a => a.items.length > 0)
|
||||
.map(({ label, items }) => {
|
||||
|
@ -181,11 +181,14 @@ export default definePlugin({
|
|||
if (label) {
|
||||
return (
|
||||
<Menu.MenuItem
|
||||
key={label}
|
||||
id={label.replace(/\W/, "_")}
|
||||
label={label}
|
||||
children={children}
|
||||
action={children[0].props.action}
|
||||
/>);
|
||||
>
|
||||
{children}
|
||||
</Menu.MenuItem>
|
||||
);
|
||||
} else {
|
||||
return children;
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ export default definePlugin({
|
|||
{
|
||||
find: '"ChannelAttachButton"',
|
||||
replacement: {
|
||||
match: /\.attachButtonInner,"aria-label":.{0,50},onDoubleClick:(.+?:void 0),\.\.\.(\i),/,
|
||||
match: /\.attachButtonInner,"aria-label":.{0,50},onDoubleClick:(.+?:void 0),.{0,30}?\.\.\.(\i),/,
|
||||
replace: "$&onClick:$1,onContextMenu:$2.onClick,",
|
||||
},
|
||||
},
|
||||
|
|
|
@ -75,8 +75,8 @@ export default definePlugin({
|
|||
patches: [{
|
||||
find: "renderConnectionStatus(){",
|
||||
replacement: {
|
||||
match: /(?<=renderConnectionStatus\(\){.+\.channel,children:).+?}\):\i(?=}\))/,
|
||||
replace: "[$&, $self.renderTimer(this.props.channel.id)]"
|
||||
match: /(renderConnectionStatus\(\){.+\.channel,children:)(.+?}\):\i)(?=}\))/,
|
||||
replace: "$1[$2,$self.renderTimer(this.props.channel.id)]"
|
||||
}
|
||||
}],
|
||||
|
||||
|
|
|
@ -17,11 +17,7 @@
|
|||
*/
|
||||
|
||||
import {
|
||||
addPreEditListener,
|
||||
addPreSendListener,
|
||||
MessageObject,
|
||||
removePreEditListener,
|
||||
removePreSendListener
|
||||
MessageObject
|
||||
} from "@api/MessageEvents";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
|
@ -36,7 +32,18 @@ export default definePlugin({
|
|||
name: "ClearURLs",
|
||||
description: "Removes tracking garbage from URLs",
|
||||
authors: [Devs.adryd],
|
||||
dependencies: ["MessageEventsAPI"],
|
||||
|
||||
start() {
|
||||
this.createRules();
|
||||
},
|
||||
|
||||
onBeforeMessageSend(_, msg) {
|
||||
return this.onSend(msg);
|
||||
},
|
||||
|
||||
onBeforeMessageEdit(_cid, _mid, msg) {
|
||||
return this.onSend(msg);
|
||||
},
|
||||
|
||||
escapeRegExp(str: string) {
|
||||
return (str && reHasRegExpChar.test(str))
|
||||
|
@ -133,17 +140,4 @@ export default definePlugin({
|
|||
);
|
||||
}
|
||||
},
|
||||
|
||||
start() {
|
||||
this.createRules();
|
||||
this.preSend = addPreSendListener((_, msg) => this.onSend(msg));
|
||||
this.preEdit = addPreEditListener((_cid, _mid, msg) =>
|
||||
this.onSend(msg)
|
||||
);
|
||||
},
|
||||
|
||||
stop() {
|
||||
removePreSendListener(this.preSend);
|
||||
removePreEditListener(this.preEdit);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -95,10 +95,9 @@ export default definePlugin({
|
|||
}
|
||||
},
|
||||
{
|
||||
find: 'console.warn("[DEPRECATED] Please use `subscribeWithSelector` middleware");',
|
||||
all: true,
|
||||
find: '"AppCrashedFatalReport: getLastCrash not supported."',
|
||||
replacement: {
|
||||
match: /console\.warn\("\[DEPRECATED\] Please use `subscribeWithSelector` middleware"\);/,
|
||||
match: /console\.log\("AppCrashedFatalReport: getLastCrash not supported\."\);/,
|
||||
replace: ""
|
||||
}
|
||||
},
|
||||
|
|
|
@ -133,7 +133,10 @@ function makeShortcuts() {
|
|||
});
|
||||
}
|
||||
|
||||
Common.ReactDOM.render(Common.React.createElement(component, props), doc.body.appendChild(document.createElement("div")));
|
||||
const root = Common.ReactDOM.createRoot(doc.body.appendChild(document.createElement("div")));
|
||||
root.render(Common.React.createElement(component, props));
|
||||
|
||||
doc.addEventListener("close", () => root.unmount(), { once: true });
|
||||
},
|
||||
|
||||
preEnable: (plugin: string) => (Vencord.Settings.plugins[plugin] ??= { enabled: true }).enabled = true,
|
||||
|
@ -176,6 +179,16 @@ export default definePlugin({
|
|||
description: "Adds shorter Aliases for many things on the window. Run `shortcutList` for a list.",
|
||||
authors: [Devs.Ven],
|
||||
|
||||
patches: [
|
||||
{
|
||||
find: 'this,"_changeCallbacks",',
|
||||
replacement: {
|
||||
match: /\i\(this,"_changeCallbacks",/,
|
||||
replace: "Reflect.defineProperty(this,Symbol.toStringTag,{value:this.getName(),configurable:!0,writable:!0,enumerable:!1}),$&"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
startAt: StartAt.Init,
|
||||
start() {
|
||||
const shortcuts = makeShortcuts();
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
import { definePluginSettings, Settings } from "@api/Settings";
|
||||
import { getUserSettingLazy } from "@api/UserSettings";
|
||||
import { ErrorCard } from "@components/ErrorCard";
|
||||
import { Flex } from "@components/Flex";
|
||||
import { Link } from "@components/Link";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { isTruthy } from "@utils/guards";
|
||||
|
@ -27,15 +28,14 @@ import { classes } from "@utils/misc";
|
|||
import { useAwaiter } from "@utils/react";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { findByCodeLazy, findComponentByCodeLazy } from "@webpack";
|
||||
import { ApplicationAssetUtils, Button, FluxDispatcher, Forms, GuildStore, React, SelectedChannelStore, SelectedGuildStore, UserStore } from "@webpack/common";
|
||||
import { ApplicationAssetUtils, Button, FluxDispatcher, Forms, React, UserStore } from "@webpack/common";
|
||||
|
||||
const useProfileThemeStyle = findByCodeLazy("profileThemeStyle:", "--profile-gradient-primary-color");
|
||||
const ActivityComponent = findComponentByCodeLazy("onOpenGameProfile");
|
||||
const ActivityView = findComponentByCodeLazy(".party?(0", ".card");
|
||||
|
||||
const ShowCurrentGame = getUserSettingLazy<boolean>("status", "showCurrentGame")!;
|
||||
|
||||
async function getApplicationAsset(key: string): Promise<string> {
|
||||
if (/https?:\/\/(cdn|media)\.discordapp\.(com|net)\/attachments\//.test(key)) return "mp:" + key.replace(/https?:\/\/(cdn|media)\.discordapp\.(com|net)\//, "");
|
||||
return (await ApplicationAssetUtils.fetchAssetIds(settings.store.appID!, [key]))[0];
|
||||
}
|
||||
|
||||
|
@ -169,7 +169,7 @@ const settings = definePluginSettings({
|
|||
value: TimestampMode.NOW
|
||||
},
|
||||
{
|
||||
label: "Same as your current time",
|
||||
label: "Same as your current time (not reset after 24h)",
|
||||
value: TimestampMode.TIME
|
||||
},
|
||||
{
|
||||
|
@ -269,6 +269,7 @@ function isStreamLinkDisabled() {
|
|||
|
||||
function isStreamLinkValid(value: string) {
|
||||
if (!isStreamLinkDisabled() && !/https?:\/\/(www\.)?(twitch\.tv|youtube\.com)\/\w+/.test(value)) return "Streaming link must be a valid URL.";
|
||||
if (value && value.length > 512) return "Streaming link must be not longer than 512 characters.";
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -277,8 +278,9 @@ function isTimestampDisabled() {
|
|||
}
|
||||
|
||||
function isImageKeyValid(value: string) {
|
||||
if (/https?:\/\/(?!i\.)?imgur\.com\//.test(value)) return "Imgur link must be a direct link to the image. (e.g. https://i.imgur.com/...)";
|
||||
if (/https?:\/\/(?!media\.)?tenor\.com\//.test(value)) return "Tenor link must be a direct link to the image. (e.g. https://media.tenor.com/...)";
|
||||
if (/https?:\/\/(cdn|media)\.discordapp\.(com|net)\//.test(value)) return "Don't use a Discord link. Use an Imgur image link instead.";
|
||||
if (/https?:\/\/(?!i\.)?imgur\.com\//.test(value)) return "Imgur link must be a direct link to the image (e.g. https://i.imgur.com/...). Right click the image and click 'Copy image address'";
|
||||
if (/https?:\/\/(?!media\.)?tenor\.com\//.test(value)) return "Tenor link must be a direct link to the image (e.g. https://media.tenor.com/...). Right click the GIF and click 'Copy image address'";
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -390,13 +392,24 @@ async function setRpc(disable?: boolean) {
|
|||
|
||||
export default definePlugin({
|
||||
name: "CustomRPC",
|
||||
description: "Allows you to set a custom rich presence.",
|
||||
description: "Add a fully customisable Rich Presence (Game status) to your Discord profile",
|
||||
authors: [Devs.captain, Devs.AutumnVN, Devs.nin0dev],
|
||||
dependencies: ["UserSettingsAPI"],
|
||||
start: setRpc,
|
||||
stop: () => setRpc(true),
|
||||
settings,
|
||||
|
||||
patches: [
|
||||
{
|
||||
find: ".party?(0",
|
||||
all: true,
|
||||
replacement: {
|
||||
match: /\i\.id===\i\.id\?null:/,
|
||||
replace: ""
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
settingsAboutComponent: () => {
|
||||
const activity = useAwaiter(createActivity);
|
||||
const gameActivityEnabled = ShowCurrentGame.useSetting();
|
||||
|
@ -410,7 +423,7 @@ export default definePlugin({
|
|||
style={{ padding: "1em" }}
|
||||
>
|
||||
<Forms.FormTitle>Notice</Forms.FormTitle>
|
||||
<Forms.FormText>Game activity isn't enabled, people won't be able to see your custom rich presence!</Forms.FormText>
|
||||
<Forms.FormText>Activity Sharing isn't enabled, people won't be able to see your custom rich presence!</Forms.FormText>
|
||||
|
||||
<Button
|
||||
color={Button.Colors.TRANSPARENT}
|
||||
|
@ -422,24 +435,33 @@ export default definePlugin({
|
|||
</ErrorCard>
|
||||
)}
|
||||
|
||||
<Flex flexDirection="column" style={{ gap: ".5em" }} className={Margins.top16}>
|
||||
<Forms.FormText>
|
||||
Go to <Link href="https://discord.com/developers/applications">Discord Developer Portal</Link> to create an application and
|
||||
Go to the <Link href="https://discord.com/developers/applications">Discord Developer Portal</Link> to create an application and
|
||||
get the application ID.
|
||||
</Forms.FormText>
|
||||
<Forms.FormText>
|
||||
Upload images in the Rich Presence tab to get the image keys.
|
||||
</Forms.FormText>
|
||||
<Forms.FormText>
|
||||
If you want to use image link, download your image and reupload the image to <Link href="https://imgur.com">Imgur</Link> and get the image link by right-clicking the image and select "Copy image address".
|
||||
If you want to use an image link, download your image and reupload the image to <Link href="https://imgur.com">Imgur</Link> and get the image link by right-clicking the image and selecting "Copy image address".
|
||||
</Forms.FormText>
|
||||
<Forms.FormText>
|
||||
You can't see your own buttons on your profile, but everyone else can see it fine.
|
||||
</Forms.FormText>
|
||||
<Forms.FormText>
|
||||
Some weird unicode text ("fonts" 𝖑𝖎𝖐𝖊 𝖙𝖍𝖎𝖘) may cause the rich presence to not show up, try using normal letters instead.
|
||||
</Forms.FormText>
|
||||
</Flex>
|
||||
|
||||
<Forms.FormDivider className={Margins.top8} />
|
||||
|
||||
<div style={{ width: "284px", ...profileThemeStyle, padding: 8, marginTop: 8, borderRadius: 8, background: "var(--bg-mod-faint)" }}>
|
||||
{activity[0] && <ActivityComponent activity={activity[0]} channelId={SelectedChannelStore.getChannelId()}
|
||||
guild={GuildStore.getGuild(SelectedGuildStore.getLastSelectedGuildId())}
|
||||
application={{ id: settings.store.appID }}
|
||||
user={UserStore.getCurrentUser()} />}
|
||||
<div style={{ width: "284px", ...profileThemeStyle, marginTop: 8, borderRadius: 8, background: "var(--bg-mod-faint)" }}>
|
||||
{activity[0] && <ActivityView
|
||||
activity={activity[0]}
|
||||
user={UserStore.getCurrentUser()}
|
||||
currentUser={UserStore.getCurrentUser()}
|
||||
/>}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
import { React } from "@webpack/common";
|
||||
import { JSX } from "react";
|
||||
|
||||
import { cl } from "../";
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
import { classes } from "@utils/misc";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { React } from "@webpack/common";
|
||||
import { JSX } from "react";
|
||||
|
||||
import { cl } from "../";
|
||||
import Grid, { GridProps } from "./Grid";
|
||||
|
|
|
@ -222,7 +222,7 @@ function CloneModal({ data }: { data: Sticker | Emoji; }) {
|
|||
alignItems: "center"
|
||||
}}>
|
||||
{guilds.map(g => (
|
||||
<Tooltip text={g.name}>
|
||||
<Tooltip key={g.id} text={g.name}>
|
||||
{({ onMouseLeave, onMouseEnter }) => (
|
||||
<div
|
||||
onMouseLeave={onMouseLeave}
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { addPreEditListener, addPreSendListener, removePreEditListener, removePreSendListener } from "@api/MessageEvents";
|
||||
import { addMessagePreEditListener, addMessagePreSendListener, removeMessagePreEditListener, removeMessagePreSendListener } from "@api/MessageEvents";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { ApngBlendOp, ApngDisposeOp, importApngJs } from "@utils/dependencies";
|
||||
|
@ -391,7 +391,7 @@ export default definePlugin({
|
|||
},
|
||||
// Separate patch for allowing using custom app icons
|
||||
{
|
||||
find: /\.getCurrentDesktopIcon.{0,25}\.isPremium/,
|
||||
find: "?24:30,",
|
||||
replacement: {
|
||||
match: /\i\.\i\.isPremium\(\i\.\i\.getCurrentUser\(\)\)/,
|
||||
replace: "true"
|
||||
|
@ -514,7 +514,7 @@ export default definePlugin({
|
|||
return array.filter(item => item != null);
|
||||
},
|
||||
|
||||
ensureChildrenIsArray(child: ReactElement) {
|
||||
ensureChildrenIsArray(child: ReactElement<any>) {
|
||||
if (!Array.isArray(child.props.children)) child.props.children = [child.props.children];
|
||||
},
|
||||
|
||||
|
@ -524,7 +524,7 @@ export default definePlugin({
|
|||
|
||||
let nextIndex = content.length;
|
||||
|
||||
const transformLinkChild = (child: ReactElement) => {
|
||||
const transformLinkChild = (child: ReactElement<any>) => {
|
||||
if (settings.store.transformEmojis) {
|
||||
const fakeNitroMatch = child.props.href.match(fakeNitroEmojiRegex);
|
||||
if (fakeNitroMatch) {
|
||||
|
@ -558,7 +558,7 @@ export default definePlugin({
|
|||
return child;
|
||||
};
|
||||
|
||||
const transformChild = (child: ReactElement) => {
|
||||
const transformChild = (child: ReactElement<any>) => {
|
||||
if (child?.props?.trusted != null) return transformLinkChild(child);
|
||||
if (child?.props?.children != null) {
|
||||
if (!Array.isArray(child.props.children)) {
|
||||
|
@ -574,7 +574,7 @@ export default definePlugin({
|
|||
return child;
|
||||
};
|
||||
|
||||
const modifyChild = (child: ReactElement) => {
|
||||
const modifyChild = (child: ReactElement<any>) => {
|
||||
const newChild = transformChild(child);
|
||||
|
||||
if (newChild?.type === "ul" || newChild?.type === "ol") {
|
||||
|
@ -601,7 +601,7 @@ export default definePlugin({
|
|||
return newChild;
|
||||
};
|
||||
|
||||
const modifyChildren = (children: Array<ReactElement>) => {
|
||||
const modifyChildren = (children: Array<ReactElement<any>>) => {
|
||||
for (const [index, child] of children.entries()) children[index] = modifyChild(child);
|
||||
|
||||
children = this.clearEmptyArrayItems(children);
|
||||
|
@ -853,7 +853,7 @@ export default definePlugin({
|
|||
});
|
||||
}
|
||||
|
||||
this.preSend = addPreSendListener(async (channelId, messageObj, extra) => {
|
||||
this.preSend = addMessagePreSendListener(async (channelId, messageObj, extra) => {
|
||||
const { guildId } = this;
|
||||
|
||||
let hasBypass = false;
|
||||
|
@ -941,7 +941,7 @@ export default definePlugin({
|
|||
return { cancel: false };
|
||||
});
|
||||
|
||||
this.preEdit = addPreEditListener(async (channelId, __, messageObj) => {
|
||||
this.preEdit = addMessagePreEditListener(async (channelId, __, messageObj) => {
|
||||
if (!s.enableEmojiBypass) return;
|
||||
|
||||
let hasBypass = false;
|
||||
|
@ -973,7 +973,7 @@ export default definePlugin({
|
|||
},
|
||||
|
||||
stop() {
|
||||
removePreSendListener(this.preSend);
|
||||
removePreEditListener(this.preEdit);
|
||||
removeMessagePreSendListener(this.preSend);
|
||||
removeMessagePreEditListener(this.preEdit);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -29,6 +29,7 @@ import definePlugin, { OptionType } from "@utils/types";
|
|||
import { extractAndLoadChunksLazy, findComponentByCodeLazy } from "@webpack";
|
||||
import { Button, Flex, Forms, React, Text, UserProfileStore, UserStore, useState } from "@webpack/common";
|
||||
import { User } from "discord-types/general";
|
||||
import { ReactElement } from "react";
|
||||
import virtualMerge from "virtual-merge";
|
||||
|
||||
interface UserProfile extends User {
|
||||
|
@ -87,7 +88,7 @@ const settings = definePluginSettings({
|
|||
|
||||
interface ColorPickerProps {
|
||||
color: number | null;
|
||||
label: React.ReactElement;
|
||||
label: ReactElement<any>;
|
||||
showEyeDropper?: boolean;
|
||||
suggestedColors?: string[];
|
||||
onChange(value: number | null): void;
|
||||
|
|
|
@ -13,7 +13,7 @@ export default definePlugin({
|
|||
authors: [Devs.Nuckyz],
|
||||
patches: [
|
||||
{
|
||||
find: "getFormatQuality(){",
|
||||
find: ".handleImageLoad)",
|
||||
replacement: {
|
||||
match: /(?<=null;return )\i\.\i&&\(\i\|\|!\i\.isAnimated.+?:(?=\i&&\(\i="png"\))/,
|
||||
replace: ""
|
||||
|
|
|
@ -27,7 +27,7 @@ export default definePlugin({
|
|||
authors: [Devs.D3SOX, Devs.Nickyux],
|
||||
patches: [
|
||||
{
|
||||
find: "#{intl::GUILD_OWNER}",
|
||||
find: "#{intl::GUILD_OWNER}),children:",
|
||||
replacement: {
|
||||
match: /,isOwner:(\i),/,
|
||||
replace: ",_isOwner:$1=$self.isGuildOwner(e),"
|
||||
|
|
|
@ -16,7 +16,7 @@ interface UserMentionComponentProps {
|
|||
id: string;
|
||||
channelId: string;
|
||||
guildId: string;
|
||||
OriginalComponent: ReactNode;
|
||||
originalComponent: () => ReactNode;
|
||||
}
|
||||
|
||||
export default definePlugin({
|
||||
|
@ -29,7 +29,7 @@ export default definePlugin({
|
|||
find: ':"text":',
|
||||
replacement: {
|
||||
match: /(hidePersonalInformation\).+?)(if\(null!=\i\){.+?return \i)(?=})/,
|
||||
replace: "$1return $self.UserMentionComponent({...arguments[0],OriginalComponent:(()=>{$2})()});"
|
||||
replace: "$1return $self.UserMentionComponent({...arguments[0],originalComponent:()=>{$2}});"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
@ -42,6 +42,6 @@ export default definePlugin({
|
|||
channelId={props.channelId}
|
||||
/>
|
||||
), {
|
||||
fallback: ({ wrappedProps }) => wrappedProps.OriginalComponent
|
||||
fallback: ({ wrappedProps: { originalComponent } }) => originalComponent()
|
||||
})
|
||||
});
|
||||
|
|
|
@ -17,11 +17,11 @@
|
|||
*/
|
||||
|
||||
import { get, set } from "@api/DataStore";
|
||||
import { addButton, removeButton } from "@api/MessagePopover";
|
||||
import { ImageInvisible, ImageVisible } from "@components/Icons";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { ChannelStore } from "@webpack/common";
|
||||
import { MessageSnapshot } from "@webpack/types";
|
||||
|
||||
let style: HTMLStyleElement;
|
||||
|
||||
|
@ -38,18 +38,14 @@ export default definePlugin({
|
|||
name: "HideAttachments",
|
||||
description: "Hide attachments and Embeds for individual messages via hover button",
|
||||
authors: [Devs.Ven],
|
||||
dependencies: ["MessagePopoverAPI"],
|
||||
|
||||
async start() {
|
||||
style = document.createElement("style");
|
||||
style.id = "VencordHideAttachments";
|
||||
document.head.appendChild(style);
|
||||
renderMessagePopoverButton(msg) {
|
||||
// @ts-ignore - discord-types lags behind discord.
|
||||
const hasAttachmentsInShapshots = msg.messageSnapshots.some(
|
||||
(snapshot: MessageSnapshot) => snapshot?.message.attachments.length
|
||||
);
|
||||
|
||||
await getHiddenMessages();
|
||||
await this.buildCss();
|
||||
|
||||
addButton("HideAttachments", msg => {
|
||||
if (!msg.attachments.length && !msg.embeds.length && !msg.stickerItems.length) return null;
|
||||
if (!msg.attachments.length && !msg.embeds.length && !msg.stickerItems.length && !hasAttachmentsInShapshots) return null;
|
||||
|
||||
const isHidden = hiddenMessages.has(msg.id);
|
||||
|
||||
|
@ -60,13 +56,20 @@ export default definePlugin({
|
|||
channel: ChannelStore.getChannel(msg.channel_id),
|
||||
onClick: () => this.toggleHide(msg.id)
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
async start() {
|
||||
style = document.createElement("style");
|
||||
style.id = "VencordHideAttachments";
|
||||
document.head.appendChild(style);
|
||||
|
||||
await getHiddenMessages();
|
||||
await this.buildCss();
|
||||
},
|
||||
|
||||
stop() {
|
||||
style.remove();
|
||||
hiddenMessages.clear();
|
||||
removeButton("HideAttachments");
|
||||
},
|
||||
|
||||
async buildCss() {
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import * as DataStore from "@api/DataStore";
|
||||
import { definePluginSettings, Settings } from "@api/Settings";
|
||||
import { getUserSettingLazy } from "@api/UserSettings";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
|
@ -62,7 +61,7 @@ const ToggleIconOff = (activity: IgnoredActivity, fill: string) => ToggleIcon(ac
|
|||
|
||||
function ToggleActivityComponent(activity: IgnoredActivity, isPlaying = false) {
|
||||
const s = settings.use(["ignoredActivities"]);
|
||||
const { ignoredActivities = [] } = s;
|
||||
const { ignoredActivities } = s;
|
||||
|
||||
if (ignoredActivities.some(act => act.id === activity.id)) return ToggleIconOff(activity, "var(--status-danger)");
|
||||
return ToggleIconOn(activity, isPlaying ? "var(--green-300)" : "var(--primary-400)");
|
||||
|
@ -71,11 +70,9 @@ function ToggleActivityComponent(activity: IgnoredActivity, isPlaying = false) {
|
|||
function handleActivityToggle(e: React.MouseEvent<HTMLButtonElement, MouseEvent>, activity: IgnoredActivity) {
|
||||
e.stopPropagation();
|
||||
|
||||
const ignoredActivityIndex = getIgnoredActivities().findIndex(act => act.id === activity.id);
|
||||
if (ignoredActivityIndex === -1) settings.store.ignoredActivities = getIgnoredActivities().concat(activity);
|
||||
else settings.store.ignoredActivities = getIgnoredActivities().filter((_, index) => index !== ignoredActivityIndex);
|
||||
|
||||
recalculateActivities();
|
||||
const ignoredActivityIndex = settings.store.ignoredActivities.findIndex(act => act.id === activity.id);
|
||||
if (ignoredActivityIndex === -1) settings.store.ignoredActivities.push(activity);
|
||||
else settings.store.ignoredActivities.splice(ignoredActivityIndex, 1);
|
||||
}
|
||||
|
||||
function recalculateActivities() {
|
||||
|
@ -209,14 +206,13 @@ const settings = definePluginSettings({
|
|||
description: "Ignore all competing activities (These are normally special game activities)",
|
||||
default: false,
|
||||
onChange: recalculateActivities
|
||||
},
|
||||
ignoredActivities: {
|
||||
type: OptionType.CUSTOM,
|
||||
default: [] as IgnoredActivity[],
|
||||
onChange: recalculateActivities
|
||||
}
|
||||
}).withPrivateSettings<{
|
||||
ignoredActivities: IgnoredActivity[];
|
||||
}>();
|
||||
|
||||
function getIgnoredActivities() {
|
||||
return settings.store.ignoredActivities ??= [];
|
||||
}
|
||||
});
|
||||
|
||||
function isActivityTypeIgnored(type: number, id?: string) {
|
||||
if (id && settings.store.idsList.includes(id)) {
|
||||
|
@ -284,29 +280,14 @@ export default definePlugin({
|
|||
],
|
||||
|
||||
async start() {
|
||||
// Migrate allowedIds
|
||||
if (Settings.plugins.IgnoreActivities.allowedIds) {
|
||||
settings.store.idsList = Settings.plugins.IgnoreActivities.allowedIds;
|
||||
delete Settings.plugins.IgnoreActivities.allowedIds; // Remove allowedIds
|
||||
}
|
||||
|
||||
const oldIgnoredActivitiesData = await DataStore.get<Map<IgnoredActivity["id"], IgnoredActivity>>("IgnoreActivities_ignoredActivities");
|
||||
|
||||
if (oldIgnoredActivitiesData != null) {
|
||||
settings.store.ignoredActivities = Array.from(oldIgnoredActivitiesData.values())
|
||||
.map(activity => ({ ...activity, name: "Unknown Name" }));
|
||||
|
||||
DataStore.del("IgnoreActivities_ignoredActivities");
|
||||
}
|
||||
|
||||
if (getIgnoredActivities().length !== 0) {
|
||||
if (settings.store.ignoredActivities.length !== 0) {
|
||||
const gamesSeen = RunningGameStore.getGamesSeen() as { id?: string; exePath: string; }[];
|
||||
|
||||
for (const [index, ignoredActivity] of getIgnoredActivities().entries()) {
|
||||
for (const [index, ignoredActivity] of settings.store.ignoredActivities.entries()) {
|
||||
if (ignoredActivity.type !== ActivitiesTypes.Game) continue;
|
||||
|
||||
if (!gamesSeen.some(game => game.id === ignoredActivity.id || game.exePath === ignoredActivity.id)) {
|
||||
getIgnoredActivities().splice(index, 1);
|
||||
settings.store.ignoredActivities.splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -316,11 +297,11 @@ export default definePlugin({
|
|||
if (isActivityTypeIgnored(props.type, props.application_id)) return false;
|
||||
|
||||
if (props.application_id != null) {
|
||||
return !getIgnoredActivities().some(activity => activity.id === props.application_id) || (settings.store.listMode === FilterMode.Whitelist && settings.store.idsList.includes(props.application_id));
|
||||
return !settings.store.ignoredActivities.some(activity => activity.id === props.application_id) || (settings.store.listMode === FilterMode.Whitelist && settings.store.idsList.includes(props.application_id));
|
||||
} else {
|
||||
const exePath = RunningGameStore.getRunningGames().find(game => game.name === props.name)?.exePath;
|
||||
if (exePath) {
|
||||
return !getIgnoredActivities().some(activity => activity.id === exePath);
|
||||
return !settings.store.ignoredActivities.some(activity => activity.id === exePath);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ import { debounce } from "@shared/debounce";
|
|||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { Menu, ReactDOM } from "@webpack/common";
|
||||
import { JSX } from "react";
|
||||
import type { Root } from "react-dom/client";
|
||||
|
||||
import { Magnifier, MagnifierProps } from "./components/Magnifier";
|
||||
|
@ -80,7 +81,12 @@ export const settings = definePluginSettings({
|
|||
});
|
||||
|
||||
|
||||
const imageContextMenuPatch: NavContextMenuPatchCallback = children => {
|
||||
const imageContextMenuPatch: NavContextMenuPatchCallback = (children, props) => {
|
||||
// Discord re-uses the image context menu for links to for the copy and open buttons
|
||||
if ("href" in props) return;
|
||||
// emojis in user statuses
|
||||
if (props.target?.classList?.contains("emoji")) return;
|
||||
|
||||
const { square, nearestNeighbour } = settings.use(["square", "nearestNeighbour"]);
|
||||
|
||||
children.push(
|
||||
|
|
|
@ -16,12 +16,19 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { addProfileBadge, removeProfileBadge } from "@api/Badges";
|
||||
import { addChatBarButton, removeChatBarButton } from "@api/ChatButtons";
|
||||
import { registerCommand, unregisterCommand } from "@api/Commands";
|
||||
import { addContextMenuPatch, removeContextMenuPatch } from "@api/ContextMenu";
|
||||
import { Settings } from "@api/Settings";
|
||||
import { addMemberListDecorator, removeMemberListDecorator } from "@api/MemberListDecorators";
|
||||
import { addMessageAccessory, removeMessageAccessory } from "@api/MessageAccessories";
|
||||
import { addMessageDecoration, removeMessageDecoration } from "@api/MessageDecorations";
|
||||
import { addMessageClickListener, addMessagePreEditListener, addMessagePreSendListener, removeMessageClickListener, removeMessagePreEditListener, removeMessagePreSendListener } from "@api/MessageEvents";
|
||||
import { addMessagePopoverButton, removeMessagePopoverButton } from "@api/MessagePopover";
|
||||
import { Settings, SettingsStore } from "@api/Settings";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import { canonicalizeFind } from "@utils/patches";
|
||||
import { Patch, Plugin, ReporterTestable, StartAt } from "@utils/types";
|
||||
import { Patch, Plugin, PluginDef, ReporterTestable, StartAt } from "@utils/types";
|
||||
import { FluxDispatcher } from "@webpack/common";
|
||||
import { FluxEvents } from "@webpack/types";
|
||||
|
||||
|
@ -83,6 +90,13 @@ function isReporterTestable(p: Plugin, part: ReporterTestable) {
|
|||
: (p.reporterTestable & part) === part;
|
||||
}
|
||||
|
||||
const pluginKeysToBind: Array<keyof PluginDef & `${"on" | "render"}${string}`> = [
|
||||
"onBeforeMessageEdit", "onBeforeMessageSend", "onMessageClick",
|
||||
"renderChatBarButton", "renderMemberListDecorator", "renderMessageAccessory", "renderMessageDecoration", "renderMessagePopoverButton"
|
||||
];
|
||||
|
||||
const neededApiPlugins = new Set<string>();
|
||||
|
||||
// First round-trip to mark and force enable dependencies
|
||||
//
|
||||
// FIXME: might need to revisit this if there's ever nested (dependencies of dependencies) dependencies since this only
|
||||
|
@ -106,22 +120,46 @@ for (const p of pluginsValues) if (isPluginEnabled(p.name)) {
|
|||
dep.isDependency = true;
|
||||
});
|
||||
|
||||
if (p.commands?.length) {
|
||||
Plugins.CommandsAPI.isDependency = true;
|
||||
settings.CommandsAPI.enabled = true;
|
||||
if (p.commands?.length) neededApiPlugins.add("CommandsAPI");
|
||||
if (p.onBeforeMessageEdit || p.onBeforeMessageSend || p.onMessageClick) neededApiPlugins.add("MessageEventsAPI");
|
||||
if (p.renderChatBarButton) neededApiPlugins.add("ChatInputButtonAPI");
|
||||
if (p.renderMemberListDecorator) neededApiPlugins.add("MemberListDecoratorsAPI");
|
||||
if (p.renderMessageAccessory) neededApiPlugins.add("MessageAccessoriesAPI");
|
||||
if (p.renderMessageDecoration) neededApiPlugins.add("MessageDecorationsAPI");
|
||||
if (p.renderMessagePopoverButton) neededApiPlugins.add("MessagePopoverAPI");
|
||||
if (p.userProfileBadge) neededApiPlugins.add("BadgeAPI");
|
||||
|
||||
for (const key of pluginKeysToBind) {
|
||||
p[key] &&= p[key].bind(p) as any;
|
||||
}
|
||||
}
|
||||
|
||||
for (const p of neededApiPlugins) {
|
||||
Plugins[p].isDependency = true;
|
||||
settings[p].enabled = true;
|
||||
}
|
||||
|
||||
for (const p of pluginsValues) {
|
||||
if (p.settings) {
|
||||
p.settings.pluginName = p.name;
|
||||
p.options ??= {};
|
||||
for (const [name, def] of Object.entries(p.settings.def)) {
|
||||
|
||||
p.settings.pluginName = p.name;
|
||||
for (const name in p.settings.def) {
|
||||
const def = p.settings.def[name];
|
||||
const checks = p.settings.checks?.[name];
|
||||
p.options[name] = { ...def, ...checks };
|
||||
}
|
||||
}
|
||||
|
||||
if (p.options) {
|
||||
for (const name in p.options) {
|
||||
const opt = p.options[name];
|
||||
if (opt.onChange != null) {
|
||||
SettingsStore.addChangeListener(`plugins.${p.name}.${name}`, opt.onChange);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (p.patches && isPluginEnabled(p.name)) {
|
||||
if (!IS_REPORTER || isReporterTestable(p, ReporterTestable.Patches)) {
|
||||
for (const patch of p.patches) {
|
||||
|
@ -215,7 +253,11 @@ export function subscribeAllPluginsFluxEvents(fluxDispatcher: typeof FluxDispatc
|
|||
}
|
||||
|
||||
export const startPlugin = traceFunction("startPlugin", function startPlugin(p: Plugin) {
|
||||
const { name, commands, contextMenus } = p;
|
||||
const {
|
||||
name, commands, contextMenus, userProfileBadge,
|
||||
onBeforeMessageEdit, onBeforeMessageSend, onMessageClick,
|
||||
renderChatBarButton, renderMemberListDecorator, renderMessageAccessory, renderMessageDecoration, renderMessagePopoverButton
|
||||
} = p;
|
||||
|
||||
if (p.start) {
|
||||
logger.info("Starting plugin", name);
|
||||
|
@ -249,7 +291,6 @@ export const startPlugin = traceFunction("startPlugin", function startPlugin(p:
|
|||
subscribePluginFluxEvents(p, FluxDispatcher);
|
||||
}
|
||||
|
||||
|
||||
if (contextMenus) {
|
||||
logger.debug("Adding context menus patches of plugin", name);
|
||||
for (const navId in contextMenus) {
|
||||
|
@ -257,11 +298,27 @@ export const startPlugin = traceFunction("startPlugin", function startPlugin(p:
|
|||
}
|
||||
}
|
||||
|
||||
if (userProfileBadge) addProfileBadge(userProfileBadge);
|
||||
|
||||
if (onBeforeMessageEdit) addMessagePreEditListener(onBeforeMessageEdit);
|
||||
if (onBeforeMessageSend) addMessagePreSendListener(onBeforeMessageSend);
|
||||
if (onMessageClick) addMessageClickListener(onMessageClick);
|
||||
|
||||
if (renderChatBarButton) addChatBarButton(name, renderChatBarButton);
|
||||
if (renderMemberListDecorator) addMemberListDecorator(name, renderMemberListDecorator);
|
||||
if (renderMessageDecoration) addMessageDecoration(name, renderMessageDecoration);
|
||||
if (renderMessageAccessory) addMessageAccessory(name, renderMessageAccessory);
|
||||
if (renderMessagePopoverButton) addMessagePopoverButton(name, renderMessagePopoverButton);
|
||||
|
||||
return true;
|
||||
}, p => `startPlugin ${p.name}`);
|
||||
|
||||
export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plugin) {
|
||||
const { name, commands, contextMenus } = p;
|
||||
const {
|
||||
name, commands, contextMenus, userProfileBadge,
|
||||
onBeforeMessageEdit, onBeforeMessageSend, onMessageClick,
|
||||
renderChatBarButton, renderMemberListDecorator, renderMessageAccessory, renderMessageDecoration, renderMessagePopoverButton
|
||||
} = p;
|
||||
|
||||
if (p.stop) {
|
||||
logger.info("Stopping plugin", name);
|
||||
|
@ -300,5 +357,17 @@ export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plu
|
|||
}
|
||||
}
|
||||
|
||||
if (userProfileBadge) removeProfileBadge(userProfileBadge);
|
||||
|
||||
if (onBeforeMessageEdit) removeMessagePreEditListener(onBeforeMessageEdit);
|
||||
if (onBeforeMessageSend) removeMessagePreSendListener(onBeforeMessageSend);
|
||||
if (onMessageClick) removeMessageClickListener(onMessageClick);
|
||||
|
||||
if (renderChatBarButton) removeChatBarButton(name);
|
||||
if (renderMemberListDecorator) removeMemberListDecorator(name);
|
||||
if (renderMessageDecoration) removeMessageDecoration(name);
|
||||
if (renderMessageAccessory) removeMessageAccessory(name);
|
||||
if (renderMessagePopoverButton) removeMessagePopoverButton(name);
|
||||
|
||||
return true;
|
||||
}, p => `stopPlugin ${p.name}`);
|
||||
|
|
|
@ -16,8 +16,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { addChatBarButton, ChatBarButton } from "@api/ChatButtons";
|
||||
import { addButton, removeButton } from "@api/MessagePopover";
|
||||
import { ChatBarButton, ChatBarButtonFactory } from "@api/ChatButtons";
|
||||
import { updateMessage } from "@api/MessageUpdater";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
|
@ -66,7 +65,7 @@ function Indicator() {
|
|||
|
||||
}
|
||||
|
||||
const ChatBarIcon: ChatBarButton = ({ isMainChat }) => {
|
||||
const ChatBarIcon: ChatBarButtonFactory = ({ isMainChat }) => {
|
||||
if (!isMainChat) return null;
|
||||
|
||||
return (
|
||||
|
@ -104,7 +103,7 @@ export default definePlugin({
|
|||
name: "InvisibleChat",
|
||||
description: "Encrypt your Messages in a non-suspicious way!",
|
||||
authors: [Devs.SammCheese],
|
||||
dependencies: ["MessagePopoverAPI", "ChatInputButtonAPI", "MessageUpdaterAPI"],
|
||||
dependencies: ["MessageUpdaterAPI"],
|
||||
reporterTestable: ReporterTestable.Patches,
|
||||
settings,
|
||||
|
||||
|
@ -125,7 +124,11 @@ export default definePlugin({
|
|||
/(http(s)?:\/\/.)?(www\.)?[-a-zA-Z0-9@:%._+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_+.~#?&//=]*)/,
|
||||
),
|
||||
async start() {
|
||||
addButton("InvisibleChat", message => {
|
||||
const { default: StegCloak } = await getStegCloak();
|
||||
steggo = new StegCloak(true, false);
|
||||
},
|
||||
|
||||
renderMessagePopoverButton(message) {
|
||||
return this.INV_REGEX.test(message?.content)
|
||||
? {
|
||||
label: "Decrypt Message",
|
||||
|
@ -142,18 +145,9 @@ export default definePlugin({
|
|||
}
|
||||
}
|
||||
: null;
|
||||
});
|
||||
|
||||
addChatBarButton("InvisibleChat", ChatBarIcon);
|
||||
|
||||
const { default: StegCloak } = await getStegCloak();
|
||||
steggo = new StegCloak(true, false);
|
||||
},
|
||||
|
||||
stop() {
|
||||
removeButton("InvisibleChat");
|
||||
removeButton("InvisibleChat");
|
||||
},
|
||||
renderChatBarButton: ChatBarIcon,
|
||||
|
||||
// Gets the Embed of a Link
|
||||
async getEmbed(url: URL): Promise<Object | {}> {
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { addClickListener, removeClickListener } from "@api/MessageEvents";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
|
@ -57,15 +56,20 @@ export default definePlugin({
|
|||
name: "MessageClickActions",
|
||||
description: "Hold Backspace and click to delete, double click to edit/reply",
|
||||
authors: [Devs.Ven],
|
||||
dependencies: ["MessageEventsAPI"],
|
||||
|
||||
settings,
|
||||
|
||||
start() {
|
||||
document.addEventListener("keydown", keydown);
|
||||
document.addEventListener("keyup", keyup);
|
||||
},
|
||||
|
||||
this.onClick = addClickListener((msg: any, channel, event) => {
|
||||
stop() {
|
||||
document.removeEventListener("keydown", keydown);
|
||||
document.removeEventListener("keyup", keyup);
|
||||
},
|
||||
|
||||
onMessageClick(msg: any, channel, event) {
|
||||
const isMe = msg.author.id === UserStore.getCurrentUser().id;
|
||||
if (!isDeletePressed) {
|
||||
if (event.detail < 2) return;
|
||||
|
@ -111,12 +115,5 @@ export default definePlugin({
|
|||
}
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
stop() {
|
||||
removeClickListener(this.onClick);
|
||||
document.removeEventListener("keydown", keydown);
|
||||
document.removeEventListener("keyup", keyup);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { addAccessory, removeAccessory } from "@api/MessageAccessories";
|
||||
import { addMessageAccessory, removeMessageAccessory } from "@api/MessageAccessories";
|
||||
import { updateMessage } from "@api/MessageUpdater";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { getUserSettingLazy } from "@api/UserSettings";
|
||||
|
@ -41,6 +41,7 @@ import {
|
|||
UserStore
|
||||
} from "@webpack/common";
|
||||
import { Channel, Message } from "discord-types/general";
|
||||
import { JSX } from "react";
|
||||
|
||||
const messageCache = new Map<string, {
|
||||
message?: Message;
|
||||
|
@ -347,10 +348,10 @@ function AutomodEmbedAccessory(props: MessageEmbedProps): JSX.Element | null {
|
|||
? parse(message.content)
|
||||
: [noContent(message.attachments.length, message.embeds.length)]
|
||||
}
|
||||
{images.map(a => {
|
||||
{images.map((a, idx) => {
|
||||
const { width, height } = computeWidthAndHeight(a.width, a.height);
|
||||
return (
|
||||
<div>
|
||||
<div key={idx}>
|
||||
<img src={a.url} width={width} height={height} />
|
||||
</div>
|
||||
);
|
||||
|
@ -372,7 +373,7 @@ export default definePlugin({
|
|||
settings,
|
||||
|
||||
start() {
|
||||
addAccessory("messageLinkEmbed", props => {
|
||||
addMessageAccessory("messageLinkEmbed", props => {
|
||||
if (!messageLinkRegex.test(props.message.content))
|
||||
return null;
|
||||
|
||||
|
@ -390,6 +391,6 @@ export default definePlugin({
|
|||
},
|
||||
|
||||
stop() {
|
||||
removeAccessory("messageLinkEmbed");
|
||||
removeMessageAccessory("messageLinkEmbed");
|
||||
}
|
||||
});
|
||||
|
|
|
@ -69,6 +69,7 @@ export function HistoryModal({ modalProps, message }: { modalProps: ModalProps;
|
|||
|
||||
{timestamps.map((timestamp, index) => (
|
||||
<TabBar.Item
|
||||
key={index}
|
||||
className="vc-settings-tab-bar-item"
|
||||
id={index}
|
||||
>
|
||||
|
|
|
@ -169,8 +169,8 @@ export default definePlugin({
|
|||
|
||||
return Settings.plugins.MessageLogger.inlineEdits && (
|
||||
<>
|
||||
{message.editHistory?.map(edit => (
|
||||
<div className="messagelogger-edited">
|
||||
{message.editHistory?.map((edit, idx) => (
|
||||
<div key={idx} className="messagelogger-edited">
|
||||
{parseEditContent(edit.content, message)}
|
||||
<Timestamp
|
||||
timestamp={edit.timestamp}
|
||||
|
@ -211,7 +211,8 @@ export default definePlugin({
|
|||
collapseDeleted: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Whether to collapse deleted messages, similar to blocked messages",
|
||||
default: false
|
||||
default: false,
|
||||
restartNeeded: true,
|
||||
},
|
||||
logEdits: {
|
||||
type: OptionType.BOOLEAN,
|
||||
|
@ -304,7 +305,7 @@ export default definePlugin({
|
|||
{...props}
|
||||
className={classes("messagelogger-edit-marker", className)}
|
||||
onClick={() => openHistoryModal(message)}
|
||||
aria-role="button"
|
||||
role="button"
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
import { ApplicationCommandInputType, ApplicationCommandOptionType, findOption, registerCommand, sendBotMessage, unregisterCommand } from "@api/Commands";
|
||||
import * as DataStore from "@api/DataStore";
|
||||
import { Settings } from "@api/Settings";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
|
||||
|
@ -29,23 +29,23 @@ const MessageTagsMarker = Symbol("MessageTags");
|
|||
interface Tag {
|
||||
name: string;
|
||||
message: string;
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
const getTags = () => DataStore.get(DATA_KEY).then<Tag[]>(t => t ?? []);
|
||||
const getTag = (name: string) => DataStore.get(DATA_KEY).then<Tag | null>((t: Tag[]) => (t ?? []).find((tt: Tag) => tt.name === name) ?? null);
|
||||
const addTag = async (tag: Tag) => {
|
||||
const tags = await getTags();
|
||||
tags.push(tag);
|
||||
DataStore.set(DATA_KEY, tags);
|
||||
return tags;
|
||||
};
|
||||
const removeTag = async (name: string) => {
|
||||
let tags = await getTags();
|
||||
tags = await tags.filter((t: Tag) => t.name !== name);
|
||||
DataStore.set(DATA_KEY, tags);
|
||||
return tags;
|
||||
};
|
||||
function getTags() {
|
||||
return settings.store.tagsList;
|
||||
}
|
||||
|
||||
function getTag(name: string) {
|
||||
return settings.store.tagsList[name] ?? null;
|
||||
}
|
||||
|
||||
function addTag(tag: Tag) {
|
||||
settings.store.tagsList[tag.name] = tag;
|
||||
}
|
||||
|
||||
function removeTag(name: string) {
|
||||
delete settings.store.tagsList[name];
|
||||
}
|
||||
|
||||
function createTagCommand(tag: Tag) {
|
||||
registerCommand({
|
||||
|
@ -53,14 +53,14 @@ function createTagCommand(tag: Tag) {
|
|||
description: tag.name,
|
||||
inputType: ApplicationCommandInputType.BUILT_IN_TEXT,
|
||||
execute: async (_, ctx) => {
|
||||
if (!await getTag(tag.name)) {
|
||||
if (!getTag(tag.name)) {
|
||||
sendBotMessage(ctx.channel.id, {
|
||||
content: `${EMOTE} The tag **${tag.name}** does not exist anymore! Please reload ur Discord to fix :)`
|
||||
});
|
||||
return { content: `/${tag.name}` };
|
||||
}
|
||||
|
||||
if (Settings.plugins.MessageTags.clyde) sendBotMessage(ctx.channel.id, {
|
||||
if (settings.store.clyde) sendBotMessage(ctx.channel.id, {
|
||||
content: `${EMOTE} The tag **${tag.name}** has been sent!`
|
||||
});
|
||||
return { content: tag.message.replaceAll("\\n", "\n") };
|
||||
|
@ -69,22 +69,38 @@ function createTagCommand(tag: Tag) {
|
|||
}, "CustomTags");
|
||||
}
|
||||
|
||||
|
||||
export default definePlugin({
|
||||
name: "MessageTags",
|
||||
description: "Allows you to save messages and to use them with a simple command.",
|
||||
authors: [Devs.Luna],
|
||||
options: {
|
||||
const settings = definePluginSettings({
|
||||
clyde: {
|
||||
name: "Clyde message on send",
|
||||
description: "If enabled, clyde will send you an ephemeral message when a tag was used.",
|
||||
type: OptionType.BOOLEAN,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
tagsList: {
|
||||
type: OptionType.CUSTOM,
|
||||
default: {} as Record<string, Tag>,
|
||||
}
|
||||
});
|
||||
|
||||
export default definePlugin({
|
||||
name: "MessageTags",
|
||||
description: "Allows you to save messages and to use them with a simple command.",
|
||||
authors: [Devs.Luna],
|
||||
settings,
|
||||
|
||||
async start() {
|
||||
for (const tag of await getTags()) createTagCommand(tag);
|
||||
// TODO: Remove DataStore tags migration once enough time has passed
|
||||
const oldTags = await DataStore.get<Tag[]>(DATA_KEY);
|
||||
if (oldTags != null) {
|
||||
// @ts-ignore
|
||||
settings.store.tagsList = Object.fromEntries(oldTags.map(oldTag => (delete oldTag.enabled, [oldTag.name, oldTag])));
|
||||
await DataStore.del(DATA_KEY);
|
||||
}
|
||||
|
||||
const tags = getTags();
|
||||
for (const tagName in tags) {
|
||||
createTagCommand(tags[tagName]);
|
||||
}
|
||||
},
|
||||
|
||||
commands: [
|
||||
|
@ -153,19 +169,18 @@ export default definePlugin({
|
|||
const name: string = findOption(args[0].options, "tag-name", "");
|
||||
const message: string = findOption(args[0].options, "message", "");
|
||||
|
||||
if (await getTag(name))
|
||||
if (getTag(name))
|
||||
return sendBotMessage(ctx.channel.id, {
|
||||
content: `${EMOTE} A Tag with the name **${name}** already exists!`
|
||||
});
|
||||
|
||||
const tag = {
|
||||
name: name,
|
||||
enabled: true,
|
||||
message: message
|
||||
};
|
||||
|
||||
createTagCommand(tag);
|
||||
await addTag(tag);
|
||||
addTag(tag);
|
||||
|
||||
sendBotMessage(ctx.channel.id, {
|
||||
content: `${EMOTE} Successfully created the tag **${name}**!`
|
||||
|
@ -175,13 +190,13 @@ export default definePlugin({
|
|||
case "delete": {
|
||||
const name: string = findOption(args[0].options, "tag-name", "");
|
||||
|
||||
if (!await getTag(name))
|
||||
if (!getTag(name))
|
||||
return sendBotMessage(ctx.channel.id, {
|
||||
content: `${EMOTE} A Tag with the name **${name}** does not exist!`
|
||||
});
|
||||
|
||||
unregisterCommand(name);
|
||||
await removeTag(name);
|
||||
removeTag(name);
|
||||
|
||||
sendBotMessage(ctx.channel.id, {
|
||||
content: `${EMOTE} Successfully deleted the tag **${name}**!`
|
||||
|
@ -192,10 +207,8 @@ export default definePlugin({
|
|||
sendBotMessage(ctx.channel.id, {
|
||||
embeds: [
|
||||
{
|
||||
// @ts-ignore
|
||||
title: "All Tags:",
|
||||
// @ts-ignore
|
||||
description: (await getTags())
|
||||
description: Object.values(getTags())
|
||||
.map(tag => `\`${tag.name}\`: ${tag.message.slice(0, 72).replaceAll("\\n", " ")}${tag.message.length > 72 ? "..." : ""}`)
|
||||
.join("\n") || `${EMOTE} Woops! There are no tags yet, use \`/tags create\` to create one!`,
|
||||
// @ts-ignore
|
||||
|
@ -208,7 +221,7 @@ export default definePlugin({
|
|||
}
|
||||
case "preview": {
|
||||
const name: string = findOption(args[0].options, "tag-name", "");
|
||||
const tag = await getTag(name);
|
||||
const tag = getTag(name);
|
||||
|
||||
if (!tag)
|
||||
return sendBotMessage(ctx.channel.id, {
|
||||
|
|
|
@ -114,7 +114,7 @@ function SettingsComponent() {
|
|||
return (
|
||||
<Flex flexDirection="column">
|
||||
{tags.map(t => (
|
||||
<Card style={{ padding: "1em 1em 0" }}>
|
||||
<Card key={t.name} style={{ padding: "1em 1em 0" }}>
|
||||
<Forms.FormTitle style={{ width: "fit-content" }}>
|
||||
<Tooltip text={t.description}>
|
||||
{({ onMouseEnter, onMouseLeave }) => (
|
||||
|
@ -218,7 +218,7 @@ export default definePlugin({
|
|||
},
|
||||
// in the member list
|
||||
{
|
||||
find: "#{intl::GUILD_OWNER}",
|
||||
find: "#{intl::GUILD_OWNER}),children:",
|
||||
replacement: {
|
||||
match: /(?<type>\i)=\(null==.{0,100}\.BOT;return null!=(?<user>\i)&&\i\.bot/,
|
||||
replace: "$<type> = $self.getTag({user: $<user>, channel: arguments[0].channel, origType: $<user>.bot ? 0 : null, location: 'not-chat' }); return typeof $<type> === 'number'"
|
||||
|
|
|
@ -24,6 +24,7 @@ import definePlugin from "@utils/types";
|
|||
import { findByPropsLazy, findComponentByCodeLazy } from "@webpack";
|
||||
import { Avatar, ChannelStore, Clickable, IconUtils, RelationshipStore, ScrollerThin, useMemo, UserStore } from "@webpack/common";
|
||||
import { Channel, User } from "discord-types/general";
|
||||
import { JSX } from "react";
|
||||
|
||||
const SelectedChannelActionCreators = findByPropsLazy("selectPrivateChannel");
|
||||
const UserUtils = findByPropsLazy("getGlobalName");
|
||||
|
@ -55,6 +56,7 @@ function getMutualGDMCountText(user: User) {
|
|||
function renderClickableGDMs(mutualDms: Channel[], onClose: () => void) {
|
||||
return mutualDms.map(c => (
|
||||
<Clickable
|
||||
key={c.id}
|
||||
className={ProfileListClasses.listRow}
|
||||
onClick={() => {
|
||||
onClose();
|
||||
|
|
|
@ -55,7 +55,7 @@ export default definePlugin({
|
|||
},
|
||||
{
|
||||
// Clicking on replied messages to jump
|
||||
find: "flash:!0,returnMessageId",
|
||||
find: '("interactionUsernameProfile',
|
||||
replacement: [
|
||||
{
|
||||
match: /.\?(.{1,10}\.show\({.{1,50}#{intl::UNBLOCK_TO_JUMP_TITLE})/,
|
||||
|
|
|
@ -113,6 +113,7 @@ function RolesAndUsersPermissionsComponent({ permissions, guild, modalProps, hea
|
|||
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className={cl("modal-list-item-btn")}
|
||||
onClick={() => selectItem(index)}
|
||||
role="button"
|
||||
|
@ -178,7 +179,7 @@ function RolesAndUsersPermissionsComponent({ permissions, guild, modalProps, hea
|
|||
<div className={cl("modal-divider")} />
|
||||
<ScrollerThin className={cl("modal-perms")} orientation="auto">
|
||||
{Object.values(PermissionsBits).map(bit => (
|
||||
<div className={cl("modal-perms-item")}>
|
||||
<div key={bit} className={cl("modal-perms-item")}>
|
||||
<div className={cl("modal-perms-item-icon")}>
|
||||
{(() => {
|
||||
const { permissions, overwriteAllow, overwriteDeny } = selectedItem;
|
||||
|
|
|
@ -192,6 +192,7 @@ function UserPermissionsComponent({ guild, guildMember, closePopout }: { guild:
|
|||
<div className={classes(RoleRootClasses.root)}>
|
||||
{userPermissions.map(({ permission, roleColor, roleName }) => (
|
||||
<Tooltip
|
||||
key={permission}
|
||||
text={<GrantedByTooltip roleName={roleName} roleColor={roleColor} />}
|
||||
tooltipClassName={cl("granted-by-container")}
|
||||
tooltipContentClassName={cl("granted-by-content")}
|
||||
|
|
|
@ -7,11 +7,10 @@
|
|||
import { classNameFactory } from "@api/Styles";
|
||||
import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModalLazy } from "@utils/modal";
|
||||
import { extractAndLoadChunksLazy, findComponentByCodeLazy, findExportedComponentLazy } from "@webpack";
|
||||
import { Button, Forms, Text, TextInput, Toasts, useEffect, useState } from "@webpack/common";
|
||||
import { Button, Forms, Text, TextInput, Toasts, useMemo, useState } from "@webpack/common";
|
||||
|
||||
import { DEFAULT_COLOR, SWATCHES } from "../constants";
|
||||
import { categories, Category, createCategory, getCategory, updateCategory } from "../data";
|
||||
import { forceUpdate } from "../index";
|
||||
import { categoryLen, createCategory, getCategory } from "../data";
|
||||
|
||||
interface ColorPickerProps {
|
||||
color: number | null;
|
||||
|
@ -33,51 +32,51 @@ interface ColorPickerWithSwatchesProps {
|
|||
const ColorPicker = findComponentByCodeLazy<ColorPickerProps>("#{intl::USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR}", ".BACKGROUND_PRIMARY)");
|
||||
const ColorPickerWithSwatches = findExportedComponentLazy<ColorPickerWithSwatchesProps>("ColorPicker", "CustomColorPicker");
|
||||
|
||||
export const requireSettingsMenu = extractAndLoadChunksLazy(['name:"UserSettings"'], /createPromise:.{0,20}Promise\.all\((\[\i\.\i\("?.+?"?\).+?\])\).then\(\i\.bind\(\i,"?(.+?)"?\)\).{0,50}"UserSettings"/);
|
||||
export const requireSettingsMenu = extractAndLoadChunksLazy(['name:"UserSettings"'], /createPromise:.{0,20}(\i\.\i\("?.+?"?\).*?).then\(\i\.bind\(\i,"?(.+?)"?\)\).{0,50}"UserSettings"/);
|
||||
|
||||
const cl = classNameFactory("vc-pindms-modal-");
|
||||
|
||||
interface Props {
|
||||
categoryId: string | null;
|
||||
initalChannelId: string | null;
|
||||
initialChannelId: string | null;
|
||||
modalProps: ModalProps;
|
||||
}
|
||||
|
||||
function useCategory(categoryId: string | null, initalChannelId: string | null) {
|
||||
const [category, setCategory] = useState<Category | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (categoryId)
|
||||
setCategory(getCategory(categoryId)!);
|
||||
else if (initalChannelId)
|
||||
setCategory({
|
||||
const category = useMemo(() => {
|
||||
if (categoryId) {
|
||||
return getCategory(categoryId);
|
||||
} else if (initalChannelId) {
|
||||
return {
|
||||
id: Toasts.genId(),
|
||||
name: `Pin Category ${categories.length + 1}`,
|
||||
name: `Pin Category ${categoryLen() + 1}`,
|
||||
color: DEFAULT_COLOR,
|
||||
collapsed: false,
|
||||
channels: [initalChannelId]
|
||||
});
|
||||
};
|
||||
}
|
||||
}, [categoryId, initalChannelId]);
|
||||
|
||||
return {
|
||||
category,
|
||||
setCategory
|
||||
};
|
||||
return category;
|
||||
}
|
||||
|
||||
export function NewCategoryModal({ categoryId, modalProps, initalChannelId }: Props) {
|
||||
const { category, setCategory } = useCategory(categoryId, initalChannelId);
|
||||
|
||||
export function NewCategoryModal({ categoryId, modalProps, initialChannelId }: Props) {
|
||||
const category = useCategory(categoryId, initialChannelId);
|
||||
if (!category) return null;
|
||||
|
||||
const onSave = async (e: React.FormEvent<HTMLFormElement> | React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||
e.preventDefault();
|
||||
if (!categoryId)
|
||||
await createCategory(category);
|
||||
else
|
||||
await updateCategory(category);
|
||||
const [name, setName] = useState(category.name);
|
||||
const [color, setColor] = useState(category.color);
|
||||
|
||||
const onSave = (e: React.FormEvent<HTMLFormElement> | React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||
e.preventDefault();
|
||||
|
||||
category.name = name;
|
||||
category.color = color;
|
||||
|
||||
if (!categoryId) {
|
||||
createCategory(category);
|
||||
}
|
||||
|
||||
forceUpdate();
|
||||
modalProps.onClose();
|
||||
};
|
||||
|
||||
|
@ -93,25 +92,25 @@ export function NewCategoryModal({ categoryId, modalProps, initalChannelId }: Pr
|
|||
<Forms.FormSection>
|
||||
<Forms.FormTitle>Name</Forms.FormTitle>
|
||||
<TextInput
|
||||
value={category.name}
|
||||
onChange={e => setCategory({ ...category, name: e })}
|
||||
value={name}
|
||||
onChange={e => setName(e)}
|
||||
/>
|
||||
</Forms.FormSection>
|
||||
<Forms.FormDivider />
|
||||
<Forms.FormSection>
|
||||
<Forms.FormTitle>Color</Forms.FormTitle>
|
||||
<ColorPickerWithSwatches
|
||||
key={category.name}
|
||||
key={category.id}
|
||||
defaultColor={DEFAULT_COLOR}
|
||||
colors={SWATCHES}
|
||||
onChange={c => setCategory({ ...category, color: c! })}
|
||||
value={category.color}
|
||||
onChange={c => setColor(c!)}
|
||||
value={color}
|
||||
renderDefaultButton={() => null}
|
||||
renderCustomButton={() => (
|
||||
<ColorPicker
|
||||
color={category.color}
|
||||
onChange={c => setCategory({ ...category, color: c! })}
|
||||
key={category.name}
|
||||
color={color}
|
||||
onChange={c => setColor(c!)}
|
||||
key={category.id}
|
||||
showEyeDropper={false}
|
||||
/>
|
||||
)}
|
||||
|
@ -119,7 +118,7 @@ export function NewCategoryModal({ categoryId, modalProps, initalChannelId }: Pr
|
|||
</Forms.FormSection>
|
||||
</ModalContent>
|
||||
<ModalFooter>
|
||||
<Button type="submit" onClick={onSave} disabled={!category.name}>{categoryId ? "Save" : "Create"}</Button>
|
||||
<Button type="submit" onClick={onSave} disabled={!name}>{categoryId ? "Save" : "Create"}</Button>
|
||||
</ModalFooter>
|
||||
</form>
|
||||
</ModalRoot>
|
||||
|
@ -129,6 +128,6 @@ export function NewCategoryModal({ categoryId, modalProps, initalChannelId }: Pr
|
|||
export const openCategoryModal = (categoryId: string | null, channelId: string | null) =>
|
||||
openModalLazy(async () => {
|
||||
await requireSettingsMenu();
|
||||
return modalProps => <NewCategoryModal categoryId={categoryId} modalProps={modalProps} initalChannelId={channelId} />;
|
||||
return modalProps => <NewCategoryModal categoryId={categoryId} modalProps={modalProps} initialChannelId={channelId} />;
|
||||
});
|
||||
|
||||
|
|
|
@ -7,8 +7,8 @@
|
|||
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import { Menu } from "@webpack/common";
|
||||
|
||||
import { addChannelToCategory, canMoveChannelInDirection, categories, isPinned, moveChannel, removeChannelFromCategory } from "../data";
|
||||
import { forceUpdate, PinOrder, settings } from "../index";
|
||||
import { addChannelToCategory, canMoveChannelInDirection, currentUserCategories, isPinned, moveChannel, removeChannelFromCategory } from "../data";
|
||||
import { PinOrder, settings } from "../index";
|
||||
import { openCategoryModal } from "./CreateCategoryModal";
|
||||
|
||||
function createPinMenuItem(channelId: string) {
|
||||
|
@ -31,11 +31,12 @@ function createPinMenuItem(channelId: string) {
|
|||
<Menu.MenuSeparator />
|
||||
|
||||
{
|
||||
categories.map(category => (
|
||||
currentUserCategories.map(category => (
|
||||
<Menu.MenuItem
|
||||
id={`pin-category-${category.name}`}
|
||||
key={category.id}
|
||||
id={`pin-category-${category.id}`}
|
||||
label={category.name}
|
||||
action={() => addChannelToCategory(channelId, category.id).then(forceUpdate)}
|
||||
action={() => addChannelToCategory(channelId, category.id)}
|
||||
/>
|
||||
))
|
||||
}
|
||||
|
@ -48,7 +49,7 @@ function createPinMenuItem(channelId: string) {
|
|||
id="unpin-dm"
|
||||
label="Unpin DM"
|
||||
color="danger"
|
||||
action={() => removeChannelFromCategory(channelId).then(forceUpdate)}
|
||||
action={() => removeChannelFromCategory(channelId)}
|
||||
/>
|
||||
|
||||
{
|
||||
|
@ -56,7 +57,7 @@ function createPinMenuItem(channelId: string) {
|
|||
<Menu.MenuItem
|
||||
id="move-up"
|
||||
label="Move Up"
|
||||
action={() => moveChannel(channelId, -1).then(forceUpdate)}
|
||||
action={() => moveChannel(channelId, -1)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -66,7 +67,7 @@ function createPinMenuItem(channelId: string) {
|
|||
<Menu.MenuItem
|
||||
id="move-down"
|
||||
label="Move Down"
|
||||
action={() => moveChannel(channelId, 1).then(forceUpdate)}
|
||||
action={() => moveChannel(channelId, 1)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -6,10 +6,10 @@
|
|||
|
||||
import * as DataStore from "@api/DataStore";
|
||||
import { Settings } from "@api/Settings";
|
||||
import { useForceUpdater } from "@utils/react";
|
||||
import { UserStore } from "@webpack/common";
|
||||
|
||||
import { DEFAULT_COLOR } from "./constants";
|
||||
import { forceUpdate, PinOrder, PrivateChannelSortStore, settings } from "./index";
|
||||
import { PinOrder, PrivateChannelSortStore, settings } from "./index";
|
||||
|
||||
export interface Category {
|
||||
id: string;
|
||||
|
@ -24,104 +24,92 @@ const CATEGORY_MIGRATED_PINDMS_KEY = "PinDMsMigratedPinDMs";
|
|||
const CATEGORY_MIGRATED_KEY = "PinDMsMigratedOldCategories";
|
||||
const OLD_CATEGORY_KEY = "BetterPinDMsCategories-";
|
||||
|
||||
|
||||
export let categories: Category[] = [];
|
||||
|
||||
export async function saveCats(cats: Category[]) {
|
||||
const { id } = UserStore.getCurrentUser();
|
||||
await DataStore.set(CATEGORY_BASE_KEY + id, cats);
|
||||
}
|
||||
let forceUpdateDms: (() => void) | undefined = undefined;
|
||||
export let currentUserCategories: Category[] = [];
|
||||
|
||||
export async function init() {
|
||||
const id = UserStore.getCurrentUser()?.id;
|
||||
await initCategories(id);
|
||||
await migrateData(id);
|
||||
forceUpdate();
|
||||
await migrateData();
|
||||
|
||||
const userId = UserStore.getCurrentUser()?.id;
|
||||
if (userId == null) return;
|
||||
|
||||
currentUserCategories = settings.store.userBasedCategoryList[userId] ??= [];
|
||||
forceUpdateDms?.();
|
||||
}
|
||||
|
||||
export async function initCategories(userId: string) {
|
||||
categories = await DataStore.get<Category[]>(CATEGORY_BASE_KEY + userId) ?? [];
|
||||
export function usePinnedDms() {
|
||||
forceUpdateDms = useForceUpdater();
|
||||
settings.use(["pinOrder", "canCollapseDmSection", "dmSectionCollapsed", "userBasedCategoryList"]);
|
||||
}
|
||||
|
||||
export function getCategory(id: string) {
|
||||
return categories.find(c => c.id === id);
|
||||
return currentUserCategories.find(c => c.id === id);
|
||||
}
|
||||
|
||||
export async function createCategory(category: Category) {
|
||||
categories.push(category);
|
||||
await saveCats(categories);
|
||||
export function getCategoryByIndex(index: number) {
|
||||
return currentUserCategories[index];
|
||||
}
|
||||
|
||||
export async function updateCategory(category: Category) {
|
||||
const index = categories.findIndex(c => c.id === category.id);
|
||||
if (index === -1) return;
|
||||
|
||||
categories[index] = category;
|
||||
await saveCats(categories);
|
||||
export function createCategory(category: Category) {
|
||||
currentUserCategories.push(category);
|
||||
}
|
||||
|
||||
export async function addChannelToCategory(channelId: string, categoryId: string) {
|
||||
const category = categories.find(c => c.id === categoryId);
|
||||
if (!category) return;
|
||||
export function addChannelToCategory(channelId: string, categoryId: string) {
|
||||
const category = currentUserCategories.find(c => c.id === categoryId);
|
||||
if (category == null) return;
|
||||
|
||||
if (category.channels.includes(channelId)) return;
|
||||
|
||||
category.channels.push(channelId);
|
||||
await saveCats(categories);
|
||||
|
||||
}
|
||||
|
||||
export async function removeChannelFromCategory(channelId: string) {
|
||||
const category = categories.find(c => c.channels.includes(channelId));
|
||||
if (!category) return;
|
||||
export function removeChannelFromCategory(channelId: string) {
|
||||
const category = currentUserCategories.find(c => c.channels.includes(channelId));
|
||||
if (category == null) return;
|
||||
|
||||
category.channels = category.channels.filter(c => c !== channelId);
|
||||
await saveCats(categories);
|
||||
}
|
||||
|
||||
export async function removeCategory(categoryId: string) {
|
||||
const catagory = categories.find(c => c.id === categoryId);
|
||||
if (!catagory) return;
|
||||
export function removeCategory(categoryId: string) {
|
||||
const categoryIndex = currentUserCategories.findIndex(c => c.id === categoryId);
|
||||
if (categoryIndex === -1) return;
|
||||
|
||||
// catagory?.channels.forEach(c => removeChannelFromCategory(c));
|
||||
categories = categories.filter(c => c.id !== categoryId);
|
||||
await saveCats(categories);
|
||||
currentUserCategories.splice(categoryIndex, 1);
|
||||
}
|
||||
|
||||
export async function collapseCategory(id: string, value = true) {
|
||||
const category = categories.find(c => c.id === id);
|
||||
if (!category) return;
|
||||
export function collapseCategory(id: string, value = true) {
|
||||
const category = currentUserCategories.find(c => c.id === id);
|
||||
if (category == null) return;
|
||||
|
||||
category.collapsed = value;
|
||||
await saveCats(categories);
|
||||
}
|
||||
|
||||
// utils
|
||||
// Utils
|
||||
export function isPinned(id: string) {
|
||||
return categories.some(c => c.channels.includes(id));
|
||||
return currentUserCategories.some(c => c.channels.includes(id));
|
||||
}
|
||||
|
||||
export function categoryLen() {
|
||||
return categories.length;
|
||||
return currentUserCategories.length;
|
||||
}
|
||||
|
||||
export function getAllUncollapsedChannels() {
|
||||
if (settings.store.pinOrder === PinOrder.LastMessage) {
|
||||
const sortedChannels = PrivateChannelSortStore.getPrivateChannelIds();
|
||||
return categories.filter(c => !c.collapsed).flatMap(c => sortedChannels.filter(channel => c.channels.includes(channel)));
|
||||
return currentUserCategories.filter(c => !c.collapsed).flatMap(c => sortedChannels.filter(channel => c.channels.includes(channel)));
|
||||
}
|
||||
|
||||
return categories.filter(c => !c.collapsed).flatMap(c => c.channels);
|
||||
return currentUserCategories.filter(c => !c.collapsed).flatMap(c => c.channels);
|
||||
}
|
||||
|
||||
export function getSections() {
|
||||
return categories.reduce((acc, category) => {
|
||||
return currentUserCategories.reduce((acc, category) => {
|
||||
acc.push(category.channels.length === 0 ? 1 : category.channels.length);
|
||||
return acc;
|
||||
}, [] as number[]);
|
||||
}
|
||||
|
||||
// move categories
|
||||
// Move categories
|
||||
export const canMoveArrayInDirection = (array: any[], index: number, direction: -1 | 1) => {
|
||||
const a = array[index];
|
||||
const b = array[index + direction];
|
||||
|
@ -130,18 +118,18 @@ export const canMoveArrayInDirection = (array: any[], index: number, direction:
|
|||
};
|
||||
|
||||
export const canMoveCategoryInDirection = (id: string, direction: -1 | 1) => {
|
||||
const index = categories.findIndex(m => m.id === id);
|
||||
return canMoveArrayInDirection(categories, index, direction);
|
||||
const categoryIndex = currentUserCategories.findIndex(m => m.id === id);
|
||||
return canMoveArrayInDirection(currentUserCategories, categoryIndex, direction);
|
||||
};
|
||||
|
||||
export const canMoveCategory = (id: string) => canMoveCategoryInDirection(id, -1) || canMoveCategoryInDirection(id, 1);
|
||||
|
||||
export const canMoveChannelInDirection = (channelId: string, direction: -1 | 1) => {
|
||||
const category = categories.find(c => c.channels.includes(channelId));
|
||||
if (!category) return false;
|
||||
const category = currentUserCategories.find(c => c.channels.includes(channelId));
|
||||
if (category == null) return false;
|
||||
|
||||
const index = category.channels.indexOf(channelId);
|
||||
return canMoveArrayInDirection(category.channels, index, direction);
|
||||
const channelIndex = category.channels.indexOf(channelId);
|
||||
return canMoveArrayInDirection(category.channels, channelIndex, direction);
|
||||
};
|
||||
|
||||
|
||||
|
@ -150,70 +138,44 @@ function swapElementsInArray(array: any[], index1: number, index2: number) {
|
|||
[array[index1], array[index2]] = [array[index2], array[index1]];
|
||||
}
|
||||
|
||||
// stolen from PinDMs
|
||||
export async function moveCategory(id: string, direction: -1 | 1) {
|
||||
const a = categories.findIndex(m => m.id === id);
|
||||
export function moveCategory(id: string, direction: -1 | 1) {
|
||||
const a = currentUserCategories.findIndex(m => m.id === id);
|
||||
const b = a + direction;
|
||||
|
||||
swapElementsInArray(categories, a, b);
|
||||
|
||||
await saveCats(categories);
|
||||
swapElementsInArray(currentUserCategories, a, b);
|
||||
}
|
||||
|
||||
export async function moveChannel(channelId: string, direction: -1 | 1) {
|
||||
const category = categories.find(c => c.channels.includes(channelId));
|
||||
if (!category) return;
|
||||
export function moveChannel(channelId: string, direction: -1 | 1) {
|
||||
const category = currentUserCategories.find(c => c.channels.includes(channelId));
|
||||
if (category == null) return;
|
||||
|
||||
const a = category.channels.indexOf(channelId);
|
||||
const b = a + direction;
|
||||
|
||||
swapElementsInArray(category.channels, a, b);
|
||||
|
||||
await saveCats(categories);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// migrate data
|
||||
const getPinDMsPins = () => (Settings.plugins.PinDMs.pinnedDMs || void 0)?.split(",") as string[] | undefined;
|
||||
|
||||
async function migratePinDMs() {
|
||||
if (categories.some(m => m.id === "oldPins")) {
|
||||
return await DataStore.set(CATEGORY_MIGRATED_PINDMS_KEY, true);
|
||||
// TODO: Remove DataStore PinnedDms migration once enough time has passed
|
||||
async function migrateData() {
|
||||
if (Settings.plugins.PinDMs.dmSectioncollapsed != null) {
|
||||
settings.store.dmSectionCollapsed = Settings.plugins.PinDMs.dmSectioncollapsed;
|
||||
delete Settings.plugins.PinDMs.dmSectioncollapsed;
|
||||
}
|
||||
|
||||
const pindmspins = getPinDMsPins();
|
||||
const dataStoreKeys = await DataStore.keys();
|
||||
const pinDmsKeys = dataStoreKeys.map(key => String(key)).filter(key => key.startsWith(CATEGORY_BASE_KEY));
|
||||
|
||||
// we dont want duplicate pins
|
||||
const difference = [...new Set(pindmspins)]?.filter(m => !categories.some(c => c.channels.includes(m)));
|
||||
if (difference?.length) {
|
||||
categories.push({
|
||||
id: "oldPins",
|
||||
name: "Pins",
|
||||
color: DEFAULT_COLOR,
|
||||
channels: difference
|
||||
});
|
||||
if (pinDmsKeys.length === 0) return;
|
||||
|
||||
for (const pinDmsKey of pinDmsKeys) {
|
||||
const categories = await DataStore.get<Category[]>(pinDmsKey);
|
||||
if (categories == null) continue;
|
||||
|
||||
const userId = pinDmsKey.replace(CATEGORY_BASE_KEY, "");
|
||||
settings.store.userBasedCategoryList[userId] = categories;
|
||||
|
||||
await DataStore.del(pinDmsKey);
|
||||
}
|
||||
|
||||
await DataStore.set(CATEGORY_MIGRATED_PINDMS_KEY, true);
|
||||
}
|
||||
|
||||
async function migrateOldCategories(userId: string) {
|
||||
const oldCats = await DataStore.get<Category[]>(OLD_CATEGORY_KEY + userId);
|
||||
// dont want to migrate if the user has already has categories.
|
||||
if (categories.length === 0 && oldCats?.length) {
|
||||
categories.push(...(oldCats.filter(m => m.id !== "oldPins")));
|
||||
}
|
||||
await DataStore.set(CATEGORY_MIGRATED_KEY, true);
|
||||
}
|
||||
|
||||
export async function migrateData(userId: string) {
|
||||
const m1 = await DataStore.get(CATEGORY_MIGRATED_KEY), m2 = await DataStore.get(CATEGORY_MIGRATED_PINDMS_KEY);
|
||||
if (m1 && m2) return;
|
||||
|
||||
// want to migrate the old categories first and then slove any conflicts with the PinDMs pins
|
||||
if (!m1) await migrateOldCategories(userId);
|
||||
if (!m2) await migratePinDMs();
|
||||
|
||||
await saveCats(categories);
|
||||
await Promise.all([DataStore.del(CATEGORY_MIGRATED_PINDMS_KEY), DataStore.del(CATEGORY_MIGRATED_KEY), DataStore.del(OLD_CATEGORY_KEY)]);
|
||||
}
|
||||
|
|
|
@ -12,13 +12,13 @@ import { Devs } from "@utils/constants";
|
|||
import { classes } from "@utils/misc";
|
||||
import definePlugin, { OptionType, StartAt } from "@utils/types";
|
||||
import { findByPropsLazy, findStoreLazy } from "@webpack";
|
||||
import { ContextMenuApi, FluxDispatcher, Menu, React } from "@webpack/common";
|
||||
import { Clickable, ContextMenuApi, FluxDispatcher, Menu, React } from "@webpack/common";
|
||||
import { Channel } from "discord-types/general";
|
||||
|
||||
import { contextMenus } from "./components/contextMenu";
|
||||
import { openCategoryModal, requireSettingsMenu } from "./components/CreateCategoryModal";
|
||||
import { DEFAULT_CHUNK_SIZE } from "./constants";
|
||||
import { canMoveCategory, canMoveCategoryInDirection, categories, Category, categoryLen, collapseCategory, getAllUncollapsedChannels, getSections, init, isPinned, moveCategory, removeCategory } from "./data";
|
||||
import { canMoveCategory, canMoveCategoryInDirection, Category, categoryLen, collapseCategory, getAllUncollapsedChannels, getCategoryByIndex, getSections, init, isPinned, moveCategory, removeCategory, usePinnedDms } from "./data";
|
||||
|
||||
interface ChannelComponentProps {
|
||||
children: React.ReactNode,
|
||||
|
@ -26,13 +26,11 @@ interface ChannelComponentProps {
|
|||
selected: boolean;
|
||||
}
|
||||
|
||||
|
||||
const headerClasses = findByPropsLazy("privateChannelsHeaderContainer");
|
||||
|
||||
export const PrivateChannelSortStore = findStoreLazy("PrivateChannelSortStore") as { getPrivateChannelIds: () => string[]; };
|
||||
|
||||
export let instance: any;
|
||||
export const forceUpdate = () => instance?.props?._forceUpdate?.();
|
||||
|
||||
export const enum PinOrder {
|
||||
LastMessage,
|
||||
|
@ -46,21 +44,28 @@ export const settings = definePluginSettings({
|
|||
options: [
|
||||
{ label: "Most recent message", value: PinOrder.LastMessage, default: true },
|
||||
{ label: "Custom (right click channels to reorder)", value: PinOrder.Custom }
|
||||
],
|
||||
onChange: () => forceUpdate()
|
||||
]
|
||||
},
|
||||
|
||||
dmSectioncollapsed: {
|
||||
canCollapseDmSection: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Collapse DM sections",
|
||||
description: "Allow uncategorised DMs section to be collapsable",
|
||||
default: false
|
||||
},
|
||||
dmSectionCollapsed: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Collapse DM section",
|
||||
default: false,
|
||||
onChange: () => forceUpdate()
|
||||
hidden: true
|
||||
},
|
||||
userBasedCategoryList: {
|
||||
type: OptionType.CUSTOM,
|
||||
default: {} as Record<string, Category[]>
|
||||
}
|
||||
});
|
||||
|
||||
export default definePlugin({
|
||||
name: "PinDMs",
|
||||
description: "Allows you to pin private channels to the top of your DM list. To pin/unpin or reorder pins, right click DMs",
|
||||
description: "Allows you to pin private channels to the top of your DM list. To pin/unpin or re-order pins, right click DMs",
|
||||
authors: [Devs.Ven, Devs.Aria],
|
||||
settings,
|
||||
contextMenus,
|
||||
|
@ -124,8 +129,8 @@ export default definePlugin({
|
|||
{
|
||||
find: ".FRIENDS},\"friends\"",
|
||||
replacement: {
|
||||
match: /(?<=\i=\i=>{).{1,100}premiumTabSelected.{0,950}showDMHeader:.+?,/,
|
||||
replace: "let forceUpdate = Vencord.Util.useForceUpdater();$&_forceUpdate:forceUpdate,"
|
||||
match: /let{showLibrary:\i,/,
|
||||
replace: "$self.usePinnedDms();$&"
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -149,6 +154,7 @@ export default definePlugin({
|
|||
}
|
||||
},
|
||||
],
|
||||
|
||||
sections: null as number[] | null,
|
||||
|
||||
set _instance(i: any) {
|
||||
|
@ -162,6 +168,7 @@ export default definePlugin({
|
|||
CONNECTION_OPEN: init,
|
||||
},
|
||||
|
||||
usePinnedDms,
|
||||
isPinned,
|
||||
categoryLen,
|
||||
getSections,
|
||||
|
@ -186,11 +193,11 @@ export default definePlugin({
|
|||
},
|
||||
|
||||
makeSpanProps() {
|
||||
return {
|
||||
return settings.store.canCollapseDmSection ? {
|
||||
onClick: () => this.collapseDMList(),
|
||||
role: "button",
|
||||
style: { cursor: "pointer" }
|
||||
};
|
||||
} : undefined;
|
||||
},
|
||||
|
||||
getChunkSize() {
|
||||
|
@ -210,30 +217,27 @@ export default definePlugin({
|
|||
},
|
||||
|
||||
isChannelIndex(sectionIndex: number, channelIndex: number) {
|
||||
if (settings.store.dmSectioncollapsed && sectionIndex !== 0)
|
||||
if (settings.store.canCollapseDmSection && settings.store.dmSectionCollapsed && sectionIndex !== 0) {
|
||||
return true;
|
||||
const cat = categories[sectionIndex - 1];
|
||||
return this.isCategoryIndex(sectionIndex) && (cat?.channels?.length === 0 || cat?.channels[channelIndex]);
|
||||
},
|
||||
}
|
||||
|
||||
isDMSectioncollapsed() {
|
||||
return settings.store.dmSectioncollapsed;
|
||||
const category = getCategoryByIndex(sectionIndex - 1);
|
||||
return this.isCategoryIndex(sectionIndex) && (category?.channels?.length === 0 || category?.channels[channelIndex]);
|
||||
},
|
||||
|
||||
collapseDMList() {
|
||||
settings.store.dmSectioncollapsed = !settings.store.dmSectioncollapsed;
|
||||
forceUpdate();
|
||||
settings.store.dmSectionCollapsed = !settings.store.dmSectionCollapsed;
|
||||
},
|
||||
|
||||
isChannelHidden(categoryIndex: number, channelIndex: number) {
|
||||
if (categoryIndex === 0) return false;
|
||||
|
||||
if (settings.store.dmSectioncollapsed && this.getSections().length + 1 === categoryIndex)
|
||||
if (settings.store.canCollapseDmSection && settings.store.dmSectionCollapsed && this.getSections().length + 1 === categoryIndex)
|
||||
return true;
|
||||
|
||||
if (!this.instance || !this.isChannelIndex(categoryIndex, channelIndex)) return false;
|
||||
|
||||
const category = categories[categoryIndex - 1];
|
||||
const category = getCategoryByIndex(categoryIndex - 1);
|
||||
if (!category) return false;
|
||||
|
||||
return category.collapsed && this.instance.props.selectedChannelId !== this.getCategoryChannels(category)[channelIndex];
|
||||
|
@ -251,18 +255,12 @@ export default definePlugin({
|
|||
},
|
||||
|
||||
renderCategory: ErrorBoundary.wrap(({ section }: { section: number; }) => {
|
||||
const category = categories[section - 1];
|
||||
|
||||
const category = getCategoryByIndex(section - 1);
|
||||
if (!category) return null;
|
||||
|
||||
return (
|
||||
<h2
|
||||
className={classes(headerClasses.privateChannelsHeaderContainer, "vc-pindms-section-container", category.collapsed ? "vc-pindms-collapsed" : "")}
|
||||
style={{ color: `#${category.color.toString(16).padStart(6, "0")}` }}
|
||||
onClick={async () => {
|
||||
await collapseCategory(category.id, !category.collapsed);
|
||||
forceUpdate();
|
||||
}}
|
||||
<Clickable
|
||||
onClick={() => collapseCategory(category.id, !category.collapsed)}
|
||||
onContextMenu={e => {
|
||||
ContextMenuApi.openContextMenu(e, () => (
|
||||
<Menu.Menu
|
||||
|
@ -284,14 +282,14 @@ export default definePlugin({
|
|||
canMoveCategoryInDirection(category.id, -1) && <Menu.MenuItem
|
||||
id="vc-pindms-move-category-up"
|
||||
label="Move Up"
|
||||
action={() => moveCategory(category.id, -1).then(() => forceUpdate())}
|
||||
action={() => moveCategory(category.id, -1)}
|
||||
/>
|
||||
}
|
||||
{
|
||||
canMoveCategoryInDirection(category.id, 1) && <Menu.MenuItem
|
||||
id="vc-pindms-move-category-down"
|
||||
label="Move Down"
|
||||
action={() => moveCategory(category.id, 1).then(() => forceUpdate())}
|
||||
action={() => moveCategory(category.id, 1)}
|
||||
/>
|
||||
}
|
||||
</>
|
||||
|
@ -304,13 +302,17 @@ export default definePlugin({
|
|||
id="vc-pindms-delete-category"
|
||||
color="danger"
|
||||
label="Delete Category"
|
||||
action={() => removeCategory(category.id).then(() => forceUpdate())}
|
||||
action={() => removeCategory(category.id)}
|
||||
/>
|
||||
|
||||
|
||||
</Menu.Menu>
|
||||
));
|
||||
}}
|
||||
>
|
||||
<h2
|
||||
className={classes(headerClasses.privateChannelsHeaderContainer, "vc-pindms-section-container", category.collapsed ? "vc-pindms-collapsed" : "")}
|
||||
style={{ color: `#${category.color.toString(16).padStart(6, "0")}` }}
|
||||
>
|
||||
<span className={headerClasses.headerText}>
|
||||
{category?.name ?? "uh oh"}
|
||||
|
@ -319,6 +321,7 @@ export default definePlugin({
|
|||
<path fill="currentColor" d="M9.3 5.3a1 1 0 0 0 0 1.4l5.29 5.3-5.3 5.3a1 1 0 1 0 1.42 1.4l6-6a1 1 0 0 0 0-1.4l-6-6a1 1 0 0 0-1.42 0Z"></path>
|
||||
</svg>
|
||||
</h2>
|
||||
</Clickable>
|
||||
);
|
||||
}, { noop: true }),
|
||||
|
||||
|
@ -341,7 +344,7 @@ export default definePlugin({
|
|||
},
|
||||
|
||||
getChannel(sectionIndex: number, index: number, channels: Record<string, Channel>) {
|
||||
const category = categories[sectionIndex - 1];
|
||||
const category = getCategoryByIndex(sectionIndex - 1);
|
||||
if (!category) return { channel: null, category: null };
|
||||
|
||||
const channelId = this.getCategoryChannels(category)[index];
|
||||
|
|
|
@ -18,9 +18,9 @@
|
|||
|
||||
import "./style.css";
|
||||
|
||||
import { addBadge, BadgePosition, BadgeUserArgs, ProfileBadge, removeBadge } from "@api/Badges";
|
||||
import { addDecorator, removeDecorator } from "@api/MemberListDecorators";
|
||||
import { addDecoration, removeDecoration } from "@api/MessageDecorations";
|
||||
import { addProfileBadge, BadgePosition, BadgeUserArgs, ProfileBadge, removeProfileBadge } from "@api/Badges";
|
||||
import { addMemberListDecorator, removeMemberListDecorator } from "@api/MemberListDecorators";
|
||||
import { addMessageDecoration, removeMessageDecoration } from "@api/MessageDecorations";
|
||||
import { Settings } from "@api/Settings";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Devs } from "@utils/constants";
|
||||
|
@ -172,26 +172,26 @@ const badge: ProfileBadge = {
|
|||
const indicatorLocations = {
|
||||
list: {
|
||||
description: "In the member list",
|
||||
onEnable: () => addDecorator("platform-indicator", props =>
|
||||
onEnable: () => addMemberListDecorator("platform-indicator", props =>
|
||||
<ErrorBoundary noop>
|
||||
<PlatformIndicator user={props.user} small={true} />
|
||||
</ErrorBoundary>
|
||||
),
|
||||
onDisable: () => removeDecorator("platform-indicator")
|
||||
onDisable: () => removeMemberListDecorator("platform-indicator")
|
||||
},
|
||||
badges: {
|
||||
description: "In user profiles, as badges",
|
||||
onEnable: () => addBadge(badge),
|
||||
onDisable: () => removeBadge(badge)
|
||||
onEnable: () => addProfileBadge(badge),
|
||||
onDisable: () => removeProfileBadge(badge)
|
||||
},
|
||||
messages: {
|
||||
description: "Inside messages",
|
||||
onEnable: () => addDecoration("platform-indicator", props =>
|
||||
onEnable: () => addMessageDecoration("platform-indicator", props =>
|
||||
<ErrorBoundary noop>
|
||||
<PlatformIndicator user={props.message?.author} wantTopMargin={true} />
|
||||
</ErrorBoundary>
|
||||
),
|
||||
onDisable: () => removeDecoration("platform-indicator")
|
||||
onDisable: () => removeMessageDecoration("platform-indicator")
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { addChatBarButton, ChatBarButton, removeChatBarButton } from "@api/ChatButtons";
|
||||
import { ChatBarButton, ChatBarButtonFactory } from "@api/ChatButtons";
|
||||
import { generateId, sendBotMessage } from "@api/Commands";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { StartAt } from "@utils/types";
|
||||
|
@ -73,7 +73,7 @@ const getAttachments = async (channelId: string) =>
|
|||
);
|
||||
|
||||
|
||||
const PreviewButton: ChatBarButton = ({ isMainChat, isEmpty, type: { attachments } }) => {
|
||||
const PreviewButton: ChatBarButtonFactory = ({ isMainChat, isEmpty, type: { attachments } }) => {
|
||||
const channelId = SelectedChannelStore.getChannelId();
|
||||
const draft = useStateFromStores([DraftStore], () => getDraft(channelId));
|
||||
|
||||
|
@ -121,11 +121,9 @@ export default definePlugin({
|
|||
name: "PreviewMessage",
|
||||
description: "Lets you preview your message before sending it.",
|
||||
authors: [Devs.Aria],
|
||||
dependencies: ["ChatInputButtonAPI"],
|
||||
// start early to ensure we're the first plugin to add our button
|
||||
// This makes the popping in less awkward
|
||||
startAt: StartAt.Init,
|
||||
|
||||
start: () => addChatBarButton("previewMessage", PreviewButton),
|
||||
stop: () => removeChatBarButton("previewMessage"),
|
||||
renderChatBarButton: PreviewButton,
|
||||
});
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { addButton, removeButton } from "@api/MessagePopover";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { insertTextIntoChatInputBox } from "@utils/discord";
|
||||
import definePlugin from "@utils/types";
|
||||
|
@ -26,10 +25,8 @@ export default definePlugin({
|
|||
name: "QuickMention",
|
||||
authors: [Devs.kemo],
|
||||
description: "Adds a quick mention button to the message actions bar",
|
||||
dependencies: ["MessagePopoverAPI"],
|
||||
|
||||
start() {
|
||||
addButton("QuickMention", msg => {
|
||||
renderMessagePopoverButton(msg) {
|
||||
const channel = ChannelStore.getChannel(msg.channel_id);
|
||||
if (channel.guild_id && !PermissionStore.can(PermissionsBits.SEND_MESSAGES, channel)) return null;
|
||||
|
||||
|
@ -40,10 +37,6 @@ export default definePlugin({
|
|||
channel,
|
||||
onClick: () => insertTextIntoChatInputBox(`<@${msg.author.id}> `)
|
||||
};
|
||||
});
|
||||
},
|
||||
stop() {
|
||||
removeButton("QuickMention");
|
||||
},
|
||||
|
||||
Icon: () => (
|
||||
|
|
|
@ -9,16 +9,11 @@ import "./style.css";
|
|||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { filters, findByPropsLazy, mapMangledModuleLazy } from "@webpack";
|
||||
import { Timestamp } from "@webpack/common";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { DateUtils, Timestamp } from "@webpack/common";
|
||||
import type { Message } from "discord-types/general";
|
||||
import type { HTMLAttributes } from "react";
|
||||
|
||||
const { calendarFormat, dateFormat, isSameDay } = mapMangledModuleLazy("millisecondsInUnit:", {
|
||||
calendarFormat: filters.byCode("sameElse"),
|
||||
dateFormat: filters.byCode('":'),
|
||||
isSameDay: filters.byCode("Math.abs(+"),
|
||||
});
|
||||
const MessageClasses = findByPropsLazy("separator", "latin24CompactTimeStamp");
|
||||
|
||||
function Sep(props: HTMLAttributes<HTMLElement>) {
|
||||
|
@ -46,14 +41,14 @@ function ReplyTimestamp({
|
|||
return (
|
||||
<Timestamp
|
||||
className="vc-reply-timestamp"
|
||||
compact={isSameDay(refTimestamp, baseTimestamp)}
|
||||
compact={DateUtils.isSameDay(refTimestamp, baseTimestamp)}
|
||||
timestamp={refTimestamp}
|
||||
isInline={false}
|
||||
>
|
||||
<Sep>[</Sep>
|
||||
{isSameDay(refTimestamp, baseTimestamp)
|
||||
? dateFormat(refTimestamp, "LT")
|
||||
: calendarFormat(refTimestamp)
|
||||
{DateUtils.isSameDay(refTimestamp, baseTimestamp)
|
||||
? DateUtils.dateFormat(refTimestamp, "LT")
|
||||
: DateUtils.calendarFormat(refTimestamp)
|
||||
}
|
||||
<Sep>]</Sep>
|
||||
</Timestamp>
|
||||
|
|
|
@ -21,7 +21,7 @@ import definePlugin from "@utils/types";
|
|||
import { findByPropsLazy } from "@webpack";
|
||||
|
||||
const SpoilerClasses = findByPropsLazy("spoilerContent");
|
||||
const MessagesClasses = findByPropsLazy("messagesWrapper");
|
||||
const MessagesClasses = findByPropsLazy("messagesWrapper", "navigationDescription");
|
||||
|
||||
export default definePlugin({
|
||||
name: "RevealAllSpoilers",
|
||||
|
|
|
@ -108,7 +108,7 @@ export default definePlugin({
|
|||
|
||||
patches: [
|
||||
{
|
||||
find: "#{intl::MESSAGE_ACTIONS_MENU_LABEL}",
|
||||
find: "#{intl::MESSAGE_ACTIONS_MENU_LABEL}),shouldHideMediaOptions:",
|
||||
replacement: {
|
||||
match: /favoriteableType:\i,(?<=(\i)\.getAttribute\("data-type"\).+?)/,
|
||||
replace: (m, target) => `${m}reverseImageSearchType:${target}.getAttribute("data-role"),`
|
||||
|
|
|
@ -159,7 +159,7 @@ export default LazyComponent(() => {
|
|||
onClick={() => openBlockModal()}
|
||||
/>
|
||||
)}
|
||||
{review.sender.badges.map(badge => <ReviewBadge {...badge} />)}
|
||||
{review.sender.badges.map((badge, idx) => <ReviewBadge key={idx} {...badge} />)}
|
||||
|
||||
{
|
||||
!settings.store.hideTimestamps && review.type !== ReviewType.System && (
|
||||
|
@ -170,7 +170,13 @@ export default LazyComponent(() => {
|
|||
|
||||
<div className={cl("review-comment")}>
|
||||
{(review.comment.length > 200 && !showAll)
|
||||
? [Parser.parseGuildEventDescription(review.comment.substring(0, 200)), "...", <br />, (<a onClick={() => setShowAll(true)}>Read more</a>)]
|
||||
? (
|
||||
<>
|
||||
{Parser.parseGuildEventDescription(review.comment.substring(0, 200))}...
|
||||
<br />
|
||||
<a onClick={() => setShowAll(true)}>Read more</a>]
|
||||
</>
|
||||
)
|
||||
: Parser.parseGuildEventDescription(review.comment)}
|
||||
</div>
|
||||
|
||||
|
|
|
@ -18,8 +18,7 @@
|
|||
|
||||
import "./styles.css";
|
||||
|
||||
import { addChatBarButton, ChatBarButton, removeChatBarButton } from "@api/ChatButtons";
|
||||
import { addPreSendListener, removePreSendListener } from "@api/MessageEvents";
|
||||
import { ChatBarButton, ChatBarButtonFactory } from "@api/ChatButtons";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { classNameFactory } from "@api/Styles";
|
||||
import { Devs } from "@utils/constants";
|
||||
|
@ -123,7 +122,7 @@ function PickerModal({ rootProps, close }: { rootProps: ModalProps, close(): voi
|
|||
);
|
||||
}
|
||||
|
||||
const ChatBarIcon: ChatBarButton = ({ isMainChat }) => {
|
||||
const ChatBarIcon: ChatBarButtonFactory = ({ isMainChat }) => {
|
||||
if (!isMainChat) return null;
|
||||
|
||||
return (
|
||||
|
@ -147,7 +146,7 @@ const ChatBarIcon: ChatBarButton = ({ isMainChat }) => {
|
|||
viewBox="0 0 24 24"
|
||||
style={{ scale: "1.2" }}
|
||||
>
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<g fill="none" fillRule="evenodd">
|
||||
<path fill="currentColor" d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.11 0-1.99.9-1.99 2L3 19a2 2 0 0 0 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v11zM7 10h5v5H7v-5z" />
|
||||
<rect width="24" height="24" />
|
||||
</g>
|
||||
|
@ -160,22 +159,14 @@ export default definePlugin({
|
|||
name: "SendTimestamps",
|
||||
description: "Send timestamps easily via chat box button & text shortcuts. Read the extended description!",
|
||||
authors: [Devs.Ven, Devs.Tyler, Devs.Grzesiek11],
|
||||
dependencies: ["MessageEventsAPI", "ChatInputButtonAPI"],
|
||||
|
||||
settings,
|
||||
|
||||
start() {
|
||||
addChatBarButton("SendTimestamps", ChatBarIcon);
|
||||
this.listener = addPreSendListener((_, msg) => {
|
||||
renderChatBarButton: ChatBarIcon,
|
||||
|
||||
onBeforeMessageSend(_, msg) {
|
||||
if (settings.store.replaceMessageContents) {
|
||||
msg.content = msg.content.replace(/`\d{1,2}:\d{2} ?(?:AM|PM)?`/gi, parseTime);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
stop() {
|
||||
removeChatBarButton("SendTimestamps");
|
||||
removePreSendListener(this.listener);
|
||||
},
|
||||
|
||||
settingsAboutComponent() {
|
||||
|
|
|
@ -31,7 +31,8 @@ export function openGuildInfoModal(guild: Guild) {
|
|||
const enum Tabs {
|
||||
ServerInfo,
|
||||
Friends,
|
||||
BlockedUsers
|
||||
BlockedUsers,
|
||||
IgnoredUsers
|
||||
}
|
||||
|
||||
interface GuildProps {
|
||||
|
@ -44,7 +45,8 @@ interface RelationshipProps extends GuildProps {
|
|||
|
||||
const fetched = {
|
||||
friends: false,
|
||||
blocked: false
|
||||
blocked: false,
|
||||
ignored: false
|
||||
};
|
||||
|
||||
function renderTimestamp(timestamp: number) {
|
||||
|
@ -56,10 +58,12 @@ function renderTimestamp(timestamp: number) {
|
|||
function GuildInfoModal({ guild }: GuildProps) {
|
||||
const [friendCount, setFriendCount] = useState<number>();
|
||||
const [blockedCount, setBlockedCount] = useState<number>();
|
||||
const [ignoredCount, setIgnoredCount] = useState<number>();
|
||||
|
||||
useEffect(() => {
|
||||
fetched.friends = false;
|
||||
fetched.blocked = false;
|
||||
fetched.ignored = false;
|
||||
}, []);
|
||||
|
||||
const [currentTab, setCurrentTab] = useState(Tabs.ServerInfo);
|
||||
|
@ -132,12 +136,19 @@ function GuildInfoModal({ guild }: GuildProps) {
|
|||
>
|
||||
Blocked Users{blockedCount !== undefined ? ` (${blockedCount})` : ""}
|
||||
</TabBar.Item>
|
||||
<TabBar.Item
|
||||
className={cl("tab", { selected: currentTab === Tabs.IgnoredUsers })}
|
||||
id={Tabs.IgnoredUsers}
|
||||
>
|
||||
Ignored Users{ignoredCount !== undefined ? ` (${ignoredCount})` : ""}
|
||||
</TabBar.Item>
|
||||
</TabBar>
|
||||
|
||||
<div className={cl("tab-content")}>
|
||||
{currentTab === Tabs.ServerInfo && <ServerInfoTab guild={guild} />}
|
||||
{currentTab === Tabs.Friends && <FriendsTab guild={guild} setCount={setFriendCount} />}
|
||||
{currentTab === Tabs.BlockedUsers && <BlockedUsersTab guild={guild} setCount={setBlockedCount} />}
|
||||
{currentTab === Tabs.IgnoredUsers && <IgnoredUserTab guild={guild} setCount={setIgnoredCount} />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -211,7 +222,13 @@ function BlockedUsersTab({ guild, setCount }: RelationshipProps) {
|
|||
return UserList("blocked", guild, blockedIds, setCount);
|
||||
}
|
||||
|
||||
function UserList(type: "friends" | "blocked", guild: Guild, ids: string[], setCount: (count: number) => void) {
|
||||
function IgnoredUserTab({ guild, setCount }: RelationshipProps) {
|
||||
const ignoredIds = Object.keys(RelationshipStore.getRelationships()).filter(id => RelationshipStore.isIgnored(id));
|
||||
return UserList("ignored", guild, ignoredIds, setCount);
|
||||
}
|
||||
|
||||
|
||||
function UserList(type: "friends" | "blocked" | "ignored", guild: Guild, ids: string[], setCount: (count: number) => void) {
|
||||
const missing = [] as string[];
|
||||
const members = [] as string[];
|
||||
|
||||
|
@ -247,6 +264,7 @@ function UserList(type: "friends" | "blocked", guild: Guild, ids: string[], setC
|
|||
<ScrollerThin fade className={cl("scroller")}>
|
||||
{members.map(id =>
|
||||
<FriendRow
|
||||
key={id}
|
||||
user={UserStore.getUser(id)}
|
||||
status={PresenceStore.getStatus(id) || "offline"}
|
||||
onSelect={() => openUserProfile(id)}
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
*/
|
||||
|
||||
import { Clipboard } from "@webpack/common";
|
||||
import { JSX } from "react";
|
||||
|
||||
import { cl } from "../utils/misc";
|
||||
import { CopyButton } from "./CopyButton";
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
import type { IThemedToken } from "@vap/shiki";
|
||||
import { hljs } from "@webpack/common";
|
||||
import { JSX } from "react";
|
||||
|
||||
import { cl } from "../utils/misc";
|
||||
import { ThemeBase } from "./Highlighter";
|
||||
|
@ -41,12 +42,12 @@ export const Code = ({
|
|||
|
||||
if (useHljs) {
|
||||
try {
|
||||
const { value: hljsHtml } = hljs.highlight(lang!, content, true);
|
||||
const { value: hljsHtml } = hljs.highlight(content, { language: lang!, ignoreIllegals: true });
|
||||
lines = hljsHtml
|
||||
.split("\n")
|
||||
.map((line, i) => <span key={i} dangerouslySetInnerHTML={{ __html: line }} />);
|
||||
} catch {
|
||||
lines = content.split("\n").map(line => <span>{line}</span>);
|
||||
lines = content.split("\n").map((line, idx) => <span key={idx}>{line}</span>);
|
||||
}
|
||||
} else {
|
||||
const renderTokens =
|
||||
|
@ -55,11 +56,11 @@ export const Code = ({
|
|||
.split("\n")
|
||||
.map(line => [{ color: theme.plainColor, content: line } as IThemedToken]);
|
||||
|
||||
lines = renderTokens.map(line => {
|
||||
lines = renderTokens.map((line, idx) => {
|
||||
// [Cynthia] this makes it so when you highlight the codeblock
|
||||
// empty lines are also selected and copied when you Ctrl+C.
|
||||
if (line.length === 0) {
|
||||
return <span>{"\n"}</span>;
|
||||
return <span key={idx}>{"\n"}</span>;
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -97,7 +97,7 @@ function ConnectionsComponent({ id, theme }: { id: string, theme: string; }) {
|
|||
gap: getSpacingPx(settings.store.iconSpacing),
|
||||
flexWrap: "wrap"
|
||||
}}>
|
||||
{connections.map(connection => <CompactConnectionComponent connection={connection} theme={theme} />)}
|
||||
{connections.map(connection => <CompactConnectionComponent connection={connection} theme={theme} key={connection.id} />)}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
@ -137,6 +137,7 @@ function CompactConnectionComponent({ connection, theme }: { connection: Connect
|
|||
className="vc-user-connection"
|
||||
href={url}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
onClick={e => {
|
||||
if (Vencord.Plugins.isPluginEnabled("OpenInApp")) {
|
||||
const OpenInApp = Vencord.Plugins.plugins.OpenInApp as any as typeof import("../openInApp").default;
|
||||
|
|
|
@ -268,7 +268,7 @@ function HiddenChannelLockScreen({ channel }: { channel: ExtendedChannel; }) {
|
|||
<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} />)}
|
||||
{availableTags.map(tag => <TagComponent tag={tag} key={tag.id} />)}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
|
|
@ -88,7 +88,7 @@ export default definePlugin({
|
|||
},
|
||||
// Make channels we dont have access to be the same level as normal ones
|
||||
{
|
||||
match: /(activeJoinedRelevantThreads:.{0,50}VIEW_CHANNEL.+?renderLevel:(.+?),threadIds.+?renderLevel:).+?(?=,threadIds)/g,
|
||||
match: /(this\.record\)\?{renderLevel:(.+?),threadIds.+?renderLevel:).+?(?=,threadIds)/g,
|
||||
replace: (_, rest, defaultRenderLevel) => `${rest}${defaultRenderLevel}`
|
||||
},
|
||||
// Remove permission checking for getRenderLevel function
|
||||
|
@ -224,12 +224,12 @@ export default definePlugin({
|
|||
find: "Missing channel in Channel.renderHeaderToolbar",
|
||||
replacement: [
|
||||
{
|
||||
match: /(?<="renderHeaderToolbar",\(\)=>{.+?case \i\.\i\.GUILD_TEXT:)(?=.+?(\i\.push.{0,50}channel:(\i)},"notifications"\)\)))(?<=isLurking:(\i).+?)/,
|
||||
replace: (_, pushNotificationButtonExpression, channel, isLurking) => `if(!${isLurking}&&$self.isHiddenChannel(${channel})){${pushNotificationButtonExpression};break;}`
|
||||
match: /"renderHeaderToolbar",\(\)=>{.+?case \i\.\i\.GUILD_TEXT:(?=.+?(\i\.push.{0,50}channel:(\i)},"notifications"\)\)))(?<=isLurking:(\i).+?)/,
|
||||
replace: (m, pushNotificationButtonExpression, channel, isLurking) => `${m}if(!${isLurking}&&$self.isHiddenChannel(${channel})){${pushNotificationButtonExpression};break;}`
|
||||
},
|
||||
{
|
||||
match: /(?<="renderHeaderToolbar",\(\)=>{.+?case \i\.\i\.GUILD_MEDIA:)(?=.+?(\i\.push.{0,40}channel:(\i)},"notifications"\)\)))(?<=isLurking:(\i).+?)/,
|
||||
replace: (_, pushNotificationButtonExpression, channel, isLurking) => `if(!${isLurking}&&$self.isHiddenChannel(${channel})){${pushNotificationButtonExpression};break;}`
|
||||
match: /"renderHeaderToolbar",\(\)=>{.+?case \i\.\i\.GUILD_MEDIA:(?=.+?(\i\.push.{0,40}channel:(\i)},"notifications"\)\)))(?<=isLurking:(\i).+?)/,
|
||||
replace: (m, pushNotificationButtonExpression, channel, isLurking) => `${m}if(!${isLurking}&&$self.isHiddenChannel(${channel})){${pushNotificationButtonExpression};break;}`
|
||||
},
|
||||
{
|
||||
match: /"renderMobileToolbar",\(\)=>{.+?case \i\.\i\.GUILD_DIRECTORY:(?<=let{channel:(\i).+?)/,
|
||||
|
|
|
@ -58,7 +58,7 @@ export default definePlugin({
|
|||
},
|
||||
},
|
||||
{
|
||||
find: /context:\i,checkElevated:!1\}\),\i\.\i.{0,200}autoTrackExposure/,
|
||||
find: /,checkElevated:!1}\),\i\.\i\)}(?<=getCurrentUser\(\);return.+?)/,
|
||||
predicate: () => settings.store.showModView,
|
||||
replacement: {
|
||||
match: /return \i\.\i\(\i\.\i\(\{user:\i,context:\i,checkElevated:!1\}\),\i\.\i\)/,
|
||||
|
@ -67,7 +67,7 @@ export default definePlugin({
|
|||
},
|
||||
// fixes a bug where Members page must be loaded to see highest role, why is Discord depending on MemberSafetyStore.getEnhancedMember for something that can be obtained here?
|
||||
{
|
||||
find: "#{intl::GUILD_MEMBER_MOD_VIEW_PERMISSION_GRANTED_BY_ARIA_LABEL}",
|
||||
find: "#{intl::GUILD_MEMBER_MOD_VIEW_PERMISSION_GRANTED_BY_ARIA_LABEL}),allowOverflow:",
|
||||
predicate: () => settings.store.showModView,
|
||||
replacement: {
|
||||
match: /(role:)\i(?=,guildId.{0,100}role:(\i\[))/,
|
||||
|
@ -76,7 +76,7 @@ export default definePlugin({
|
|||
},
|
||||
// allows you to open mod view on yourself
|
||||
{
|
||||
find: ".MEMBER_SAFETY,{modViewPanel:",
|
||||
find: 'action:"PRESS_MOD_VIEW",icon:',
|
||||
predicate: () => settings.store.showModView,
|
||||
replacement: {
|
||||
match: /\i(?=\?null)/,
|
||||
|
|
|
@ -84,13 +84,12 @@ export default definePlugin({
|
|||
],
|
||||
|
||||
TooltipWrapper: ErrorBoundary.wrap(({ message, children, text }: { message: Message; children: FunctionComponent<any>; text: ReactNode; }) => {
|
||||
if (settings.store.displayStyle === DisplayStyle.Tooltip) return <Tooltip
|
||||
children={children}
|
||||
text={renderTimeout(message, false)}
|
||||
/>;
|
||||
if (settings.store.displayStyle === DisplayStyle.Tooltip)
|
||||
return <Tooltip text={renderTimeout(message, false)}>{children}</Tooltip>;
|
||||
|
||||
return (
|
||||
<div className="vc-std-wrapper">
|
||||
<Tooltip text={text} children={children} />
|
||||
<Tooltip text={text}>{children}</Tooltip>
|
||||
<Text variant="text-md/normal" color="status-danger">
|
||||
{renderTimeout(message, true)} timeout remaining
|
||||
</Text>
|
||||
|
|
|
@ -16,8 +16,8 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { addChatBarButton, ChatBarButton, removeChatBarButton } from "@api/ChatButtons";
|
||||
import { addPreSendListener, removePreSendListener, SendListener } from "@api/MessageEvents";
|
||||
import { ChatBarButton, ChatBarButtonFactory } from "@api/ChatButtons";
|
||||
import { addMessagePreSendListener, MessageSendListener, removeMessagePreSendListener } from "@api/MessageEvents";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
|
@ -41,7 +41,7 @@ const settings = definePluginSettings({
|
|||
}
|
||||
});
|
||||
|
||||
const SilentMessageToggle: ChatBarButton = ({ isMainChat }) => {
|
||||
const SilentMessageToggle: ChatBarButtonFactory = ({ isMainChat }) => {
|
||||
const [enabled, setEnabled] = useState(lastState);
|
||||
|
||||
function setEnabledValue(value: boolean) {
|
||||
|
@ -50,15 +50,15 @@ const SilentMessageToggle: ChatBarButton = ({ isMainChat }) => {
|
|||
}
|
||||
|
||||
useEffect(() => {
|
||||
const listener: SendListener = (_, message) => {
|
||||
const listener: MessageSendListener = (_, message) => {
|
||||
if (enabled) {
|
||||
if (settings.store.autoDisable) setEnabledValue(false);
|
||||
if (!message.content.startsWith("@silent ")) message.content = "@silent " + message.content;
|
||||
}
|
||||
};
|
||||
|
||||
addPreSendListener(listener);
|
||||
return () => void removePreSendListener(listener);
|
||||
addMessagePreSendListener(listener);
|
||||
return () => void removeMessagePreSendListener(listener);
|
||||
}, [enabled]);
|
||||
|
||||
if (!isMainChat) return null;
|
||||
|
@ -78,7 +78,7 @@ const SilentMessageToggle: ChatBarButton = ({ isMainChat }) => {
|
|||
{!enabled && <>
|
||||
<mask id="vc-silent-msg-mask">
|
||||
<path fill="#fff" d="M0 0h24v24H0Z" />
|
||||
<path stroke="#000" stroke-width="5.99068" d="M0 24 24 0" />
|
||||
<path stroke="#000" strokeWidth="5.99068" d="M0 24 24 0" />
|
||||
</mask>
|
||||
<path fill="var(--status-danger)" d="m21.178 1.70703 1.414 1.414L4.12103 21.593l-1.414-1.415L21.178 1.70703Z" />
|
||||
</>}
|
||||
|
@ -91,9 +91,7 @@ export default definePlugin({
|
|||
name: "SilentMessageToggle",
|
||||
authors: [Devs.Nuckyz, Devs.CatNoir],
|
||||
description: "Adds a button to the chat bar to toggle sending a silent message.",
|
||||
dependencies: ["MessageEventsAPI", "ChatInputButtonAPI"],
|
||||
settings,
|
||||
|
||||
start: () => addChatBarButton("SilentMessageToggle", SilentMessageToggle),
|
||||
stop: () => removeChatBarButton("SilentMessageToggle")
|
||||
renderChatBarButton: SilentMessageToggle,
|
||||
});
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { addChatBarButton, ChatBarButton, removeChatBarButton } from "@api/ChatButtons";
|
||||
import { ChatBarButton, ChatBarButtonFactory } from "@api/ChatButtons";
|
||||
import { ApplicationCommandInputType, ApplicationCommandOptionType, findOption, sendBotMessage } from "@api/Commands";
|
||||
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
|
@ -43,7 +43,7 @@ const settings = definePluginSettings({
|
|||
}
|
||||
});
|
||||
|
||||
const SilentTypingToggle: ChatBarButton = ({ isMainChat }) => {
|
||||
const SilentTypingToggle: ChatBarButtonFactory = ({ isMainChat }) => {
|
||||
const { isEnabled, showIcon } = settings.use(["isEnabled", "showIcon"]);
|
||||
const toggle = () => settings.store.isEnabled = !settings.store.isEnabled;
|
||||
|
||||
|
@ -96,11 +96,12 @@ export default definePlugin({
|
|||
name: "SilentTyping",
|
||||
authors: [Devs.Ven, Devs.Rini, Devs.ImBanana],
|
||||
description: "Hide that you are typing",
|
||||
dependencies: ["ChatInputButtonAPI"],
|
||||
settings,
|
||||
|
||||
contextMenus: {
|
||||
"textarea-context": ChatBarContextCheckbox
|
||||
},
|
||||
|
||||
patches: [
|
||||
{
|
||||
find: '.dispatch({type:"TYPING_START_LOCAL"',
|
||||
|
@ -136,6 +137,5 @@ export default definePlugin({
|
|||
FluxDispatcher.dispatch({ type: "TYPING_START_LOCAL", channelId });
|
||||
},
|
||||
|
||||
start: () => addChatBarButton("SilentTyping", SilentTypingToggle),
|
||||
stop: () => removeChatBarButton("SilentTyping"),
|
||||
renderChatBarButton: SilentTypingToggle,
|
||||
});
|
||||
|
|
|
@ -16,12 +16,28 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import "./styles.css";
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Flex } from "@components/Flex";
|
||||
import { classNameFactory } from "@api/Styles";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { RelationshipStore } from "@webpack/common";
|
||||
import { DateUtils, RelationshipStore, Text, TooltipContainer } from "@webpack/common";
|
||||
import { User } from "discord-types/general";
|
||||
import { PropsWithChildren } from "react";
|
||||
|
||||
const formatter = new Intl.DateTimeFormat(undefined, {
|
||||
month: "numeric",
|
||||
day: "numeric",
|
||||
year: "numeric",
|
||||
});
|
||||
|
||||
const cl = classNameFactory("vc-sortFriendRequests-");
|
||||
|
||||
function getSince(user: User) {
|
||||
return new Date(RelationshipStore.getSince(user.id));
|
||||
}
|
||||
|
||||
const settings = definePluginSettings({
|
||||
showDates: {
|
||||
|
@ -48,28 +64,27 @@ export default definePlugin({
|
|||
find: "#{intl::FRIEND_REQUEST_CANCEL}",
|
||||
replacement: {
|
||||
predicate: () => settings.store.showDates,
|
||||
match: /subText:(\i)(?<=user:(\i).+?)/,
|
||||
replace: (_, subtext, user) => `subText:$self.makeSubtext(${subtext},${user})`
|
||||
match: /(?<=\.listItemContents,children:\[)\(0,.+?(?=,\(0)(?<=user:(\i).+?)/,
|
||||
replace: (children, user) => `$self.WrapperDateComponent({user:${user},children:${children}})`
|
||||
}
|
||||
}],
|
||||
|
||||
wrapSort(comparator: Function, row: any) {
|
||||
return row.type === 3 || row.type === 4
|
||||
? -this.getSince(row.user)
|
||||
? -getSince(row.user)
|
||||
: comparator(row);
|
||||
},
|
||||
|
||||
getSince(user: User) {
|
||||
return new Date(RelationshipStore.getSince(user.id));
|
||||
},
|
||||
WrapperDateComponent: ErrorBoundary.wrap(({ user, children }: PropsWithChildren<{ user: User; }>) => {
|
||||
const since = getSince(user);
|
||||
|
||||
makeSubtext(text: string, user: User) {
|
||||
const since = this.getSince(user);
|
||||
return (
|
||||
<Flex flexDirection="column" style={{ gap: 0, flexWrap: "wrap", lineHeight: "0.9rem" }}>
|
||||
<span>{text}</span>
|
||||
{!isNaN(since.getTime()) && <span>Received — {since.toDateString()}</span>}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
return <div className={cl("wrapper")}>
|
||||
{children}
|
||||
{!isNaN(since.getTime()) && (
|
||||
<TooltipContainer text={DateUtils.dateFormat(since, "LLLL")} tooltipClassName={cl("tooltip")}>
|
||||
<Text variant="text-xs/normal" className={cl("date")}>{formatter.format(since)}</Text>
|
||||
</TooltipContainer>
|
||||
)}
|
||||
</div>;
|
||||
})
|
||||
});
|
||||
|
|
18
src/plugins/sortFriendRequests/styles.css
Normal file
18
src/plugins/sortFriendRequests/styles.css
Normal file
|
@ -0,0 +1,18 @@
|
|||
.vc-sortFriendRequests-wrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
.vc-sortFriendRequests-tooltip {
|
||||
max-width: none;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.vc-sortFriendRequests-date {
|
||||
color: var(--text-muted);
|
||||
font-family: var(--font-code);
|
||||
}
|
|
@ -120,8 +120,8 @@ function ServerTrace({ trace }: ServerTraceProps) {
|
|||
<Forms.FormSection title="Server Trace" tag="h2">
|
||||
<code>
|
||||
<Flex flexDirection="column" style={{ color: "var(--header-primary)", gap: 5, userSelect: "text" }}>
|
||||
{lines.map(line => (
|
||||
<span>{line}</span>
|
||||
{lines.map((line, idx) => (
|
||||
<span key={idx}>{line}</span>
|
||||
))}
|
||||
</Flex>
|
||||
</code>
|
||||
|
|
|
@ -17,13 +17,11 @@
|
|||
*/
|
||||
|
||||
import { DataStore } from "@api/index";
|
||||
import { addPreSendListener, removePreSendListener } from "@api/MessageEvents";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Flex } from "@components/Flex";
|
||||
import { DeleteIcon } from "@components/Icons";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import { useForceUpdater } from "@utils/react";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { Button, Forms, React, TextInput, useState } from "@webpack/common";
|
||||
|
||||
|
@ -35,8 +33,6 @@ type Rule = Record<"find" | "replace" | "onlyIfIncludes", string>;
|
|||
interface TextReplaceProps {
|
||||
title: string;
|
||||
rulesArray: Rule[];
|
||||
rulesKey: string;
|
||||
update: () => void;
|
||||
}
|
||||
|
||||
const makeEmptyRule: () => Rule = () => ({
|
||||
|
@ -46,34 +42,36 @@ const makeEmptyRule: () => Rule = () => ({
|
|||
});
|
||||
const makeEmptyRuleArray = () => [makeEmptyRule()];
|
||||
|
||||
let stringRules = makeEmptyRuleArray();
|
||||
let regexRules = makeEmptyRuleArray();
|
||||
|
||||
const settings = definePluginSettings({
|
||||
replace: {
|
||||
type: OptionType.COMPONENT,
|
||||
description: "",
|
||||
component: () => {
|
||||
const update = useForceUpdater();
|
||||
const { stringRules, regexRules } = settings.use(["stringRules", "regexRules"]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<TextReplace
|
||||
title="Using String"
|
||||
rulesArray={stringRules}
|
||||
rulesKey={STRING_RULES_KEY}
|
||||
update={update}
|
||||
/>
|
||||
<TextReplace
|
||||
title="Using Regex"
|
||||
rulesArray={regexRules}
|
||||
rulesKey={REGEX_RULES_KEY}
|
||||
update={update}
|
||||
/>
|
||||
<TextReplaceTesting />
|
||||
</>
|
||||
);
|
||||
}
|
||||
},
|
||||
stringRules: {
|
||||
type: OptionType.CUSTOM,
|
||||
default: makeEmptyRuleArray(),
|
||||
},
|
||||
regexRules: {
|
||||
type: OptionType.CUSTOM,
|
||||
default: makeEmptyRuleArray(),
|
||||
}
|
||||
});
|
||||
|
||||
function stringToRegex(str: string) {
|
||||
|
@ -120,28 +118,24 @@ function Input({ initialValue, onChange, placeholder }: {
|
|||
);
|
||||
}
|
||||
|
||||
function TextReplace({ title, rulesArray, rulesKey, update }: TextReplaceProps) {
|
||||
function TextReplace({ title, rulesArray }: TextReplaceProps) {
|
||||
const isRegexRules = title === "Using Regex";
|
||||
|
||||
async function onClickRemove(index: number) {
|
||||
if (index === rulesArray.length - 1) return;
|
||||
rulesArray.splice(index, 1);
|
||||
|
||||
await DataStore.set(rulesKey, rulesArray);
|
||||
update();
|
||||
}
|
||||
|
||||
async function onChange(e: string, index: number, key: string) {
|
||||
if (index === rulesArray.length - 1)
|
||||
if (index === rulesArray.length - 1) {
|
||||
rulesArray.push(makeEmptyRule());
|
||||
}
|
||||
|
||||
rulesArray[index][key] = e;
|
||||
|
||||
if (rulesArray[index].find === "" && rulesArray[index].replace === "" && rulesArray[index].onlyIfIncludes === "" && index !== rulesArray.length - 1)
|
||||
if (rulesArray[index].find === "" && rulesArray[index].replace === "" && rulesArray[index].onlyIfIncludes === "" && index !== rulesArray.length - 1) {
|
||||
rulesArray.splice(index, 1);
|
||||
|
||||
await DataStore.set(rulesKey, rulesArray);
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -208,20 +202,18 @@ function TextReplaceTesting() {
|
|||
}
|
||||
|
||||
function applyRules(content: string): string {
|
||||
if (content.length === 0)
|
||||
if (content.length === 0) {
|
||||
return content;
|
||||
}
|
||||
|
||||
if (stringRules) {
|
||||
for (const rule of stringRules) {
|
||||
for (const rule of settings.store.stringRules) {
|
||||
if (!rule.find) continue;
|
||||
if (rule.onlyIfIncludes && !content.includes(rule.onlyIfIncludes)) continue;
|
||||
|
||||
content = ` ${content} `.replaceAll(rule.find, rule.replace.replaceAll("\\n", "\n")).replace(/^\s|\s$/g, "");
|
||||
}
|
||||
}
|
||||
|
||||
if (regexRules) {
|
||||
for (const rule of regexRules) {
|
||||
for (const rule of settings.store.regexRules) {
|
||||
if (!rule.find) continue;
|
||||
if (rule.onlyIfIncludes && !content.includes(rule.onlyIfIncludes)) continue;
|
||||
|
||||
|
@ -232,7 +224,6 @@ function applyRules(content: string): string {
|
|||
new Logger("TextReplace").error(`Invalid regex: ${rule.find}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
content = content.trim();
|
||||
return content;
|
||||
|
@ -244,22 +235,27 @@ export default definePlugin({
|
|||
name: "TextReplace",
|
||||
description: "Replace text in your messages. You can find pre-made rules in the #textreplace-rules channel in Vencord's Server",
|
||||
authors: [Devs.AutumnVN, Devs.TheKodeToad],
|
||||
dependencies: ["MessageEventsAPI"],
|
||||
|
||||
settings,
|
||||
|
||||
async start() {
|
||||
stringRules = await DataStore.get(STRING_RULES_KEY) ?? makeEmptyRuleArray();
|
||||
regexRules = await DataStore.get(REGEX_RULES_KEY) ?? makeEmptyRuleArray();
|
||||
|
||||
this.preSend = addPreSendListener((channelId, msg) => {
|
||||
onBeforeMessageSend(channelId, msg) {
|
||||
// Channel used for sharing rules, applying rules here would be messy
|
||||
if (channelId === TEXT_REPLACE_RULES_CHANNEL_ID) return;
|
||||
msg.content = applyRules(msg.content);
|
||||
});
|
||||
},
|
||||
|
||||
stop() {
|
||||
removePreSendListener(this.preSend);
|
||||
async start() {
|
||||
// TODO: Remove DataStore rules migrations once enough time has passed
|
||||
const oldStringRules = await DataStore.get<Rule[]>(STRING_RULES_KEY);
|
||||
if (oldStringRules != null) {
|
||||
settings.store.stringRules = oldStringRules;
|
||||
await DataStore.del(STRING_RULES_KEY);
|
||||
}
|
||||
|
||||
const oldRegexRules = await DataStore.get<Rule[]>(REGEX_RULES_KEY);
|
||||
if (oldRegexRules != null) {
|
||||
settings.store.regexRules = oldRegexRules;
|
||||
await DataStore.del(REGEX_RULES_KEY);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { ChatBarButton } from "@api/ChatButtons";
|
||||
import { ChatBarButton, ChatBarButtonFactory } from "@api/ChatButtons";
|
||||
import { classes } from "@utils/misc";
|
||||
import { openModal } from "@utils/modal";
|
||||
import { Alerts, Forms, Tooltip, useEffect, useState } from "@webpack/common";
|
||||
|
@ -40,7 +40,7 @@ export function TranslateIcon({ height = 24, width = 24, className }: { height?:
|
|||
|
||||
export let setShouldShowTranslateEnabledTooltip: undefined | ((show: boolean) => void);
|
||||
|
||||
export const TranslateChatBarIcon: ChatBarButton = ({ isMainChat }) => {
|
||||
export const TranslateChatBarIcon: ChatBarButtonFactory = ({ isMainChat }) => {
|
||||
const { autoTranslate, showChatBarButton } = settings.use(["autoTranslate", "showChatBarButton"]);
|
||||
|
||||
const [shouldShowTranslateEnabledTooltip, setter] = useState(false);
|
||||
|
|
|
@ -18,11 +18,7 @@
|
|||
|
||||
import "./styles.css";
|
||||
|
||||
import { addChatBarButton, removeChatBarButton } from "@api/ChatButtons";
|
||||
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import { addAccessory, removeAccessory } from "@api/MessageAccessories";
|
||||
import { addPreSendListener, removePreSendListener } from "@api/MessageEvents";
|
||||
import { addButton, removeButton } from "@api/MessagePopover";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { ChannelStore, Menu } from "@webpack/common";
|
||||
|
@ -51,11 +47,12 @@ const messageCtxPatch: NavContextMenuPatchCallback = (children, { message }) =>
|
|||
));
|
||||
};
|
||||
|
||||
let tooltipTimeout: any;
|
||||
|
||||
export default definePlugin({
|
||||
name: "Translate",
|
||||
description: "Translate messages with Google Translate or DeepL",
|
||||
authors: [Devs.Ven, Devs.AshtonMemer],
|
||||
dependencies: ["MessageAccessoriesAPI", "MessagePopoverAPI", "MessageEventsAPI", "ChatInputButtonAPI"],
|
||||
settings,
|
||||
contextMenus: {
|
||||
"message": messageCtxPatch
|
||||
|
@ -63,12 +60,11 @@ export default definePlugin({
|
|||
// not used, just here in case some other plugin wants it or w/e
|
||||
translate,
|
||||
|
||||
start() {
|
||||
addAccessory("vc-translation", props => <TranslationAccessory message={props.message} />);
|
||||
renderMessageAccessory: props => <TranslationAccessory message={props.message} />,
|
||||
|
||||
addChatBarButton("vc-translate", TranslateChatBarIcon);
|
||||
renderChatBarButton: TranslateChatBarIcon,
|
||||
|
||||
addButton("vc-translate", message => {
|
||||
renderMessagePopoverButton(message) {
|
||||
if (!message.content) return null;
|
||||
|
||||
return {
|
||||
|
@ -81,10 +77,9 @@ export default definePlugin({
|
|||
handleTranslate(message.id, trans);
|
||||
}
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
let tooltipTimeout: any;
|
||||
this.preSend = addPreSendListener(async (_, message) => {
|
||||
async onBeforeMessageSend(_, message) {
|
||||
if (!settings.store.autoTranslate) return;
|
||||
if (!message.content) return;
|
||||
|
||||
|
@ -94,14 +89,5 @@ export default definePlugin({
|
|||
|
||||
const trans = await translate("sent", message.content);
|
||||
message.content = trans.text;
|
||||
|
||||
});
|
||||
},
|
||||
|
||||
stop() {
|
||||
removePreSendListener(this.preSend);
|
||||
removeChatBarButton("vc-translate");
|
||||
removeButton("vc-translate");
|
||||
removeAccessory("vc-translation");
|
||||
},
|
||||
}
|
||||
});
|
||||
|
|
|
@ -23,6 +23,7 @@ import { openUserProfile } from "@utils/discord";
|
|||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { Avatar, GuildMemberStore, React, RelationshipStore } from "@webpack/common";
|
||||
import { User } from "discord-types/general";
|
||||
import { PropsWithChildren } from "react";
|
||||
|
||||
const settings = definePluginSettings({
|
||||
showAvatars: {
|
||||
|
@ -100,7 +101,7 @@ export default definePlugin({
|
|||
{
|
||||
// Style the indicator and add function call to modify the children before rendering
|
||||
match: /(?<=children:\[(\i)\.length>0.{0,200}?"aria-atomic":!0,children:)\i(?<=guildId:(\i).+?)/,
|
||||
replace: "$self.mutateChildren($2,$1,$&),style:$self.TYPING_TEXT_STYLE"
|
||||
replace: "$self.renderTypingUsers({ users: $1, guildId: $2, children: $& }),style:$self.TYPING_TEXT_STYLE"
|
||||
},
|
||||
{
|
||||
// Changes the indicator to keep the user object when creating the list of typing users
|
||||
|
@ -125,7 +126,7 @@ export default definePlugin({
|
|||
|
||||
buildSeveralUsers,
|
||||
|
||||
mutateChildren(guildId: any, users: User[], children: any) {
|
||||
renderTypingUsers: ErrorBoundary.wrap(({ guildId, users, children }: PropsWithChildren<{ guildId: string, users: User[]; }>) => {
|
||||
try {
|
||||
if (!Array.isArray(children)) {
|
||||
return children;
|
||||
|
@ -133,15 +134,17 @@ export default definePlugin({
|
|||
|
||||
let element = 0;
|
||||
|
||||
return children.map(c =>
|
||||
c.type === "strong" || (typeof c !== "string" && !React.isValidElement(c))
|
||||
? <TypingUser guildId={guildId} user={users[element++]} />
|
||||
: c
|
||||
);
|
||||
return children.map(c => {
|
||||
if (c.type !== "strong" && !(typeof c !== "string" && !React.isValidElement(c)))
|
||||
return c;
|
||||
|
||||
const user = users[element++];
|
||||
return <TypingUser key={user.id} guildId={guildId} user={user} />;
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
return children;
|
||||
}
|
||||
}, { noop: true })
|
||||
});
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { addPreEditListener, addPreSendListener, MessageObject, removePreEditListener, removePreSendListener } from "@api/MessageEvents";
|
||||
import { MessageObject } from "@api/MessageEvents";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
|
||||
|
@ -24,7 +24,7 @@ export default definePlugin({
|
|||
name: "Unindent",
|
||||
description: "Trims leading indentation from codeblocks",
|
||||
authors: [Devs.Ven],
|
||||
dependencies: ["MessageEventsAPI"],
|
||||
|
||||
patches: [
|
||||
{
|
||||
find: "inQuote:",
|
||||
|
@ -55,13 +55,11 @@ export default definePlugin({
|
|||
});
|
||||
},
|
||||
|
||||
start() {
|
||||
this.preSend = addPreSendListener((_, msg) => this.unindentMsg(msg));
|
||||
this.preEdit = addPreEditListener((_cid, _mid, msg) => this.unindentMsg(msg));
|
||||
onBeforeMessageSend(_, msg) {
|
||||
return this.unindentMsg(msg);
|
||||
},
|
||||
|
||||
stop() {
|
||||
removePreSendListener(this.preSend);
|
||||
removePreEditListener(this.preEdit);
|
||||
onBeforeMessageEdit(_cid, _mid, msg) {
|
||||
return this.unindentMsg(msg);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -21,12 +21,18 @@ import { ImageInvisible, ImageVisible } from "@components/Icons";
|
|||
import { Devs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { Constants, Menu, PermissionsBits, PermissionStore, RestAPI, UserStore } from "@webpack/common";
|
||||
import { MessageSnapshot } from "@webpack/types";
|
||||
|
||||
|
||||
const EMBED_SUPPRESSED = 1 << 2;
|
||||
|
||||
const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { channel, message: { author, embeds, flags, id: messageId } }) => {
|
||||
const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { channel, message: { author, messageSnapshots, embeds, flags, id: messageId } }) => {
|
||||
const isEmbedSuppressed = (flags & EMBED_SUPPRESSED) !== 0;
|
||||
if (!isEmbedSuppressed && !embeds.length) return;
|
||||
const hasEmbedsInSnapshots = messageSnapshots.some(
|
||||
(snapshot: MessageSnapshot) => snapshot?.message.embeds.length
|
||||
);
|
||||
|
||||
if (!isEmbedSuppressed && !embeds.length && !hasEmbedsInSnapshots) return;
|
||||
|
||||
const hasEmbedPerms = channel.isPrivate() || !!(PermissionStore.getChannelPermissions({ id: channel.id }) & PermissionsBits.EMBED_LINKS);
|
||||
if (author.id === UserStore.getCurrentUser().id && !hasEmbedPerms) return;
|
||||
|
|
|
@ -18,8 +18,8 @@
|
|||
|
||||
import "./style.css";
|
||||
|
||||
import { addDecorator, removeDecorator } from "@api/MemberListDecorators";
|
||||
import { addDecoration, removeDecoration } from "@api/MessageDecorations";
|
||||
import { addMemberListDecorator, removeMemberListDecorator } from "@api/MemberListDecorators";
|
||||
import { addMessageDecoration, removeMessageDecoration } from "@api/MessageDecorations";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
|
@ -96,16 +96,16 @@ export default definePlugin({
|
|||
|
||||
start() {
|
||||
if (settings.store.showInMemberList) {
|
||||
addDecorator("UserVoiceShow", ({ user }) => user == null ? null : <VoiceChannelIndicator userId={user.id} />);
|
||||
addMemberListDecorator("UserVoiceShow", ({ user }) => user == null ? null : <VoiceChannelIndicator userId={user.id} />);
|
||||
}
|
||||
if (settings.store.showInMessages) {
|
||||
addDecoration("UserVoiceShow", ({ message }) => message?.author == null ? null : <VoiceChannelIndicator userId={message.author.id} isMessageIndicator />);
|
||||
addMessageDecoration("UserVoiceShow", ({ message }) => message?.author == null ? null : <VoiceChannelIndicator userId={message.author.id} isMessageIndicator />);
|
||||
}
|
||||
},
|
||||
|
||||
stop() {
|
||||
removeDecorator("UserVoiceShow");
|
||||
removeDecoration("UserVoiceShow");
|
||||
removeMemberListDecorator("UserVoiceShow");
|
||||
removeMessageDecoration("UserVoiceShow");
|
||||
},
|
||||
|
||||
VoiceChannelIndicator
|
||||
|
|
|
@ -25,6 +25,7 @@ import { wordsToTitle } from "@utils/text";
|
|||
import definePlugin, { OptionType, PluginOptionsItem, ReporterTestable } from "@utils/types";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { Button, ChannelStore, Forms, GuildMemberStore, SelectedChannelStore, SelectedGuildStore, useMemo, UserStore } from "@webpack/common";
|
||||
import { ReactElement } from "react";
|
||||
|
||||
interface VoiceState {
|
||||
userId: string;
|
||||
|
@ -289,7 +290,7 @@ export default definePlugin({
|
|||
description: "Undeafen Message (only self for now)",
|
||||
default: "{{USER}} undeafened"
|
||||
}
|
||||
};
|
||||
} satisfies Record<string, PluginOptionsItem>;
|
||||
},
|
||||
|
||||
settingsAboutComponent({ tempSettings: s }) {
|
||||
|
@ -303,7 +304,7 @@ export default definePlugin({
|
|||
[],
|
||||
);
|
||||
|
||||
let errorComponent: React.ReactElement | null = null;
|
||||
let errorComponent: ReactElement<any> | null = null;
|
||||
if (!hasVoices) {
|
||||
let error = "No narrator voices found. ";
|
||||
error += navigator.platform?.toLowerCase().includes("linux")
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
*/
|
||||
|
||||
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import { addButton, removeButton } from "@api/MessagePopover";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { CodeBlock } from "@components/CodeBlock";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
|
@ -149,8 +148,8 @@ export default definePlugin({
|
|||
name: "ViewRaw",
|
||||
description: "Copy and view the raw content/data of any message, channel or guild",
|
||||
authors: [Devs.KingFish, Devs.Ven, Devs.rad, Devs.ImLvna],
|
||||
dependencies: ["MessagePopoverAPI"],
|
||||
settings,
|
||||
|
||||
contextMenus: {
|
||||
"guild-context": MakeContextCallback("Guild"),
|
||||
"channel-context": MakeContextCallback("Channel"),
|
||||
|
@ -159,8 +158,7 @@ export default definePlugin({
|
|||
"user-context": MakeContextCallback("User")
|
||||
},
|
||||
|
||||
start() {
|
||||
addButton("ViewRaw", msg => {
|
||||
renderMessagePopoverButton(msg) {
|
||||
const handleClick = () => {
|
||||
if (settings.store.clickMethod === "Right") {
|
||||
copyWithToast(msg.content);
|
||||
|
@ -193,10 +191,5 @@ export default definePlugin({
|
|||
onClick: handleClick,
|
||||
onContextMenu: handleContextMenu
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
stop() {
|
||||
removeButton("ViewRaw");
|
||||
}
|
||||
});
|
||||
|
|
|
@ -23,7 +23,7 @@ import { Queue } from "@utils/Queue";
|
|||
import { useForceUpdater } from "@utils/react";
|
||||
import definePlugin from "@utils/types";
|
||||
import { findByPropsLazy, findComponentByCodeLazy } from "@webpack";
|
||||
import { ChannelStore, Constants, FluxDispatcher, React, RestAPI, Tooltip } from "@webpack/common";
|
||||
import { ChannelStore, Constants, FluxDispatcher, React, RestAPI, Tooltip, useEffect, useLayoutEffect } from "@webpack/common";
|
||||
import { CustomEmoji } from "@webpack/types";
|
||||
import { Message, ReactionEmoji, User } from "discord-types/general";
|
||||
|
||||
|
@ -134,18 +134,21 @@ export default definePlugin({
|
|||
renderUsers(props: RootObject) {
|
||||
return props.message.reactions.length > 10 ? null : (
|
||||
<ErrorBoundary noop>
|
||||
<this._renderUsers {...props} />
|
||||
<this.UsersComponent {...props} />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
},
|
||||
_renderUsers({ message, emoji, type }: RootObject) {
|
||||
|
||||
UsersComponent({ message, emoji, type }: RootObject) {
|
||||
const forceUpdate = useForceUpdater();
|
||||
React.useLayoutEffect(() => { // bc need to prevent autoscrolling
|
||||
|
||||
useLayoutEffect(() => { // bc need to prevent autoscrolling
|
||||
if (Scroll?.scrollCounter > 0) {
|
||||
Scroll.setAutomaticAnchor(null);
|
||||
}
|
||||
});
|
||||
React.useEffect(() => {
|
||||
|
||||
useEffect(() => {
|
||||
const cb = (e: any) => {
|
||||
if (e.messageId === message.id)
|
||||
forceUpdate();
|
||||
|
@ -153,7 +156,7 @@ export default definePlugin({
|
|||
FluxDispatcher.subscribe("MESSAGE_REACTION_ADD_USERS", cb);
|
||||
|
||||
return () => FluxDispatcher.unsubscribe("MESSAGE_REACTION_ADD_USERS", cb);
|
||||
}, [message.id]);
|
||||
}, [message.id, forceUpdate]);
|
||||
|
||||
const reactions = getReactionsWithQueue(message, emoji, type);
|
||||
const users = Object.values(reactions).filter(Boolean) as User[];
|
||||
|
|
|
@ -6,6 +6,9 @@
|
|||
|
||||
import { LiteralUnion } from "type-fest";
|
||||
|
||||
export const SYM_IS_PROXY = Symbol("SettingsStore.isProxy");
|
||||
export const SYM_GET_RAW_TARGET = Symbol("SettingsStore.getRawTarget");
|
||||
|
||||
// Resolves a possibly nested prop in the form of "some.nested.prop" to type of T.some.nested.prop
|
||||
type ResolvePropDeep<T, P> = P extends `${infer Pre}.${infer Suf}`
|
||||
? Pre extends keyof T
|
||||
|
@ -28,6 +31,11 @@ interface SettingsStoreOptions {
|
|||
// merges the SettingsStoreOptions type into the class
|
||||
export interface SettingsStore<T extends object> extends SettingsStoreOptions { }
|
||||
|
||||
interface ProxyContext<T extends object = any> {
|
||||
root: T;
|
||||
path: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The SettingsStore allows you to easily create a mutable store that
|
||||
* has support for global and path-based change listeners.
|
||||
|
@ -35,6 +43,90 @@ export interface SettingsStore<T extends object> extends SettingsStoreOptions {
|
|||
export class SettingsStore<T extends object> {
|
||||
private pathListeners = new Map<string, Set<(newData: any) => void>>();
|
||||
private globalListeners = new Set<(newData: T, path: string) => void>();
|
||||
private readonly proxyContexts = new WeakMap<any, ProxyContext<T>>();
|
||||
|
||||
private readonly proxyHandler: ProxyHandler<any> = (() => {
|
||||
const self = this;
|
||||
|
||||
return {
|
||||
get(target, key: any, receiver) {
|
||||
if (key === SYM_IS_PROXY) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (key === SYM_GET_RAW_TARGET) {
|
||||
return target;
|
||||
}
|
||||
|
||||
let v = Reflect.get(target, key, receiver);
|
||||
|
||||
const proxyContext = self.proxyContexts.get(target);
|
||||
if (proxyContext == null) {
|
||||
return v;
|
||||
}
|
||||
|
||||
const { root, path } = proxyContext;
|
||||
|
||||
if (!(key in target) && self.getDefaultValue != null) {
|
||||
v = self.getDefaultValue({
|
||||
target,
|
||||
key,
|
||||
root,
|
||||
path
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof v === "object" && v !== null && !v[SYM_IS_PROXY]) {
|
||||
const getPath = `${path}${path && "."}${key}`;
|
||||
return self.makeProxy(v, root, getPath);
|
||||
}
|
||||
|
||||
return v;
|
||||
},
|
||||
set(target, key: string, value) {
|
||||
if (value?.[SYM_IS_PROXY]) {
|
||||
value = value[SYM_GET_RAW_TARGET];
|
||||
}
|
||||
|
||||
if (target[key] === value) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!Reflect.set(target, key, value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const proxyContext = self.proxyContexts.get(target);
|
||||
if (proxyContext == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const { root, path } = proxyContext;
|
||||
|
||||
const setPath = `${path}${path && "."}${key}`;
|
||||
self.notifyListeners(setPath, value, root);
|
||||
|
||||
return true;
|
||||
},
|
||||
deleteProperty(target, key: string) {
|
||||
if (!Reflect.deleteProperty(target, key)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const proxyContext = self.proxyContexts.get(target);
|
||||
if (proxyContext == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const { root, path } = proxyContext;
|
||||
|
||||
const deletePath = `${path}${path && "."}${key}`;
|
||||
self.notifyListeners(deletePath, undefined, root);
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
/**
|
||||
* The store object. Making changes to this object will trigger the applicable change listeners
|
||||
|
@ -51,39 +143,35 @@ export class SettingsStore<T extends object> {
|
|||
Object.assign(this, options);
|
||||
}
|
||||
|
||||
private makeProxy(object: any, root: T = object, path: string = "") {
|
||||
const self = this;
|
||||
|
||||
return new Proxy(object, {
|
||||
get(target, key: string) {
|
||||
let v = target[key];
|
||||
|
||||
if (!(key in target) && self.getDefaultValue) {
|
||||
v = self.getDefaultValue({
|
||||
target,
|
||||
key,
|
||||
private makeProxy(object: any, root: T = object, path = "") {
|
||||
this.proxyContexts.set(object, {
|
||||
root,
|
||||
path
|
||||
});
|
||||
|
||||
return new Proxy(object, this.proxyHandler);
|
||||
}
|
||||
|
||||
if (typeof v === "object" && v !== null && !Array.isArray(v))
|
||||
return self.makeProxy(v, root, `${path}${path && "."}${key}`);
|
||||
private notifyListeners(pathStr: string, value: any, root: T) {
|
||||
const paths = pathStr.split(".");
|
||||
|
||||
return v;
|
||||
},
|
||||
set(target, key: string, value) {
|
||||
if (target[key] === value) return true;
|
||||
// Because we support any type of settings with OptionType.CUSTOM, and those objects get proxied recursively,
|
||||
// the path ends up including all the nested paths (plugins.pluginName.settingName.example.one).
|
||||
// So, we need to extract the top-level setting path (plugins.pluginName.settingName),
|
||||
// to be able to notify globalListeners and top-level setting name listeners (let { settingName } = settings.use(["settingName"]),
|
||||
// with the new value
|
||||
if (paths.length > 2 && paths[0] === "plugins") {
|
||||
const settingPath = paths.slice(0, 3);
|
||||
const settingPathStr = settingPath.join(".");
|
||||
const settingValue = settingPath.reduce((acc, curr) => acc[curr], root);
|
||||
|
||||
Reflect.set(target, key, value);
|
||||
const setPath = `${path}${path && "."}${key}`;
|
||||
|
||||
self.globalListeners.forEach(cb => cb(value, setPath));
|
||||
self.pathListeners.get(setPath)?.forEach(cb => cb(value));
|
||||
|
||||
return true;
|
||||
this.globalListeners.forEach(cb => cb(root, settingPathStr));
|
||||
this.pathListeners.get(settingPathStr)?.forEach(cb => cb(settingValue));
|
||||
} else {
|
||||
this.globalListeners.forEach(cb => cb(root, pathStr));
|
||||
}
|
||||
});
|
||||
|
||||
this.pathListeners.get(pathStr)?.forEach(cb => cb(value));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue