Add the option to toggle plugin chatbar & message hover buttons (#3693)
This commit is contained in:
parent
52e76a9cfc
commit
eb5a42dd87
18 changed files with 586 additions and 209 deletions
|
|
@ -9,11 +9,15 @@ import "./ChatButton.css";
|
|||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import { classes } from "@utils/misc";
|
||||
import { IconComponent } from "@utils/types";
|
||||
import { Channel } from "@vencord/discord-types";
|
||||
import { waitFor } from "@webpack";
|
||||
import { ButtonWrapperClasses, Clickable, Tooltip } from "@webpack/common";
|
||||
import { ButtonWrapperClasses, Clickable, Menu, Tooltip } from "@webpack/common";
|
||||
import { HTMLProps, JSX, MouseEventHandler, ReactNode } from "react";
|
||||
|
||||
import { addContextMenuPatch, findGroupChildrenByChildId } from "./ContextMenu";
|
||||
import { useSettings } from "./Settings";
|
||||
|
||||
let ChannelTextAreaClasses: Record<"button" | "buttonContainer", string>;
|
||||
waitFor(["buttonContainer", "channelTextArea"], m => ChannelTextAreaClasses = m);
|
||||
|
||||
|
|
@ -76,26 +80,51 @@ export interface ChatBarProps {
|
|||
}
|
||||
|
||||
export type ChatBarButtonFactory = (props: ChatBarProps & { isMainChat: boolean; }) => JSX.Element | null;
|
||||
export type ChatBarButtonData = {
|
||||
render: ChatBarButtonFactory;
|
||||
/**
|
||||
* This icon is used only for Settings UI. Your render function must still render an icon,
|
||||
* and it can be different from this one.
|
||||
*/
|
||||
icon: IconComponent;
|
||||
};
|
||||
|
||||
const buttonFactories = new Map<string, ChatBarButtonFactory>();
|
||||
/**
|
||||
* Don't use this directly, use {@link addChatBarButton} and {@link removeChatBarButton} instead.
|
||||
*/
|
||||
export const ChatBarButtonMap = new Map<string, ChatBarButtonData>();
|
||||
const logger = new Logger("ChatButtons");
|
||||
|
||||
function VencordChatBarButtons(props: ChatBarProps) {
|
||||
const { chatBarButtons } = useSettings(["uiElements.chatBarButtons.*"]).uiElements;
|
||||
|
||||
return (
|
||||
<>
|
||||
{Array.from(ChatBarButtonMap)
|
||||
.filter(([key]) => chatBarButtons[key]?.enabled !== false)
|
||||
.map(([key, { render: Button }]) => (
|
||||
<ErrorBoundary noop key={key} onError={e => logger.error(`Failed to render ${key}`, e.error)}>
|
||||
<Button {...props} isMainChat={props.type.analyticsName === "normal"} />
|
||||
</ErrorBoundary>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function _injectButtons(buttons: ReactNode[], props: ChatBarProps) {
|
||||
if (props.disabled) return;
|
||||
|
||||
for (const [key, Button] of buttonFactories) {
|
||||
buttons.unshift(
|
||||
<ErrorBoundary noop key={key} onError={e => logger.error(`Failed to render ${key}`, e.error)}>
|
||||
<Button {...props} isMainChat={props.type.analyticsName === "normal"} />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
buttons.unshift(<VencordChatBarButtons key="vencord-chat-buttons" {...props} />);
|
||||
|
||||
return buttons;
|
||||
}
|
||||
|
||||
export const addChatBarButton = (id: string, button: ChatBarButtonFactory) => buttonFactories.set(id, button);
|
||||
export const removeChatBarButton = (id: string) => buttonFactories.delete(id);
|
||||
/**
|
||||
* The icon argument is used only for Settings UI. Your render function must still render an icon,
|
||||
* and it can be different from this one.
|
||||
*/
|
||||
export const addChatBarButton = (id: string, render: ChatBarButtonFactory, icon: IconComponent) => ChatBarButtonMap.set(id, { render, icon });
|
||||
export const removeChatBarButton = (id: string) => ChatBarButtonMap.delete(id);
|
||||
|
||||
export interface ChatBarButtonProps {
|
||||
children: ReactNode;
|
||||
|
|
@ -105,6 +134,7 @@ export interface ChatBarButtonProps {
|
|||
onAuxClick?: MouseEventHandler;
|
||||
buttonProps?: Omit<HTMLProps<HTMLDivElement>, "size" | "onClick" | "onContextMenu" | "onAuxClick">;
|
||||
}
|
||||
|
||||
export const ChatBarButton = ErrorBoundary.wrap((props: ChatBarButtonProps) => {
|
||||
return (
|
||||
<Tooltip text={props.tooltip}>
|
||||
|
|
@ -129,3 +159,35 @@ export const ChatBarButton = ErrorBoundary.wrap((props: ChatBarButtonProps) => {
|
|||
</Tooltip>
|
||||
);
|
||||
}, { noop: true });
|
||||
|
||||
addContextMenuPatch("textarea-context", (children, args) => {
|
||||
const { chatBarButtons } = useSettings(["uiElements.chatBarButtons.*"]).uiElements;
|
||||
|
||||
const buttons = Array.from(ChatBarButtonMap.entries());
|
||||
if (!buttons.length) return;
|
||||
|
||||
const group = findGroupChildrenByChildId("submit-button", children);
|
||||
if (!group) return;
|
||||
|
||||
const idx = group.findIndex(c => c?.props?.id === "submit-button");
|
||||
if (idx === -1) return;
|
||||
|
||||
group.splice(idx, 0,
|
||||
<Menu.MenuItem id="vc-chat-buttons" key="vencord-chat-buttons" label="Vencord Buttons">
|
||||
{buttons.map(([id]) => (
|
||||
<Menu.MenuCheckboxItem
|
||||
label={id}
|
||||
key={id}
|
||||
id={`vc-chat-button-${id}`}
|
||||
checked={chatBarButtons[id]?.enabled !== false}
|
||||
action={() => {
|
||||
const wasEnabled = chatBarButtons[id]?.enabled !== false;
|
||||
|
||||
chatBarButtons[id] ??= {} as any;
|
||||
chatBarButtons[id].enabled = !wasEnabled;
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Menu.MenuItem>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -18,9 +18,12 @@
|
|||
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import { IconComponent } from "@utils/types";
|
||||
import { Channel, Message } from "@vencord/discord-types";
|
||||
import type { ComponentType, MouseEventHandler } from "react";
|
||||
|
||||
import { useSettings } from "./Settings";
|
||||
|
||||
const logger = new Logger("MessagePopover");
|
||||
|
||||
export interface MessagePopoverButtonItem {
|
||||
|
|
@ -34,41 +37,63 @@ export interface MessagePopoverButtonItem {
|
|||
}
|
||||
|
||||
export type MessagePopoverButtonFactory = (message: Message) => MessagePopoverButtonItem | null;
|
||||
export type MessagePopoverButtonData = {
|
||||
render: MessagePopoverButtonFactory;
|
||||
/**
|
||||
* This icon is used only for Settings UI. Your render function must still return an icon,
|
||||
* and it can be different from this one.
|
||||
*/
|
||||
icon: IconComponent;
|
||||
};
|
||||
|
||||
export const buttons = new Map<string, MessagePopoverButtonFactory>();
|
||||
export const MessagePopoverButtonMap = new Map<string, MessagePopoverButtonData>();
|
||||
|
||||
/**
|
||||
* The icon argument is used only for Settings UI. Your render function must still return an icon,
|
||||
* and it can be different from this one.
|
||||
*/
|
||||
export function addMessagePopoverButton(
|
||||
identifier: string,
|
||||
item: MessagePopoverButtonFactory,
|
||||
render: MessagePopoverButtonFactory,
|
||||
icon: IconComponent
|
||||
) {
|
||||
buttons.set(identifier, item);
|
||||
MessagePopoverButtonMap.set(identifier, { render, icon });
|
||||
}
|
||||
|
||||
export function removeMessagePopoverButton(identifier: string) {
|
||||
buttons.delete(identifier);
|
||||
MessagePopoverButtonMap.delete(identifier);
|
||||
}
|
||||
|
||||
function VencordPopoverButtons(props: { Component: React.ComponentType<MessagePopoverButtonItem>, message: Message; }) {
|
||||
const { Component, message } = props;
|
||||
|
||||
const { messagePopoverButtons } = useSettings(["uiElements.messagePopoverButtons.*"]).uiElements;
|
||||
|
||||
const elements = Array.from(MessagePopoverButtonMap.entries())
|
||||
.filter(([key]) => messagePopoverButtons[key]?.enabled !== false)
|
||||
.map(([key, { render }]) => {
|
||||
try {
|
||||
// FIXME: this should use proper React to ensure hooks work
|
||||
const item = render(message);
|
||||
if (!item) return null;
|
||||
|
||||
return (
|
||||
<ErrorBoundary noop>
|
||||
<Component key={key} {...item} />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
} catch (err) {
|
||||
logger.error(`[${key}]`, err);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
return <>{elements}</>;
|
||||
}
|
||||
|
||||
export function _buildPopoverElements(
|
||||
Component: React.ComponentType<MessagePopoverButtonItem>,
|
||||
message: Message
|
||||
) {
|
||||
const items: React.ReactNode[] = [];
|
||||
|
||||
for (const [identifier, getItem] of buttons.entries()) {
|
||||
try {
|
||||
const item = getItem(message);
|
||||
if (item) {
|
||||
item.key ??= identifier;
|
||||
items.push(
|
||||
<ErrorBoundary noop>
|
||||
<Component {...item} />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error(`[${identifier}]`, err);
|
||||
}
|
||||
}
|
||||
|
||||
return <>{items}</>;
|
||||
return <VencordPopoverButtons Component={Component} message={message} />;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -179,7 +179,7 @@ export const startPlugin = traceFunction("startPlugin", function startPlugin(p:
|
|||
const {
|
||||
name, commands, contextMenus, managedStyle, userProfileBadge,
|
||||
onBeforeMessageEdit, onBeforeMessageSend, onMessageClick,
|
||||
renderChatBarButton, renderMemberListDecorator, renderMessageAccessory, renderMessageDecoration, renderMessagePopoverButton
|
||||
renderChatBarButton, chatBarButton, renderMemberListDecorator, renderMessageAccessory, renderMessageDecoration, renderMessagePopoverButton, messagePopoverButton
|
||||
} = p;
|
||||
|
||||
if (p.start) {
|
||||
|
|
@ -229,11 +229,15 @@ export const startPlugin = traceFunction("startPlugin", function startPlugin(p:
|
|||
if (onBeforeMessageSend) addMessagePreSendListener(onBeforeMessageSend);
|
||||
if (onMessageClick) addMessageClickListener(onMessageClick);
|
||||
|
||||
if (renderChatBarButton) addChatBarButton(name, renderChatBarButton);
|
||||
if (chatBarButton) addChatBarButton(name, chatBarButton.render, chatBarButton.icon);
|
||||
// @ts-expect-error: legacy code doesn't have icon
|
||||
else 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);
|
||||
if (messagePopoverButton) addMessagePopoverButton(name, messagePopoverButton.render, messagePopoverButton.icon);
|
||||
// @ts-expect-error: legacy code doesn't have icon
|
||||
else if (renderMessagePopoverButton) addMessagePopoverButton(name, renderMessagePopoverButton);
|
||||
|
||||
return true;
|
||||
}, p => `startPlugin ${p.name}`);
|
||||
|
|
@ -242,7 +246,7 @@ export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plu
|
|||
const {
|
||||
name, commands, contextMenus, managedStyle, userProfileBadge,
|
||||
onBeforeMessageEdit, onBeforeMessageSend, onMessageClick,
|
||||
renderChatBarButton, renderMemberListDecorator, renderMessageAccessory, renderMessageDecoration, renderMessagePopoverButton
|
||||
renderChatBarButton, chatBarButton, renderMemberListDecorator, renderMessageAccessory, renderMessageDecoration, renderMessagePopoverButton, messagePopoverButton
|
||||
} = p;
|
||||
|
||||
if (p.stop) {
|
||||
|
|
@ -290,11 +294,11 @@ export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plu
|
|||
if (onBeforeMessageSend) removeMessagePreSendListener(onBeforeMessageSend);
|
||||
if (onMessageClick) removeMessageClickListener(onMessageClick);
|
||||
|
||||
if (renderChatBarButton) removeChatBarButton(name);
|
||||
if (chatBarButton || renderChatBarButton) removeChatBarButton(name);
|
||||
if (renderMemberListDecorator) removeMemberListDecorator(name);
|
||||
if (renderMessageDecoration) removeMessageDecoration(name);
|
||||
if (renderMessageAccessory) removeMessageAccessory(name);
|
||||
if (renderMessagePopoverButton) removeMessagePopoverButton(name);
|
||||
if (messagePopoverButton || renderMessagePopoverButton) removeMessagePopoverButton(name);
|
||||
|
||||
return true;
|
||||
}, p => `stopPlugin ${p.name}`);
|
||||
|
|
@ -335,11 +339,11 @@ export const initPluginManager = onlyOnce(function init() {
|
|||
|
||||
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.chatBarButton || 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.messagePopoverButton || p.renderMessagePopoverButton) neededApiPlugins.add("MessagePopoverAPI");
|
||||
if (p.userProfileBadge) neededApiPlugins.add("BadgeAPI");
|
||||
|
||||
for (const key of pluginKeysToBind) {
|
||||
|
|
|
|||
|
|
@ -25,6 +25,18 @@ import { React, useEffect } from "@webpack/common";
|
|||
import plugins from "~plugins";
|
||||
|
||||
const logger = new Logger("Settings");
|
||||
|
||||
export interface SettingsPluginUiElement {
|
||||
enabled: boolean;
|
||||
// TODO
|
||||
/** not implemented for now */
|
||||
order?: number;
|
||||
}
|
||||
export type SettingsPluginUiElements = {
|
||||
/** id will be whatever id the element was registered with. Usually, but not always, the plugin name */
|
||||
[id: string]: SettingsPluginUiElement;
|
||||
};
|
||||
|
||||
export interface Settings {
|
||||
autoUpdate: boolean;
|
||||
autoUpdateNotification: boolean,
|
||||
|
|
@ -59,6 +71,11 @@ export interface Settings {
|
|||
};
|
||||
};
|
||||
|
||||
uiElements: {
|
||||
messagePopoverButtons: SettingsPluginUiElements;
|
||||
chatBarButtons: SettingsPluginUiElements;
|
||||
},
|
||||
|
||||
notifications: {
|
||||
timeout: number;
|
||||
position: "top-right" | "bottom-right";
|
||||
|
|
@ -90,6 +107,11 @@ const DefaultSettings: Settings = {
|
|||
winNativeTitleBar: false,
|
||||
plugins: {},
|
||||
|
||||
uiElements: {
|
||||
chatBarButtons: {},
|
||||
messagePopoverButtons: {}
|
||||
},
|
||||
|
||||
notifications: {
|
||||
timeout: 5000,
|
||||
position: "bottom-right",
|
||||
|
|
|
|||
|
|
@ -430,3 +430,17 @@ export function WebsiteIcon(props: IconProps) {
|
|||
</Icon>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* A question mark inside a square, used as a placeholder icon when no other icon is available
|
||||
*/
|
||||
export function PlaceholderIcon(props: IconProps) {
|
||||
return (
|
||||
<Icon
|
||||
{...props}
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path fill={props.fill || "currentColor"} fillRule="evenodd" d="M5 2a3 3 0 0 0-3 3v14a3 3 0 0 0 3 3h14a3 3 0 0 0 3-3V5a3 3 0 0 0-3-3H5Zm6.81 7c-.54 0-1 .26-1.23.61A1 1 0 0 1 8.92 8.5 3.49 3.49 0 0 1 11.82 7c1.81 0 3.43 1.38 3.43 3.25 0 1.45-.98 2.61-2.27 3.06a1 1 0 0 1-1.96.37l-.19-1a1 1 0 0 1 .98-1.18c.87 0 1.44-.63 1.44-1.25S12.68 9 11.81 9ZM13 16a1 1 0 1 1-2 0 1 1 0 0 1 2 0Zm7-10.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0ZM18.5 20a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3ZM7 18.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0ZM5.5 7a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z" clipRule="evenodd" />
|
||||
</Icon>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
42
src/components/settings/tabs/plugins/UIElements.css
Normal file
42
src/components/settings/tabs/plugins/UIElements.css
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
.vc-plugin-ui-elements-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--interactive-normal);
|
||||
margin-top: 0.5em;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--background-modifier-hover);
|
||||
color: var(--interactive-hover);
|
||||
}
|
||||
}
|
||||
|
||||
.vc-plugin-ui-elements-button-arrow {
|
||||
margin-left: auto;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.vc-plugin-ui-elements-modal-content {
|
||||
padding: 1em;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5em;
|
||||
}
|
||||
|
||||
.vc-plugin-ui-elements-switches {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1em;
|
||||
}
|
||||
|
||||
.vc-plugin-ui-elements-switches-row {
|
||||
display: flex;
|
||||
gap: 1em;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
|
||||
:last-child {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
107
src/components/settings/tabs/plugins/UIElements.tsx
Normal file
107
src/components/settings/tabs/plugins/UIElements.tsx
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import "./UIElements.css";
|
||||
|
||||
import { ChatBarButtonMap } from "@api/ChatButtons";
|
||||
import { MessagePopoverButtonMap } from "@api/MessagePopover";
|
||||
import { SettingsPluginUiElements, useSettings } from "@api/Settings";
|
||||
import { classNameFactory } from "@api/Styles";
|
||||
import { BaseText } from "@components/BaseText";
|
||||
import { Card } from "@components/Card";
|
||||
import { PlaceholderIcon } from "@components/Icons";
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
import { Switch } from "@components/Switch";
|
||||
import { Margins } from "@utils/margins";
|
||||
import { classes } from "@utils/misc";
|
||||
import { ModalContent, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||
import { IconComponent } from "@utils/types";
|
||||
import { Clickable } from "@webpack/common";
|
||||
|
||||
|
||||
const cl = classNameFactory("vc-plugin-ui-elements-");
|
||||
|
||||
export function UIElementsButton() {
|
||||
return (
|
||||
<Clickable onClick={() => openModal(modalProps => <UIElementsModal {...modalProps} />)}>
|
||||
<Card className={cl("button")} defaultPadding>
|
||||
<div className={cl("button-description")}>
|
||||
<Paragraph size="md" weight="semibold">
|
||||
Manage plugin UI elements
|
||||
</Paragraph>
|
||||
<Paragraph size="xs">
|
||||
Allows you to hide buttons you don't like
|
||||
</Paragraph>
|
||||
</div>
|
||||
<svg
|
||||
className={cl("button-arrow")}
|
||||
aria-hidden="true"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<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" />
|
||||
</svg>
|
||||
</Card>
|
||||
</Clickable >
|
||||
);
|
||||
}
|
||||
|
||||
function Section(props: {
|
||||
title: string;
|
||||
description: string;
|
||||
settings: SettingsPluginUiElements;
|
||||
buttonMap: Map<string, { icon: IconComponent; }>;
|
||||
}) {
|
||||
const { buttonMap, description, title, settings } = props;
|
||||
|
||||
return (
|
||||
<section>
|
||||
<BaseText tag="h3" size="xl" weight="bold">{title}</BaseText>
|
||||
<Paragraph size="sm" className={classes(Margins.top8, Margins.bottom20)}>{description}</Paragraph>
|
||||
|
||||
<div className={cl("switches")}>
|
||||
{Array.from(buttonMap, ([name, { icon }]) => {
|
||||
const Icon = icon ?? PlaceholderIcon;
|
||||
return (
|
||||
<Paragraph size="md" weight="semibold" key={name} className={cl("switches-row")}>
|
||||
<Icon height={20} width={20} />
|
||||
{name}
|
||||
<Switch
|
||||
checked={settings[name]?.enabled ?? true}
|
||||
onChange={v => {
|
||||
settings[name] ??= {} as any;
|
||||
settings[name].enabled = v;
|
||||
}}
|
||||
/>
|
||||
</Paragraph>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function UIElementsModal(props: ModalProps) {
|
||||
const { uiElements } = useSettings(["uiElements.*"]);
|
||||
|
||||
return (
|
||||
<ModalRoot {...props} size={ModalSize.MEDIUM}>
|
||||
<ModalContent className={cl("modal-content")}>
|
||||
<Section
|
||||
title="Chatbar Buttons"
|
||||
description="These are the buttons on the right side of the chat input bar"
|
||||
buttonMap={ChatBarButtonMap}
|
||||
settings={uiElements.chatBarButtons}
|
||||
/>
|
||||
<Section
|
||||
title="Message Popover Buttons"
|
||||
description="These are the floating buttons on the right when you hover over a message"
|
||||
buttonMap={MessagePopoverButtonMap}
|
||||
settings={uiElements.messagePopoverButtons}
|
||||
/>
|
||||
</ModalContent>
|
||||
</ModalRoot>
|
||||
);
|
||||
}
|
||||
|
|
@ -40,6 +40,7 @@ import { JSX } from "react";
|
|||
import Plugins, { ExcludedPlugins, PluginMeta } from "~plugins";
|
||||
|
||||
import { PluginCard } from "./PluginCard";
|
||||
import { UIElementsButton } from "./UIElements";
|
||||
|
||||
export const cl = classNameFactory("vc-plugins-");
|
||||
export const logger = new Logger("PluginSettings", "#a6d189");
|
||||
|
|
@ -260,6 +261,8 @@ function PluginSettings() {
|
|||
<SettingsTab title="Plugins">
|
||||
<ReloadRequiredCard required={changes.hasChanges} />
|
||||
|
||||
<UIElementsButton />
|
||||
|
||||
<HeadingTertiary className={classes(Margins.top20, Margins.bottom8)}>
|
||||
Filters
|
||||
</HeadingTertiary>
|
||||
|
|
|
|||
|
|
@ -43,6 +43,15 @@ migratePluginSettings("HideMedia", "HideAttachments");
|
|||
|
||||
const hasMedia = (msg: Message) => msg.attachments.length > 0 || msg.embeds.length > 0 || msg.stickerItems.length > 0;
|
||||
|
||||
async function toggleHide(channelId: string, messageId: string) {
|
||||
const ids = await getHiddenMessages();
|
||||
if (!ids.delete(messageId))
|
||||
ids.add(messageId);
|
||||
|
||||
await saveHiddenMessages(ids);
|
||||
updateMessage(channelId, messageId);
|
||||
}
|
||||
|
||||
export default definePlugin({
|
||||
name: "HideMedia",
|
||||
description: "Hide attachments and embeds for individual messages via hover button",
|
||||
|
|
@ -57,18 +66,21 @@ export default definePlugin({
|
|||
}
|
||||
}],
|
||||
|
||||
renderMessagePopoverButton(msg) {
|
||||
if (!hasMedia(msg) && !msg.messageSnapshots.some(s => hasMedia(s.message))) return null;
|
||||
messagePopoverButton: {
|
||||
icon: ImageInvisible,
|
||||
render(msg) {
|
||||
if (!hasMedia(msg) && !msg.messageSnapshots.some(s => hasMedia(s.message))) return null;
|
||||
|
||||
const isHidden = hiddenMessages.has(msg.id);
|
||||
const isHidden = hiddenMessages.has(msg.id);
|
||||
|
||||
return {
|
||||
label: isHidden ? "Show Media" : "Hide Media",
|
||||
icon: isHidden ? ImageVisible : ImageInvisible,
|
||||
message: msg,
|
||||
channel: ChannelStore.getChannel(msg.channel_id),
|
||||
onClick: () => this.toggleHide(msg.channel_id, msg.id)
|
||||
};
|
||||
return {
|
||||
label: isHidden ? "Show Media" : "Hide Media",
|
||||
icon: isHidden ? ImageVisible : ImageInvisible,
|
||||
message: msg,
|
||||
channel: ChannelStore.getChannel(msg.channel_id),
|
||||
onClick: () => toggleHide(msg.channel_id, msg.id)
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
renderMessageAccessory({ message }) {
|
||||
|
|
@ -92,13 +104,4 @@ export default definePlugin({
|
|||
shouldHide(messageId: string) {
|
||||
return hiddenMessages.has(messageId);
|
||||
},
|
||||
|
||||
async toggleHide(channelId: string, messageId: string) {
|
||||
const ids = await getHiddenMessages();
|
||||
if (!ids.delete(messageId))
|
||||
ids.add(messageId);
|
||||
|
||||
await saveHiddenMessages(ids);
|
||||
updateMessage(channelId, messageId);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
import { ChatBarButton, ChatBarButtonFactory } from "@api/ChatButtons";
|
||||
import { generateId, sendBotMessage } from "@api/Commands";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { StartAt } from "@utils/types";
|
||||
import definePlugin, { IconComponent, StartAt } from "@utils/types";
|
||||
import { CloudUpload, MessageAttachment } from "@vencord/discord-types";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { DraftStore, DraftType, SelectedChannelStore, UserStore, useStateFromStores } from "@webpack/common";
|
||||
|
|
@ -73,6 +73,22 @@ const getAttachments = async (channelId: string) =>
|
|||
);
|
||||
|
||||
|
||||
const PreviewIcon: IconComponent = ({ height = 20, width = 20, className }) => {
|
||||
return (
|
||||
<svg
|
||||
fill="currentColor"
|
||||
fillRule="evenodd"
|
||||
width={width}
|
||||
height={height}
|
||||
className={className}
|
||||
viewBox="0 0 24 24"
|
||||
style={{ scale: "1.096", translate: "0 -1px" }}
|
||||
>
|
||||
<path d="M22.89 11.7c.07.2.07.4 0 .6C22.27 13.9 19.1 21 12 21c-7.11 0-10.27-7.11-10.89-8.7a.83.83 0 0 1 0-.6C1.73 10.1 4.9 3 12 3c7.11 0 10.27 7.11 10.89 8.7Zm-4.5-3.62A15.11 15.11 0 0 1 20.85 12c-.38.88-1.18 2.47-2.46 3.92C16.87 17.62 14.8 19 12 19c-2.8 0-4.87-1.38-6.39-3.08A15.11 15.11 0 0 1 3.15 12c.38-.88 1.18-2.47 2.46-3.92C7.13 6.38 9.2 5 12 5c2.8 0 4.87 1.38 6.39 3.08ZM15.56 11.77c.2-.1.44.02.44.23a4 4 0 1 1-4-4c.21 0 .33.25.23.44a2.5 2.5 0 0 0 3.32 3.32Z" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
const PreviewButton: ChatBarButtonFactory = ({ isMainChat, isEmpty, type: { attachments } }) => {
|
||||
const channelId = SelectedChannelStore.getChannelId();
|
||||
const draft = useStateFromStores([DraftStore], () => getDraft(channelId));
|
||||
|
|
@ -102,16 +118,7 @@ const PreviewButton: ChatBarButtonFactory = ({ isMainChat, isEmpty, type: { atta
|
|||
}
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
fillRule="evenodd"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
style={{ scale: "1.096", translate: "0 -1px" }}
|
||||
>
|
||||
<path d="M22.89 11.7c.07.2.07.4 0 .6C22.27 13.9 19.1 21 12 21c-7.11 0-10.27-7.11-10.89-8.7a.83.83 0 0 1 0-.6C1.73 10.1 4.9 3 12 3c7.11 0 10.27 7.11 10.89 8.7Zm-4.5-3.62A15.11 15.11 0 0 1 20.85 12c-.38.88-1.18 2.47-2.46 3.92C16.87 17.62 14.8 19 12 19c-2.8 0-4.87-1.38-6.39-3.08A15.11 15.11 0 0 1 3.15 12c.38-.88 1.18-2.47 2.46-3.92C7.13 6.38 9.2 5 12 5c2.8 0 4.87 1.38 6.39 3.08ZM15.56 11.77c.2-.1.44.02.44.23a4 4 0 1 1-4-4c.21 0 .33.25.23.44a2.5 2.5 0 0 0 3.32 3.32Z" />
|
||||
</svg>
|
||||
<PreviewIcon />
|
||||
</ChatBarButton>
|
||||
);
|
||||
|
||||
|
|
@ -125,5 +132,8 @@ export default definePlugin({
|
|||
// This makes the popping in less awkward
|
||||
startAt: StartAt.Init,
|
||||
|
||||
renderChatBarButton: PreviewButton,
|
||||
chatBarButton: {
|
||||
icon: PreviewIcon,
|
||||
render: PreviewButton
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -21,29 +21,12 @@ import { insertTextIntoChatInputBox } from "@utils/discord";
|
|||
import definePlugin from "@utils/types";
|
||||
import { ChannelStore, PermissionsBits, PermissionStore } from "@webpack/common";
|
||||
|
||||
export default definePlugin({
|
||||
name: "QuickMention",
|
||||
authors: [Devs.kemo],
|
||||
description: "Adds a quick mention button to the message actions bar",
|
||||
|
||||
renderMessagePopoverButton(msg) {
|
||||
const channel = ChannelStore.getChannel(msg.channel_id);
|
||||
if (channel.guild_id && !PermissionStore.can(PermissionsBits.SEND_MESSAGES, channel)) return null;
|
||||
|
||||
return {
|
||||
label: "Quick Mention",
|
||||
icon: this.Icon,
|
||||
message: msg,
|
||||
channel,
|
||||
onClick: () => insertTextIntoChatInputBox(`<@${msg.author.id}> `)
|
||||
};
|
||||
},
|
||||
|
||||
Icon: () => (
|
||||
function Icon({ height = 24, width = 24, className = "icon" }: { height?: number | string; width?: number | string; className?: string; }) {
|
||||
return (
|
||||
<svg
|
||||
className="icon"
|
||||
height="24"
|
||||
width="24"
|
||||
className={className}
|
||||
height={height}
|
||||
width={width}
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
>
|
||||
|
|
@ -51,5 +34,27 @@ export default definePlugin({
|
|||
d="M12 2C6.486 2 2 6.486 2 12C2 17.515 6.486 22 12 22C14.039 22 15.993 21.398 17.652 20.259L16.521 18.611C15.195 19.519 13.633 20 12 20C7.589 20 4 16.411 4 12C4 7.589 7.589 4 12 4C16.411 4 20 7.589 20 12V12.782C20 14.17 19.402 15 18.4 15L18.398 15.018C18.338 15.005 18.273 15 18.209 15H18C17.437 15 16.6 14.182 16.6 13.631V12C16.6 9.464 14.537 7.4 12 7.4C9.463 7.4 7.4 9.463 7.4 12C7.4 14.537 9.463 16.6 12 16.6C13.234 16.6 14.35 16.106 15.177 15.313C15.826 16.269 16.93 17 18 17L18.002 16.981C18.064 16.994 18.129 17 18.195 17H18.4C20.552 17 22 15.306 22 12.782V12C22 6.486 17.514 2 12 2ZM12 14.599C10.566 14.599 9.4 13.433 9.4 11.999C9.4 10.565 10.566 9.399 12 9.399C13.434 9.399 14.6 10.565 14.6 11.999C14.6 13.433 13.434 14.599 12 14.599Z"
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export default definePlugin({
|
||||
name: "QuickMention",
|
||||
authors: [Devs.kemo],
|
||||
description: "Adds a quick mention button to the message actions bar",
|
||||
|
||||
messagePopoverButton: {
|
||||
icon: Icon,
|
||||
render(msg) {
|
||||
const channel = ChannelStore.getChannel(msg.channel_id);
|
||||
if (channel.guild_id && !PermissionStore.can(PermissionsBits.SEND_MESSAGES, channel)) return null;
|
||||
|
||||
return {
|
||||
label: "Quick Mention",
|
||||
icon: Icon,
|
||||
message: msg,
|
||||
channel,
|
||||
onClick: () => insertTextIntoChatInputBox(`<@${msg.author.id}> `)
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import { Devs } from "@utils/constants";
|
|||
import { getTheme, insertTextIntoChatInputBox, Theme } from "@utils/discord";
|
||||
import { Margins } from "@utils/margins";
|
||||
import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModal } from "@utils/modal";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import definePlugin, { IconComponent, OptionType } from "@utils/types";
|
||||
import { Button, Forms, Parser, Select, useMemo, useState } from "@webpack/common";
|
||||
|
||||
const settings = definePluginSettings({
|
||||
|
|
@ -125,7 +125,26 @@ function PickerModal({ rootProps, close }: { rootProps: ModalProps, close(): voi
|
|||
);
|
||||
}
|
||||
|
||||
const ChatBarIcon: ChatBarButtonFactory = ({ isMainChat }) => {
|
||||
const SendTimestampIcon: IconComponent = ({ height = 20, width = 20, className }) => {
|
||||
return (
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
role="img"
|
||||
width={width}
|
||||
height={height}
|
||||
className={className}
|
||||
viewBox="0 0 24 24"
|
||||
style={{ scale: "1.2" }}
|
||||
>
|
||||
<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>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
const SendTimestampButton: ChatBarButtonFactory = ({ isMainChat }) => {
|
||||
if (!isMainChat) return null;
|
||||
|
||||
return (
|
||||
|
|
@ -141,19 +160,7 @@ const ChatBarIcon: ChatBarButtonFactory = ({ isMainChat }) => {
|
|||
}}
|
||||
buttonProps={{ "aria-haspopup": "dialog" }}
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
role="img"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
style={{ scale: "1.2" }}
|
||||
>
|
||||
<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>
|
||||
</svg>
|
||||
<SendTimestampIcon />
|
||||
</ChatBarButton>
|
||||
);
|
||||
};
|
||||
|
|
@ -164,7 +171,10 @@ export default definePlugin({
|
|||
authors: [Devs.Ven, Devs.Tyler, Devs.Grzesiek11],
|
||||
settings,
|
||||
|
||||
renderChatBarButton: ChatBarIcon,
|
||||
chatBarButton: {
|
||||
icon: SendTimestampIcon,
|
||||
render: SendTimestampButton
|
||||
},
|
||||
|
||||
onBeforeMessageSend(_, msg) {
|
||||
if (settings.store.replaceMessageContents) {
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ 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";
|
||||
import definePlugin, { IconComponent, OptionType } from "@utils/types";
|
||||
import { React, useEffect, useState } from "@webpack/common";
|
||||
|
||||
let lastState = false;
|
||||
|
|
@ -41,6 +41,33 @@ const settings = definePluginSettings({
|
|||
}
|
||||
});
|
||||
|
||||
function SilentMessageDisabledIcon() {
|
||||
return (
|
||||
<SilentMessageIcon>
|
||||
<mask id="vc-silent-msg-mask">
|
||||
<path fill="#fff" d="M0 0h24v24H0Z" />
|
||||
<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" />
|
||||
</SilentMessageIcon>
|
||||
);
|
||||
}
|
||||
|
||||
const SilentMessageIcon: IconComponent = ({ height = 20, width = 20, className, children }) => {
|
||||
return (
|
||||
<svg
|
||||
width={width}
|
||||
height={height}
|
||||
viewBox="0 0 24 24"
|
||||
className={className}
|
||||
style={{ scale: "1.2" }}
|
||||
>
|
||||
<path fill="currentColor" mask="url(#vc-silent-msg-mask)" d="M18 10.7101C15.1085 9.84957 13 7.17102 13 4c0-.30736.0198-.6101.0582-.907C12.7147 3.03189 12.3611 3 12 3 8.686 3 6 5.686 6 9v5c0 1.657-1.344 3-3 3v1h18v-1c-1.656 0-3-1.343-3-3v-3.2899ZM8.55493 19c.693 1.19 1.96897 2 3.44497 2s2.752-.81 3.445-2H8.55493ZM18.2624 5.50209 21 2.5V1h-4.9651v1.49791h2.4411L16 5.61088V7h5V5.50209h-2.7376Z" />
|
||||
{children}
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
const SilentMessageToggle: ChatBarButtonFactory = ({ isMainChat }) => {
|
||||
const [enabled, setEnabled] = useState(lastState);
|
||||
|
||||
|
|
@ -68,21 +95,7 @@ const SilentMessageToggle: ChatBarButtonFactory = ({ isMainChat }) => {
|
|||
tooltip={enabled ? "Disable Silent Message" : "Enable Silent Message"}
|
||||
onClick={() => setEnabledValue(!enabled)}
|
||||
>
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
style={{ scale: "1.2" }}
|
||||
>
|
||||
<path fill="currentColor" mask="url(#vc-silent-msg-mask)" d="M18 10.7101C15.1085 9.84957 13 7.17102 13 4c0-.30736.0198-.6101.0582-.907C12.7147 3.03189 12.3611 3 12 3 8.686 3 6 5.686 6 9v5c0 1.657-1.344 3-3 3v1h18v-1c-1.656 0-3-1.343-3-3v-3.2899ZM8.55493 19c.693 1.19 1.96897 2 3.44497 2s2.752-.81 3.445-2H8.55493ZM18.2624 5.50209 21 2.5V1h-4.9651v1.49791h2.4411L16 5.61088V7h5V5.50209h-2.7376Z" />
|
||||
{!enabled && <>
|
||||
<mask id="vc-silent-msg-mask">
|
||||
<path fill="#fff" d="M0 0h24v24H0Z" />
|
||||
<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" />
|
||||
</>}
|
||||
</svg>
|
||||
{enabled ? <SilentMessageIcon /> : <SilentMessageDisabledIcon />}
|
||||
</ChatBarButton>
|
||||
);
|
||||
};
|
||||
|
|
@ -93,5 +106,8 @@ export default definePlugin({
|
|||
description: "Adds a button to the chat bar to toggle sending a silent message.",
|
||||
settings,
|
||||
|
||||
renderChatBarButton: SilentMessageToggle,
|
||||
chatBarButton: {
|
||||
icon: SilentMessageIcon,
|
||||
render: SilentMessageToggle
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import { ApplicationCommandInputType, ApplicationCommandOptionType, findOption,
|
|||
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import definePlugin, { IconComponent, OptionType } from "@utils/types";
|
||||
import { FluxDispatcher, Menu, React } from "@webpack/common";
|
||||
|
||||
const settings = definePluginSettings({
|
||||
|
|
@ -43,6 +43,33 @@ const settings = definePluginSettings({
|
|||
}
|
||||
});
|
||||
|
||||
function SilentTypingEnabledIcon() {
|
||||
return (
|
||||
<SilentTypingIcon>
|
||||
<mask id="silent-typing-msg-mask">
|
||||
<path fill="#fff" d="M0 0h24v24H0Z"></path>
|
||||
<path stroke="#000" strokeWidth="5.99068" d="M0 24 24 0" transform="translate(-2, -3)"></path>
|
||||
</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" />
|
||||
</SilentTypingIcon>
|
||||
);
|
||||
}
|
||||
|
||||
const SilentTypingIcon: IconComponent = ({ height = 20, width = 20, className, children }) => {
|
||||
return (
|
||||
<svg
|
||||
width={width}
|
||||
height={height}
|
||||
className={className}
|
||||
viewBox="0 0 24 24"
|
||||
style={{ scale: "1.2" }}
|
||||
>
|
||||
<path fill="currentColor" mask="url(#silent-typing-msg-mask)" d="M18.333 15.556H1.667a1.667 1.667 0 0 1 -1.667 -1.667v-10a1.667 1.667 0 0 1 1.667 -1.667h16.667a1.667 1.667 0 0 1 1.667 1.667v10a1.667 1.667 0 0 1 -1.667 1.667M4.444 6.25V4.861a0.417 0.417 0 0 0 -0.417 -0.417H2.639a0.417 0.417 0 0 0 -0.417 0.417V6.25a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V4.861a0.417 0.417 0 0 0 -0.417 -0.417H5.973a0.417 0.417 0 0 0 -0.417 0.417V6.25a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V4.861a0.417 0.417 0 0 0 -0.417 -0.417h-1.389a0.417 0.417 0 0 0 -0.417 0.417V6.25a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V4.861a0.417 0.417 0 0 0 -0.417 -0.417h-1.389a0.417 0.417 0 0 0 -0.417 0.417V6.25a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V4.861a0.417 0.417 0 0 0 -0.417 -0.417h-1.389a0.417 0.417 0 0 0 -0.417 0.417V6.25a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m-11.667 3.333V8.194a0.417 0.417 0 0 0 -0.417 -0.417H4.306a0.417 0.417 0 0 0 -0.417 0.417V9.583a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V8.194a0.417 0.417 0 0 0 -0.417 -0.417H7.639a0.417 0.417 0 0 0 -0.417 0.417V9.583a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V8.194a0.417 0.417 0 0 0 -0.417 -0.417h-1.389a0.417 0.417 0 0 0 -0.417 0.417V9.583a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V8.194a0.417 0.417 0 0 0 -0.417 -0.417h-1.389a0.417 0.417 0 0 0 -0.417 0.417V9.583a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m-11.667 3.333v-1.389a0.417 0.417 0 0 0 -0.417 -0.417H2.639a0.417 0.417 0 0 0 -0.417 0.417V12.917a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m10 0v-1.389a0.417 0.417 0 0 0 -0.417 -0.417H5.973a0.417 0.417 0 0 0 -0.417 0.417V12.917a0.417 0.417 0 0 0 0.417 0.417h8.056a0.417 0.417 0 0 0 0.417 -0.417m3.333 0v-1.389a0.417 0.417 0 0 0 -0.417 -0.417h-1.389a0.417 0.417 0 0 0 -0.417 0.417V12.917a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417" transform="translate(2, 3)" />
|
||||
{children}
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
const SilentTypingToggle: ChatBarButtonFactory = ({ isMainChat }) => {
|
||||
const { isEnabled, showIcon } = settings.use(["isEnabled", "showIcon"]);
|
||||
const toggle = () => settings.store.isEnabled = !settings.store.isEnabled;
|
||||
|
|
@ -54,18 +81,7 @@ const SilentTypingToggle: ChatBarButtonFactory = ({ isMainChat }) => {
|
|||
tooltip={isEnabled ? "Disable Silent Typing" : "Enable Silent Typing"}
|
||||
onClick={toggle}
|
||||
>
|
||||
<svg width="20" height="20" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" style={{ scale: "1.2" }}>
|
||||
<path fill="currentColor" mask="url(#silent-typing-msg-mask)" d="M18.333 15.556H1.667a1.667 1.667 0 0 1 -1.667 -1.667v-10a1.667 1.667 0 0 1 1.667 -1.667h16.667a1.667 1.667 0 0 1 1.667 1.667v10a1.667 1.667 0 0 1 -1.667 1.667M4.444 6.25V4.861a0.417 0.417 0 0 0 -0.417 -0.417H2.639a0.417 0.417 0 0 0 -0.417 0.417V6.25a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V4.861a0.417 0.417 0 0 0 -0.417 -0.417H5.973a0.417 0.417 0 0 0 -0.417 0.417V6.25a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V4.861a0.417 0.417 0 0 0 -0.417 -0.417h-1.389a0.417 0.417 0 0 0 -0.417 0.417V6.25a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V4.861a0.417 0.417 0 0 0 -0.417 -0.417h-1.389a0.417 0.417 0 0 0 -0.417 0.417V6.25a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V4.861a0.417 0.417 0 0 0 -0.417 -0.417h-1.389a0.417 0.417 0 0 0 -0.417 0.417V6.25a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m-11.667 3.333V8.194a0.417 0.417 0 0 0 -0.417 -0.417H4.306a0.417 0.417 0 0 0 -0.417 0.417V9.583a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V8.194a0.417 0.417 0 0 0 -0.417 -0.417H7.639a0.417 0.417 0 0 0 -0.417 0.417V9.583a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V8.194a0.417 0.417 0 0 0 -0.417 -0.417h-1.389a0.417 0.417 0 0 0 -0.417 0.417V9.583a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V8.194a0.417 0.417 0 0 0 -0.417 -0.417h-1.389a0.417 0.417 0 0 0 -0.417 0.417V9.583a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m-11.667 3.333v-1.389a0.417 0.417 0 0 0 -0.417 -0.417H2.639a0.417 0.417 0 0 0 -0.417 0.417V12.917a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m10 0v-1.389a0.417 0.417 0 0 0 -0.417 -0.417H5.973a0.417 0.417 0 0 0 -0.417 0.417V12.917a0.417 0.417 0 0 0 0.417 0.417h8.056a0.417 0.417 0 0 0 0.417 -0.417m3.333 0v-1.389a0.417 0.417 0 0 0 -0.417 -0.417h-1.389a0.417 0.417 0 0 0 -0.417 0.417V12.917a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417" transform="translate(2, 3)" />
|
||||
{isEnabled && (
|
||||
<>
|
||||
<mask id="silent-typing-msg-mask">
|
||||
<path fill="#fff" d="M0 0h24v24H0Z"></path>
|
||||
<path stroke="#000" strokeWidth="5.99068" d="M0 24 24 0" transform="translate(-2, -3)"></path>
|
||||
</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" />
|
||||
</>
|
||||
)}
|
||||
</svg>
|
||||
{isEnabled ? <SilentTypingEnabledIcon /> : <SilentTypingIcon />}
|
||||
</ChatBarButton>
|
||||
);
|
||||
};
|
||||
|
|
@ -137,5 +153,8 @@ export default definePlugin({
|
|||
FluxDispatcher.dispatch({ type: "TYPING_START_LOCAL", channelId });
|
||||
},
|
||||
|
||||
renderChatBarButton: SilentTypingToggle,
|
||||
chatBarButton: {
|
||||
icon: SilentTypingIcon,
|
||||
render: SilentTypingToggle
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -19,13 +19,14 @@
|
|||
import { ChatBarButton, ChatBarButtonFactory } from "@api/ChatButtons";
|
||||
import { classes } from "@utils/misc";
|
||||
import { openModal } from "@utils/modal";
|
||||
import { IconComponent } from "@utils/types";
|
||||
import { Alerts, Forms, Tooltip, useEffect, useState } from "@webpack/common";
|
||||
|
||||
import { settings } from "./settings";
|
||||
import { TranslateModal } from "./TranslateModal";
|
||||
import { cl } from "./utils";
|
||||
|
||||
export function TranslateIcon({ height = 20, width = 20, className }: { height?: number; width?: number; className?: string; }) {
|
||||
export const TranslateIcon: IconComponent = ({ height = 20, width = 20, className }) => {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 96 960 960"
|
||||
|
|
@ -36,7 +37,7 @@ export function TranslateIcon({ height = 20, width = 20, className }: { height?:
|
|||
<path fill="currentColor" d="m475 976 181-480h82l186 480h-87l-41-126H604l-47 126h-82Zm151-196h142l-70-194h-2l-70 194Zm-466 76-55-55 204-204q-38-44-67.5-88.5T190 416h87q17 33 37.5 62.5T361 539q45-47 75-97.5T487 336H40v-80h280v-80h80v80h280v80H567q-22 69-58.5 135.5T419 598l98 99-30 81-127-122-200 200Z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export let setShouldShowTranslateEnabledTooltip: undefined | ((show: boolean) => void);
|
||||
|
||||
|
|
|
|||
|
|
@ -74,22 +74,28 @@ export default definePlugin({
|
|||
|
||||
renderMessageAccessory: props => <TranslationAccessory message={props.message} />,
|
||||
|
||||
renderChatBarButton: TranslateChatBarIcon,
|
||||
chatBarButton: {
|
||||
icon: TranslateIcon,
|
||||
render: TranslateChatBarIcon
|
||||
},
|
||||
|
||||
renderMessagePopoverButton(message: Message) {
|
||||
const content = getMessageContent(message);
|
||||
if (!content) return null;
|
||||
messagePopoverButton: {
|
||||
icon: TranslateIcon,
|
||||
render(message: Message) {
|
||||
const content = getMessageContent(message);
|
||||
if (!content) return null;
|
||||
|
||||
return {
|
||||
label: "Translate",
|
||||
icon: TranslateIcon,
|
||||
message,
|
||||
channel: ChannelStore.getChannel(message.channel_id),
|
||||
onClick: async () => {
|
||||
const trans = await translate("received", content);
|
||||
handleTranslate(message.id, trans);
|
||||
}
|
||||
};
|
||||
return {
|
||||
label: "Translate",
|
||||
icon: TranslateIcon,
|
||||
message,
|
||||
channel: ChannelStore.getChannel(message.channel_id),
|
||||
onClick: async () => {
|
||||
const trans = await translate("received", content);
|
||||
handleTranslate(message.id, trans);
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
async onBeforeMessageSend(_, message) {
|
||||
|
|
|
|||
|
|
@ -26,16 +26,25 @@ import { Devs } from "@utils/constants";
|
|||
import { copyWithToast, getCurrentGuild, getIntlMessage } from "@utils/discord";
|
||||
import { Margins } from "@utils/margins";
|
||||
import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import definePlugin, { IconComponent, OptionType } from "@utils/types";
|
||||
import { Message } from "@vencord/discord-types";
|
||||
import { Button, ChannelStore, Forms, GuildRoleStore, Menu, Text } from "@webpack/common";
|
||||
|
||||
|
||||
const CopyIcon = () => {
|
||||
return <svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" width="20" height="20">
|
||||
<path d="M12.9297 3.25007C12.7343 3.05261 12.4154 3.05226 12.2196 3.24928L11.5746 3.89824C11.3811 4.09297 11.3808 4.40733 11.5739 4.60245L16.5685 9.64824C16.7614 9.84309 16.7614 10.1569 16.5685 10.3517L11.5739 15.3975C11.3808 15.5927 11.3811 15.907 11.5746 16.1017L12.2196 16.7507C12.4154 16.9477 12.7343 16.9474 12.9297 16.7499L19.2604 10.3517C19.4532 10.1568 19.4532 9.84314 19.2604 9.64832L12.9297 3.25007Z" />
|
||||
<path d="M8.42616 4.60245C8.6193 4.40733 8.61898 4.09297 8.42545 3.89824L7.78047 3.24928C7.58466 3.05226 7.26578 3.05261 7.07041 3.25007L0.739669 9.64832C0.5469 9.84314 0.546901 10.1568 0.739669 10.3517L7.07041 16.7499C7.26578 16.9474 7.58465 16.9477 7.78047 16.7507L8.42545 16.1017C8.61898 15.907 8.6193 15.5927 8.42616 15.3975L3.43155 10.3517C3.23869 10.1569 3.23869 9.84309 3.43155 9.64824L8.42616 4.60245Z" />
|
||||
</svg>;
|
||||
const CopyIcon: IconComponent = ({ height = 20, width = 20, className }) => {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
width={width}
|
||||
height={height}
|
||||
className={className}
|
||||
>
|
||||
<path d="M12.9297 3.25007C12.7343 3.05261 12.4154 3.05226 12.2196 3.24928L11.5746 3.89824C11.3811 4.09297 11.3808 4.40733 11.5739 4.60245L16.5685 9.64824C16.7614 9.84309 16.7614 10.1569 16.5685 10.3517L11.5739 15.3975C11.3808 15.5927 11.3811 15.907 11.5746 16.1017L12.2196 16.7507C12.4154 16.9477 12.7343 16.9474 12.9297 16.7499L19.2604 10.3517C19.4532 10.1568 19.4532 9.84314 19.2604 9.64832L12.9297 3.25007Z" />
|
||||
<path d="M8.42616 4.60245C8.6193 4.40733 8.61898 4.09297 8.42545 3.89824L7.78047 3.24928C7.58466 3.05226 7.26578 3.05261 7.07041 3.25007L0.739669 9.64832C0.5469 9.84314 0.546901 10.1568 0.739669 10.3517L7.07041 16.7499C7.26578 16.9474 7.58465 16.9477 7.78047 16.7507L8.42545 16.1017C8.61898 15.907 8.6193 15.5927 8.42616 15.3975L3.43155 10.3517C3.23869 10.1569 3.23869 9.84309 3.43155 9.64824L8.42616 4.60245Z" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
function sortObject<T extends object>(obj: T): T {
|
||||
|
|
@ -133,9 +142,11 @@ function MakeContextCallback(name: "Guild" | "Role" | "User" | "Channel"): NavCo
|
|||
children = p.children;
|
||||
}
|
||||
|
||||
// typescript parser goes crazy if this is inline
|
||||
const id = `vc-view-${name.toLowerCase()}-raw`;
|
||||
children.splice(-1, 0,
|
||||
<Menu.MenuItem
|
||||
id={`vc-view-${name.toLowerCase()}-raw`}
|
||||
id={id}
|
||||
label="View Raw"
|
||||
action={() => openViewRawModal(JSON.stringify(value, null, 4), name)}
|
||||
icon={CopyIcon}
|
||||
|
|
@ -177,38 +188,41 @@ export default definePlugin({
|
|||
"dev-context": devContextCallback
|
||||
},
|
||||
|
||||
renderMessagePopoverButton(msg) {
|
||||
const handleClick = () => {
|
||||
if (settings.store.clickMethod === "Right") {
|
||||
copyWithToast(msg.content);
|
||||
} else {
|
||||
openViewRawModalMessage(msg);
|
||||
}
|
||||
};
|
||||
messagePopoverButton: {
|
||||
icon: CopyIcon,
|
||||
render(msg) {
|
||||
const handleClick = () => {
|
||||
if (settings.store.clickMethod === "Right") {
|
||||
copyWithToast(msg.content);
|
||||
} else {
|
||||
openViewRawModalMessage(msg);
|
||||
}
|
||||
};
|
||||
|
||||
const handleContextMenu = e => {
|
||||
if (settings.store.clickMethod === "Left") {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
copyWithToast(msg.content);
|
||||
} else {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
openViewRawModalMessage(msg);
|
||||
}
|
||||
};
|
||||
const handleContextMenu = e => {
|
||||
if (settings.store.clickMethod === "Left") {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
copyWithToast(msg.content);
|
||||
} else {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
openViewRawModalMessage(msg);
|
||||
}
|
||||
};
|
||||
|
||||
const label = settings.store.clickMethod === "Right"
|
||||
? "Copy Raw (Left Click) / View Raw (Right Click)"
|
||||
: "View Raw (Left Click) / Copy Raw (Right Click)";
|
||||
const label = settings.store.clickMethod === "Right"
|
||||
? "Copy Raw (Left Click) / View Raw (Right Click)"
|
||||
: "View Raw (Left Click) / Copy Raw (Right Click)";
|
||||
|
||||
return {
|
||||
label,
|
||||
icon: CopyIcon,
|
||||
message: msg,
|
||||
channel: ChannelStore.getChannel(msg.channel_id),
|
||||
onClick: handleClick,
|
||||
onContextMenu: handleContextMenu
|
||||
};
|
||||
return {
|
||||
label,
|
||||
icon: CopyIcon,
|
||||
message: msg,
|
||||
channel: ChannelStore.getChannel(msg.channel_id),
|
||||
onClick: handleClick,
|
||||
onContextMenu: handleContextMenu
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -16,17 +16,17 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import type { ProfileBadge } from "@api/Badges";
|
||||
import type { ChatBarButtonFactory } from "@api/ChatButtons";
|
||||
import type { NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import type { MemberListDecoratorFactory } from "@api/MemberListDecorators";
|
||||
import type { MessageAccessoryFactory } from "@api/MessageAccessories";
|
||||
import type { MessageDecorationFactory } from "@api/MessageDecorations";
|
||||
import type { MessageClickListener, MessageEditListener, MessageSendListener } from "@api/MessageEvents";
|
||||
import type { MessagePopoverButtonFactory } from "@api/MessagePopover";
|
||||
import type { Command, FluxEvents } from "@vencord/discord-types";
|
||||
import type { ReactNode } from "react";
|
||||
import type { LiteralUnion } from "type-fest";
|
||||
import { ProfileBadge } from "@api/Badges";
|
||||
import { ChatBarButtonData } from "@api/ChatButtons";
|
||||
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import { MemberListDecoratorFactory } from "@api/MemberListDecorators";
|
||||
import { MessageAccessoryFactory } from "@api/MessageAccessories";
|
||||
import { MessageDecorationFactory } from "@api/MessageDecorations";
|
||||
import { MessageClickListener, MessageEditListener, MessageSendListener } from "@api/MessageEvents";
|
||||
import { MessagePopoverButtonData } from "@api/MessagePopover";
|
||||
import { Command, FluxEvents } from "@vencord/discord-types";
|
||||
import { ReactNode } from "react";
|
||||
import { LiteralUnion } from "type-fest";
|
||||
|
||||
// exists to export default definePlugin({...})
|
||||
export default function definePlugin<P extends PluginDef>(p: P & Record<PropertyKey, any>) {
|
||||
|
|
@ -95,6 +95,8 @@ export interface Plugin extends PluginDef {
|
|||
isDependency?: boolean;
|
||||
}
|
||||
|
||||
export type IconComponent = (props: IconProps & Record<string, any>) => ReactNode;
|
||||
export type IconProps = { height?: number | string; width?: number | string; className?: string; };
|
||||
export interface PluginDef {
|
||||
name: string;
|
||||
description: string;
|
||||
|
|
@ -180,17 +182,27 @@ export interface PluginDef {
|
|||
|
||||
userProfileBadge?: ProfileBadge;
|
||||
|
||||
messagePopoverButton?: MessagePopoverButtonData;
|
||||
chatBarButton?: ChatBarButtonData;
|
||||
|
||||
onMessageClick?: MessageClickListener;
|
||||
onBeforeMessageSend?: MessageSendListener;
|
||||
onBeforeMessageEdit?: MessageEditListener;
|
||||
|
||||
renderMessagePopoverButton?: MessagePopoverButtonFactory;
|
||||
renderMessageAccessory?: MessageAccessoryFactory;
|
||||
renderMessageDecoration?: MessageDecorationFactory;
|
||||
|
||||
renderMemberListDecorator?: MemberListDecoratorFactory;
|
||||
|
||||
renderChatBarButton?: ChatBarButtonFactory;
|
||||
// TODO: Remove eventually
|
||||
/**
|
||||
* @deprecated Use {@link chatBarButton} instead
|
||||
*/
|
||||
renderChatBarButton?: never;
|
||||
/**
|
||||
* @deprecated Use {@link messagePopoverButton} instead
|
||||
*/
|
||||
renderMessagePopoverButton?: never;
|
||||
}
|
||||
|
||||
export const enum StartAt {
|
||||
|
|
@ -420,3 +432,5 @@ export type PluginNative<PluginExports extends Record<string, (event: Electron.I
|
|||
? (...args: Args) => Return extends Promise<any> ? Return : Promise<Return>
|
||||
: never;
|
||||
};
|
||||
|
||||
export type AllOrNothing<T> = T | { [K in keyof T]?: never; };
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue