Compare commits
4 Commits
v1.0.9
...
feat/relat
Author | SHA1 | Date | |
---|---|---|---|
![]() |
0e06b8d34c | ||
![]() |
b972aa1663 | ||
![]() |
3bf81ee0fa | ||
![]() |
486230a335 |
10
README.md
10
README.md
@ -4,14 +4,12 @@ The cutest Discord client mod
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Super easy to install (Download Installer, open, click install button, done)
|
- Super easy to install (one click installer)
|
||||||
- 100+ plugins built in: [See a list](https://gist.github.com/Vendicated/8696cde7b92548064a3ae92ead84d033)
|
- 90+ plugins built in: [See a list](https://gist.github.com/Vendicated/8696cde7b92548064a3ae92ead84d033)
|
||||||
- Some highlights: SpotifyControls, Experiments, NoTrack, MessageLogger, QuickReply, Free Emotes/Stickers, CustomCommands, ShowHiddenChannels, PronounDB
|
- Some highlights: SpotifyControls, Experiments, NoTrack, MessageLogger, QuickReply, Free Emotes/Stickers, CustomCommands, ShowHiddenChannels, PronounDB
|
||||||
- Fairly lightweight despite the many inbuilt plugins
|
|
||||||
- Excellent Browser Support: Run Vencord in your Browser via extension or UserScript
|
- Excellent Browser Support: Run Vencord in your Browser via extension or UserScript
|
||||||
- Works on any Discord branch: Stable, Canary or PTB all work (though for the best experience I recommend stable!)
|
|
||||||
- Custom CSS and Themes: Inbuilt css editor with support to import any css files (including BetterDiscord themes)
|
- Custom CSS and Themes: Inbuilt css editor with support to import any css files (including BetterDiscord themes)
|
||||||
- Privacy friendly, blocks Discord analytics & crash reporting out of the box and has no telemetry
|
- Works in all Electron versions (Confirmed working on versions 13-23)
|
||||||
- Maintained very actively, broken plugins are usually fixed within 12 hours
|
- Maintained very actively, broken plugins are usually fixed within 12 hours
|
||||||
|
|
||||||
## Installing / Uninstalling
|
## Installing / Uninstalling
|
||||||
@ -22,7 +20,7 @@ The cutest Discord client mod
|
|||||||
|
|
||||||
[](https://addons.mozilla.org/en-GB/firefox/addon/vencord-web/) [](https://chrome.google.com/webstore/detail/vencord-web/cbghhgpcnddeihccjmnadmkaejncjndb)
|
[](https://addons.mozilla.org/en-GB/firefox/addon/vencord-web/) [](https://chrome.google.com/webstore/detail/vencord-web/cbghhgpcnddeihccjmnadmkaejncjndb)
|
||||||
|
|
||||||
Or use the [UserScript](https://raw.githubusercontent.com/Vencord/builds/main/Vencord.user.js) - Please note that the CSS Editor, Themes loaded from remote sources and co. will not work in the UserScript. Use the extension if you need any of those
|
Or use the [UserScript](https://raw.githubusercontent.com/Vencord/builds/main/Vencord.user.js) - Please note that QuickCSS and plugins making use of external resources will not work with the UserScript.
|
||||||
|
|
||||||
## Building from Source
|
## Building from Source
|
||||||
|
|
||||||
|
@ -92,7 +92,6 @@ function GM_fetch(url, opt) {
|
|||||||
resp.arrayBuffer = () => blobTo("arrayBuffer", blob);
|
resp.arrayBuffer = () => blobTo("arrayBuffer", blob);
|
||||||
resp.text = () => blobTo("text", blob);
|
resp.text = () => blobTo("text", blob);
|
||||||
resp.json = async () => JSON.parse(await blobTo("text", blob));
|
resp.json = async () => JSON.parse(await blobTo("text", blob));
|
||||||
resp.headers = new Headers(parseHeaders(resp.responseHeaders));
|
|
||||||
resolve(resp);
|
resolve(resp);
|
||||||
};
|
};
|
||||||
options.ontimeout = () => reject("fetch timeout");
|
options.ontimeout = () => reject("fetch timeout");
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "vencord",
|
"name": "vencord",
|
||||||
"private": "true",
|
"private": "true",
|
||||||
"version": "1.0.9",
|
"version": "1.0.6",
|
||||||
"description": "The cutest Discord client mod",
|
"description": "The cutest Discord client mod",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"homepage": "https://github.com/Vendicated/Vencord#readme",
|
"homepage": "https://github.com/Vendicated/Vencord#readme",
|
||||||
|
@ -95,12 +95,3 @@ async function init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
init();
|
init();
|
||||||
|
|
||||||
if (!IS_WEB && Settings.winNativeTitleBar && navigator.platform.toLowerCase().startsWith("win")) {
|
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
|
||||||
document.head.append(Object.assign(document.createElement("style"), {
|
|
||||||
id: "vencord-native-titlebar-style",
|
|
||||||
textContent: "[class*=titleBar-]{display: none!important}"
|
|
||||||
}));
|
|
||||||
}, { once: true });
|
|
||||||
}
|
|
||||||
|
@ -89,6 +89,4 @@ export default ErrorBoundary.wrap(function NotificationComponent({
|
|||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
}, {
|
|
||||||
onError: ({ props }) => props.onClose!()
|
|
||||||
});
|
});
|
||||||
|
@ -34,7 +34,6 @@ export interface Settings {
|
|||||||
frameless: boolean;
|
frameless: boolean;
|
||||||
transparent: boolean;
|
transparent: boolean;
|
||||||
winCtrlQ: boolean;
|
winCtrlQ: boolean;
|
||||||
winNativeTitleBar: boolean;
|
|
||||||
plugins: {
|
plugins: {
|
||||||
[plugin: string]: {
|
[plugin: string]: {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
@ -58,7 +57,6 @@ const DefaultSettings: Settings = {
|
|||||||
frameless: false,
|
frameless: false,
|
||||||
transparent: false,
|
transparent: false,
|
||||||
winCtrlQ: false,
|
winCtrlQ: false,
|
||||||
winNativeTitleBar: false,
|
|
||||||
plugins: {},
|
plugins: {},
|
||||||
|
|
||||||
notifications: {
|
notifications: {
|
||||||
@ -92,7 +90,7 @@ function makeProxy(settings: any, root = settings, path = ""): Settings {
|
|||||||
// Return empty for plugins with no settings
|
// Return empty for plugins with no settings
|
||||||
if (path === "plugins" && p in plugins)
|
if (path === "plugins" && p in plugins)
|
||||||
return target[p] = makeProxy({
|
return target[p] = makeProxy({
|
||||||
enabled: plugins[p].required ?? plugins[p].enabledByDefault ?? false
|
enabled: plugins[p].required ?? false
|
||||||
}, root, `plugins.${p}`);
|
}, root, `plugins.${p}`);
|
||||||
|
|
||||||
// Since the property is not set, check if this is a plugin's setting and if so, try to resolve
|
// Since the property is not set, check if this is a plugin's setting and if so, try to resolve
|
||||||
|
@ -17,24 +17,20 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import Logger from "@utils/Logger";
|
import Logger from "@utils/Logger";
|
||||||
import { Margins } from "@utils/margins";
|
|
||||||
import { LazyComponent } from "@utils/misc";
|
import { LazyComponent } from "@utils/misc";
|
||||||
import { React } from "@webpack/common";
|
import { Margins, React } from "@webpack/common";
|
||||||
|
|
||||||
import { ErrorCard } from "./ErrorCard";
|
import { ErrorCard } from "./ErrorCard";
|
||||||
|
|
||||||
interface Props<T = any> {
|
interface Props {
|
||||||
/** Render nothing if an error occurs */
|
/** Render nothing if an error occurs */
|
||||||
noop?: boolean;
|
noop?: boolean;
|
||||||
/** Fallback component to render if an error occurs */
|
/** Fallback component to render if an error occurs */
|
||||||
fallback?: React.ComponentType<React.PropsWithChildren<{ error: any; message: string; stack: string; }>>;
|
fallback?: React.ComponentType<React.PropsWithChildren<{ error: any; message: string; stack: string; }>>;
|
||||||
/** called when an error occurs. The props property is only available if using .wrap */
|
/** called when an error occurs */
|
||||||
onError?(data: { error: Error, errorInfo: React.ErrorInfo, props: T; }): void;
|
onError?(error: Error, errorInfo: React.ErrorInfo): void;
|
||||||
/** Custom error message */
|
/** Custom error message */
|
||||||
message?: string;
|
message?: string;
|
||||||
|
|
||||||
/** The props passed to the wrapped component. Only used by wrap */
|
|
||||||
wrappedProps?: T;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const color = "#e78284";
|
const color = "#e78284";
|
||||||
@ -69,7 +65,7 @@ const ErrorBoundary = LazyComponent(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
||||||
this.props.onError?.({ error, errorInfo, props: this.props.wrappedProps });
|
this.props.onError?.(error, errorInfo);
|
||||||
logger.error("A component threw an Error\n", error);
|
logger.error("A component threw an Error\n", error);
|
||||||
logger.error("Component Stack", errorInfo.componentStack);
|
logger.error("Component Stack", errorInfo.componentStack);
|
||||||
}
|
}
|
||||||
@ -88,13 +84,15 @@ const ErrorBoundary = LazyComponent(() => {
|
|||||||
const msg = this.props.message || "An error occurred while rendering this Component. More info can be found below and in your console.";
|
const msg = this.props.message || "An error occurred while rendering this Component. More info can be found below and in your console.";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ErrorCard style={{ overflow: "hidden" }}>
|
<ErrorCard style={{
|
||||||
|
overflow: "hidden",
|
||||||
|
}}>
|
||||||
<h1>Oh no!</h1>
|
<h1>Oh no!</h1>
|
||||||
<p>{msg}</p>
|
<p>{msg}</p>
|
||||||
<code>
|
<code>
|
||||||
{this.state.message}
|
{this.state.message}
|
||||||
{!!this.state.stack && (
|
{!!this.state.stack && (
|
||||||
<pre className={Margins.top8}>
|
<pre className={Margins.marginTop8}>
|
||||||
{this.state.stack}
|
{this.state.stack}
|
||||||
</pre>
|
</pre>
|
||||||
)}
|
)}
|
||||||
@ -105,11 +103,11 @@ const ErrorBoundary = LazyComponent(() => {
|
|||||||
};
|
};
|
||||||
}) as
|
}) as
|
||||||
React.ComponentType<React.PropsWithChildren<Props>> & {
|
React.ComponentType<React.PropsWithChildren<Props>> & {
|
||||||
wrap<T extends object = any>(Component: React.ComponentType<T>, errorBoundaryProps?: Omit<Props<T>, "wrappedProps">): React.ComponentType<T>;
|
wrap<T extends object = any>(Component: React.ComponentType<T>, errorBoundaryProps?: Props): React.ComponentType<T>;
|
||||||
};
|
};
|
||||||
|
|
||||||
ErrorBoundary.wrap = (Component, errorBoundaryProps) => props => (
|
ErrorBoundary.wrap = (Component, errorBoundaryProps) => props => (
|
||||||
<ErrorBoundary {...errorBoundaryProps} wrappedProps={props}>
|
<ErrorBoundary {...errorBoundaryProps}>
|
||||||
<Component {...props} />
|
<Component {...props} />
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
);
|
);
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
.vc-error-card {
|
|
||||||
padding: 2em;
|
|
||||||
background-color: #e7828430;
|
|
||||||
border: 1px solid #e78284;
|
|
||||||
border-radius: 5px;
|
|
||||||
color: var(--text-normal, white);
|
|
||||||
}
|
|
@ -16,15 +16,24 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import "./ErrorCard.css";
|
import { Card } from "@webpack/common";
|
||||||
|
|
||||||
import { classes } from "@utils/misc";
|
interface Props {
|
||||||
import type { HTMLProps } from "react";
|
style?: React.CSSProperties;
|
||||||
|
className?: string;
|
||||||
export function ErrorCard(props: React.PropsWithChildren<HTMLProps<HTMLDivElement>>) {
|
}
|
||||||
|
export function ErrorCard(props: React.PropsWithChildren<Props>) {
|
||||||
return (
|
return (
|
||||||
<div {...props} className={classes(props.className, "vc-error-card")}>
|
<Card className={props.className} style={
|
||||||
|
{
|
||||||
|
padding: "2em",
|
||||||
|
backgroundColor: "#e7828430",
|
||||||
|
borderColor: "#e78284",
|
||||||
|
color: "var(--text-normal)",
|
||||||
|
...props.style
|
||||||
|
}
|
||||||
|
}>
|
||||||
{props.children}
|
{props.children}
|
||||||
</div>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -17,11 +17,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { debounce } from "@utils/debounce";
|
import { debounce } from "@utils/debounce";
|
||||||
import { Margins } from "@utils/margins";
|
|
||||||
import { makeCodeblock } from "@utils/misc";
|
import { makeCodeblock } from "@utils/misc";
|
||||||
import { canonicalizeMatch, canonicalizeReplace, ReplaceFn } from "@utils/patches";
|
import { canonicalizeMatch, canonicalizeReplace, ReplaceFn } from "@utils/patches";
|
||||||
import { search } from "@webpack";
|
import { search } from "@webpack";
|
||||||
import { Button, Clipboard, Forms, Parser, React, Switch, Text, TextInput } from "@webpack/common";
|
import { Button, Clipboard, Forms, Margins, Parser, React, Switch, Text, TextInput } from "@webpack/common";
|
||||||
|
|
||||||
import { CheckedTextInput } from "./CheckedTextInput";
|
import { CheckedTextInput } from "./CheckedTextInput";
|
||||||
import ErrorBoundary from "./ErrorBoundary";
|
import ErrorBoundary from "./ErrorBoundary";
|
||||||
@ -129,7 +128,7 @@ function ReplacementComponent({ module, match, replacement, setReplacementError
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{!!diff?.length && (
|
{!!diff?.length && (
|
||||||
<Button className={Margins.top20} onClick={() => {
|
<Button className={Margins.marginTop20} onClick={() => {
|
||||||
try {
|
try {
|
||||||
Function(patchedCode.replace(/^function\(/, "function patchedModule("));
|
Function(patchedCode.replace(/^function\(/, "function patchedModule("));
|
||||||
setCompileResult([true, "Compiled successfully"]);
|
setCompileResult([true, "Compiled successfully"]);
|
||||||
@ -203,7 +202,7 @@ function ReplacementInput({ replacement, setReplacement, replacementError }) {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<Switch
|
<Switch
|
||||||
className={Margins.top8}
|
className={Margins.marginTop8}
|
||||||
value={isFunc}
|
value={isFunc}
|
||||||
onChange={setIsFunc}
|
onChange={setIsFunc}
|
||||||
note="'replacement' will be evaled if this is toggled"
|
note="'replacement' will be evaled if this is toggled"
|
||||||
@ -257,7 +256,7 @@ function PatchHelper() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Forms.FormSection>
|
<Forms.FormSection>
|
||||||
<Text variant="heading-md/normal" tag="h2" className={Margins.bottom8}>Patch Helper</Text>
|
<Text variant="heading-md/normal" tag="h2" className={Margins.marginBottom8}>Patch Helper</Text>
|
||||||
<Forms.FormTitle>find</Forms.FormTitle>
|
<Forms.FormTitle>find</Forms.FormTitle>
|
||||||
<TextInput
|
<TextInput
|
||||||
type="text"
|
type="text"
|
||||||
@ -297,7 +296,7 @@ function PatchHelper() {
|
|||||||
|
|
||||||
{!!(find && match && replacement) && (
|
{!!(find && match && replacement) && (
|
||||||
<>
|
<>
|
||||||
<Forms.FormTitle className={Margins.top20}>Code</Forms.FormTitle>
|
<Forms.FormTitle className={Margins.marginTop20}>Code</Forms.FormTitle>
|
||||||
<div style={{ userSelect: "text" }}>{Parser.parse(makeCodeblock(code, "ts"))}</div>
|
<div style={{ userSelect: "text" }}>{Parser.parse(makeCodeblock(code, "ts"))}</div>
|
||||||
<Button onClick={() => Clipboard.copy(code)}>Copy to Clipboard</Button>
|
<Button onClick={() => Clipboard.copy(code)}>Copy to Clipboard</Button>
|
||||||
</>
|
</>
|
||||||
|
@ -30,12 +30,11 @@ import PluginModal from "@components/PluginSettings/PluginModal";
|
|||||||
import { Switch } from "@components/Switch";
|
import { Switch } from "@components/Switch";
|
||||||
import { ChangeList } from "@utils/ChangeList";
|
import { ChangeList } from "@utils/ChangeList";
|
||||||
import Logger from "@utils/Logger";
|
import Logger from "@utils/Logger";
|
||||||
import { Margins } from "@utils/margins";
|
|
||||||
import { classes, LazyComponent, useAwaiter } from "@utils/misc";
|
import { classes, LazyComponent, useAwaiter } from "@utils/misc";
|
||||||
import { openModalLazy } from "@utils/modal";
|
import { openModalLazy } from "@utils/modal";
|
||||||
import { Plugin } from "@utils/types";
|
import { Plugin } from "@utils/types";
|
||||||
import { findByCode, findByPropsLazy } from "@webpack";
|
import { findByCode, findByPropsLazy } from "@webpack";
|
||||||
import { Alerts, Button, Card, Forms, Parser, React, Select, Text, TextInput, Toasts, Tooltip } from "@webpack/common";
|
import { Alerts, Button, Card, Forms, Margins, Parser, React, Select, Text, TextInput, Toasts, Tooltip } from "@webpack/common";
|
||||||
|
|
||||||
import Plugins from "~plugins";
|
import Plugins from "~plugins";
|
||||||
|
|
||||||
@ -297,15 +296,15 @@ export default ErrorBoundary.wrap(function PluginSettings() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Forms.FormSection className={Margins.top16}>
|
<Forms.FormSection className={Margins.marginTop16}>
|
||||||
<ReloadRequiredCard required={changes.hasChanges} />
|
<ReloadRequiredCard required={changes.hasChanges} />
|
||||||
|
|
||||||
<Forms.FormTitle tag="h5" className={classes(Margins.top20, Margins.bottom8)}>
|
<Forms.FormTitle tag="h5" className={classes(Margins.marginTop20, Margins.marginBottom8)}>
|
||||||
Filters
|
Filters
|
||||||
</Forms.FormTitle>
|
</Forms.FormTitle>
|
||||||
|
|
||||||
<div className={cl("filter-controls")}>
|
<div className={cl("filter-controls")}>
|
||||||
<TextInput autoFocus value={searchValue.value} placeholder="Search for a plugin..." onChange={onSearch} className={Margins.bottom20} />
|
<TextInput autoFocus value={searchValue.value} placeholder="Search for a plugin..." onChange={onSearch} className={Margins.marginBottom20} />
|
||||||
<div className={InputStyles.inputWrapper}>
|
<div className={InputStyles.inputWrapper}>
|
||||||
<Select
|
<Select
|
||||||
className={InputStyles.inputDefault}
|
className={InputStyles.inputDefault}
|
||||||
@ -322,15 +321,15 @@ export default ErrorBoundary.wrap(function PluginSettings() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Forms.FormTitle className={Margins.top20}>Plugins</Forms.FormTitle>
|
<Forms.FormTitle className={Margins.marginTop20}>Plugins</Forms.FormTitle>
|
||||||
|
|
||||||
<div className={cl("grid")}>
|
<div className={cl("grid")}>
|
||||||
{plugins}
|
{plugins}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Forms.FormDivider className={Margins.top20} />
|
<Forms.FormDivider className={Margins.marginTop20} />
|
||||||
|
|
||||||
<Forms.FormTitle tag="h5" className={classes(Margins.top20, Margins.bottom8)}>
|
<Forms.FormTitle tag="h5" className={classes(Margins.marginTop20, Margins.marginBottom8)}>
|
||||||
Required Plugins
|
Required Plugins
|
||||||
</Forms.FormTitle>
|
</Forms.FormTitle>
|
||||||
<div className={cl("grid")}>
|
<div className={cl("grid")}>
|
||||||
|
@ -18,26 +18,25 @@
|
|||||||
|
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Flex } from "@components/Flex";
|
import { Flex } from "@components/Flex";
|
||||||
import { Margins } from "@utils/margins";
|
|
||||||
import { classes } from "@utils/misc";
|
import { classes } from "@utils/misc";
|
||||||
import { downloadSettingsBackup, uploadSettingsBackup } from "@utils/settingsSync";
|
import { downloadSettingsBackup, uploadSettingsBackup } from "@utils/settingsSync";
|
||||||
import { Button, Card, Forms, Text } from "@webpack/common";
|
import { Button, Card, Forms, Margins, Text } from "@webpack/common";
|
||||||
|
|
||||||
function BackupRestoreTab() {
|
function BackupRestoreTab() {
|
||||||
return (
|
return (
|
||||||
<Forms.FormSection title="Settings Sync" className={Margins.top16}>
|
<Forms.FormSection title="Settings Sync" className={Margins.marginTop16}>
|
||||||
<Card className={classes("vc-settings-card", "vc-backup-restore-card")}>
|
<Card className={classes("vc-settings-card", "vc-backup-restore-card")}>
|
||||||
<Flex flexDirection="column">
|
<Flex flexDirection="column">
|
||||||
<strong>Warning</strong>
|
<strong>Warning</strong>
|
||||||
<span>Importing a settings file will overwrite your current settings.</span>
|
<span>Importing a settings file will overwrite your current settings.</span>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Card>
|
</Card>
|
||||||
<Text variant="text-md/normal" className={Margins.bottom8}>
|
<Text variant="text-md/normal" className={Margins.marginBottom8}>
|
||||||
You can import and export your Vencord settings as a JSON file.
|
You can import and export your Vencord settings as a JSON file.
|
||||||
This allows you to easily transfer your settings to another device,
|
This allows you to easily transfer your settings to another device,
|
||||||
or recover your settings after reinstalling Vencord or Discord.
|
or recover your settings after reinstalling Vencord or Discord.
|
||||||
</Text>
|
</Text>
|
||||||
<Text variant="text-md/normal" className={Margins.bottom8}>
|
<Text variant="text-md/normal" className={Margins.marginBottom8}>
|
||||||
Settings Export contains:
|
Settings Export contains:
|
||||||
<ul>
|
<ul>
|
||||||
<li>— Custom QuickCSS</li>
|
<li>— Custom QuickCSS</li>
|
||||||
|
@ -19,10 +19,9 @@
|
|||||||
import { useSettings } from "@api/settings";
|
import { useSettings } from "@api/settings";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Link } from "@components/Link";
|
import { Link } from "@components/Link";
|
||||||
import { Margins } from "@utils/margins";
|
|
||||||
import { useAwaiter } from "@utils/misc";
|
import { useAwaiter } from "@utils/misc";
|
||||||
import { findLazy } from "@webpack";
|
import { findLazy } from "@webpack";
|
||||||
import { Card, Forms, React, TextArea } from "@webpack/common";
|
import { Card, Forms, Margins, React, TextArea } from "@webpack/common";
|
||||||
|
|
||||||
const TextAreaProps = findLazy(m => typeof m.textarea === "string");
|
const TextAreaProps = findLazy(m => typeof m.textarea === "string");
|
||||||
|
|
||||||
@ -52,7 +51,7 @@ function Validators({ themeLinks }: { themeLinks: string[]; }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Forms.FormTitle className={Margins.top20} tag="h5">Validator</Forms.FormTitle>
|
<Forms.FormTitle className={Margins.marginTop20} tag="h5">Validator</Forms.FormTitle>
|
||||||
<Forms.FormText>This section will tell you whether your themes can successfully be loaded</Forms.FormText>
|
<Forms.FormText>This section will tell you whether your themes can successfully be loaded</Forms.FormText>
|
||||||
<div>
|
<div>
|
||||||
{themeLinks.map(link => (
|
{themeLinks.map(link => (
|
||||||
@ -94,7 +93,7 @@ export default ErrorBoundary.wrap(function () {
|
|||||||
<Forms.FormTitle tag="h5">Paste links to .css / .theme.css files here</Forms.FormTitle>
|
<Forms.FormTitle tag="h5">Paste links to .css / .theme.css files here</Forms.FormTitle>
|
||||||
<Forms.FormText>One link per line</Forms.FormText>
|
<Forms.FormText>One link per line</Forms.FormText>
|
||||||
<Forms.FormText>Make sure to use the raw links or github.io links!</Forms.FormText>
|
<Forms.FormText>Make sure to use the raw links or github.io links!</Forms.FormText>
|
||||||
<Forms.FormDivider className={Margins.top8 + " " + Margins.bottom8} />
|
<Forms.FormDivider className={Margins.marginTop8 + " " + Margins.marginBottom8} />
|
||||||
<Forms.FormTitle tag="h5">Find Themes:</Forms.FormTitle>
|
<Forms.FormTitle tag="h5">Find Themes:</Forms.FormTitle>
|
||||||
<div style={{ marginBottom: ".5em" }}>
|
<div style={{ marginBottom: ".5em" }}>
|
||||||
<Link style={{ marginRight: ".5em" }} href="https://betterdiscord.app/themes">
|
<Link style={{ marginRight: ".5em" }} href="https://betterdiscord.app/themes">
|
||||||
|
@ -22,10 +22,9 @@ import { ErrorCard } from "@components/ErrorCard";
|
|||||||
import { Flex } from "@components/Flex";
|
import { Flex } from "@components/Flex";
|
||||||
import { handleComponentFailed } from "@components/handleComponentFailed";
|
import { handleComponentFailed } from "@components/handleComponentFailed";
|
||||||
import { Link } from "@components/Link";
|
import { Link } from "@components/Link";
|
||||||
import { Margins } from "@utils/margins";
|
|
||||||
import { classes, useAwaiter } from "@utils/misc";
|
import { classes, useAwaiter } from "@utils/misc";
|
||||||
import { changes, checkForUpdates, getRepo, isNewer, rebuild, update, updateError, UpdateLogger } from "@utils/updater";
|
import { changes, checkForUpdates, getRepo, isNewer, rebuild, update, updateError, UpdateLogger } from "@utils/updater";
|
||||||
import { Alerts, Button, Card, Forms, Parser, React, Switch, Toasts } from "@webpack/common";
|
import { Alerts, Button, Card, Forms, Margins, Parser, React, Switch, Toasts } from "@webpack/common";
|
||||||
|
|
||||||
import gitHash from "~git-hash";
|
import gitHash from "~git-hash";
|
||||||
|
|
||||||
@ -110,14 +109,14 @@ function Updatable(props: CommonProps) {
|
|||||||
</ErrorCard>
|
</ErrorCard>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<Forms.FormText className={Margins.bottom8}>
|
<Forms.FormText className={Margins.marginBottom8}>
|
||||||
{isOutdated ? `There are ${updates.length} Updates` : "Up to Date!"}
|
{isOutdated ? `There are ${updates.length} Updates` : "Up to Date!"}
|
||||||
</Forms.FormText>
|
</Forms.FormText>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isOutdated && <Changes updates={updates} {...props} />}
|
{isOutdated && <Changes updates={updates} {...props} />}
|
||||||
|
|
||||||
<Flex className={classes(Margins.bottom8, Margins.top8)}>
|
<Flex className={classes(Margins.marginBottom8, Margins.marginTop8)}>
|
||||||
{isOutdated && <Button
|
{isOutdated && <Button
|
||||||
size={Button.Sizes.SMALL}
|
size={Button.Sizes.SMALL}
|
||||||
disabled={isUpdating || isChecking}
|
disabled={isUpdating || isChecking}
|
||||||
@ -176,7 +175,7 @@ function Updatable(props: CommonProps) {
|
|||||||
function Newer(props: CommonProps) {
|
function Newer(props: CommonProps) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Forms.FormText className={Margins.bottom8}>
|
<Forms.FormText className={Margins.marginBottom8}>
|
||||||
Your local copy has more recent commits. Please stash or reset them.
|
Your local copy has more recent commits. Please stash or reset them.
|
||||||
</Forms.FormText>
|
</Forms.FormText>
|
||||||
<Changes {...props} updates={changes} />
|
<Changes {...props} updates={changes} />
|
||||||
@ -200,7 +199,7 @@ function Updater() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Forms.FormSection className={Margins.top16}>
|
<Forms.FormSection className={Margins.marginTop16}>
|
||||||
<Forms.FormTitle tag="h5">Updater Settings</Forms.FormTitle>
|
<Forms.FormTitle tag="h5">Updater Settings</Forms.FormTitle>
|
||||||
<Switch
|
<Switch
|
||||||
value={settings.notifyAboutUpdates}
|
value={settings.notifyAboutUpdates}
|
||||||
@ -226,7 +225,7 @@ function Updater() {
|
|||||||
</Link>
|
</Link>
|
||||||
)} (<HashLink hash={gitHash} repo={repo} disabled={repoPending} />)</Forms.FormText>
|
)} (<HashLink hash={gitHash} repo={repo} disabled={repoPending} />)</Forms.FormText>
|
||||||
|
|
||||||
<Forms.FormDivider className={Margins.top8 + " " + Margins.bottom8} />
|
<Forms.FormDivider className={Margins.marginTop8 + " " + Margins.marginBottom8} />
|
||||||
|
|
||||||
<Forms.FormTitle tag="h5">Updates</Forms.FormTitle>
|
<Forms.FormTitle tag="h5">Updates</Forms.FormTitle>
|
||||||
|
|
||||||
|
@ -63,15 +63,11 @@ function VencordSettings() {
|
|||||||
title: "Enable React Developer Tools",
|
title: "Enable React Developer Tools",
|
||||||
note: "Requires a full restart"
|
note: "Requires a full restart"
|
||||||
},
|
},
|
||||||
!IS_WEB && (!isWindows ? {
|
!IS_WEB && !isWindows && {
|
||||||
key: "frameless",
|
key: "frameless",
|
||||||
title: "Disable the window frame",
|
title: "Disable the window frame",
|
||||||
note: "Requires a full restart"
|
note: "Requires a full restart"
|
||||||
} : {
|
},
|
||||||
key: "winNativeTitleBar",
|
|
||||||
title: "Use Windows' native title bar instead of Discord's custom one",
|
|
||||||
note: "Requires a full restart"
|
|
||||||
}),
|
|
||||||
!IS_WEB && {
|
!IS_WEB && {
|
||||||
key: "transparent",
|
key: "transparent",
|
||||||
title: "Enable window transparency",
|
title: "Enable window transparency",
|
||||||
|
@ -20,7 +20,6 @@ import "./settingsStyles.css";
|
|||||||
|
|
||||||
import { classNameFactory } from "@api/Styles";
|
import { classNameFactory } from "@api/Styles";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { handleComponentFailed } from "@components/handleComponentFailed";
|
|
||||||
import { findByCodeLazy } from "@webpack";
|
import { findByCodeLazy } from "@webpack";
|
||||||
import { Forms, SettingsRouter, Text } from "@webpack/common";
|
import { Forms, SettingsRouter, Text } from "@webpack/common";
|
||||||
|
|
||||||
@ -62,8 +61,8 @@ function Settings(props: SettingsProps) {
|
|||||||
<Text variant="heading-md/normal" tag="h2">Vencord Settings</Text>
|
<Text variant="heading-md/normal" tag="h2">Vencord Settings</Text>
|
||||||
|
|
||||||
<TabBar
|
<TabBar
|
||||||
type="top"
|
type={TabBar.Types.TOP}
|
||||||
look="brand"
|
look={TabBar.Looks.BRAND}
|
||||||
className={cl("tab-bar")}
|
className={cl("tab-bar")}
|
||||||
selectedItem={tab}
|
selectedItem={tab}
|
||||||
onItemSelect={SettingsRouter.open}
|
onItemSelect={SettingsRouter.open}
|
||||||
@ -84,7 +83,7 @@ function Settings(props: SettingsProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function (props: SettingsProps) {
|
export default function (props: SettingsProps) {
|
||||||
return <ErrorBoundary onError={handleComponentFailed}>
|
return <ErrorBoundary>
|
||||||
<Settings tab={props.tab} />
|
<Settings tab={props.tab} />
|
||||||
</ErrorBoundary>;
|
</ErrorBoundary>;
|
||||||
}
|
}
|
||||||
|
@ -16,12 +16,29 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { maybePromptToUpdate } from "@utils/updater";
|
import { isOutdated, rebuild, update } from "@utils/updater";
|
||||||
|
|
||||||
export function handleComponentFailed() {
|
export async function handleComponentFailed() {
|
||||||
maybePromptToUpdate(
|
if (isOutdated) {
|
||||||
|
setImmediate(async () => {
|
||||||
|
const wantsUpdate = confirm(
|
||||||
"Uh Oh! Failed to render this Page." +
|
"Uh Oh! Failed to render this Page." +
|
||||||
" However, there is an update available that might fix it." +
|
" However, there is an update available that might fix it." +
|
||||||
" Would you like to update and restart now?"
|
" Would you like to update and restart now?"
|
||||||
);
|
);
|
||||||
|
if (wantsUpdate) {
|
||||||
|
try {
|
||||||
|
await update();
|
||||||
|
await rebuild();
|
||||||
|
if (IS_WEB)
|
||||||
|
location.reload();
|
||||||
|
else
|
||||||
|
DiscordNative.app.relaunch();
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
alert("That also failed :( Try updating or reinstalling with the installer!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -79,10 +79,7 @@ if (!process.argv.includes("--vanilla")) {
|
|||||||
options.webPreferences.sandbox = false;
|
options.webPreferences.sandbox = false;
|
||||||
if (settings.frameless) {
|
if (settings.frameless) {
|
||||||
options.frame = false;
|
options.frame = false;
|
||||||
} else if (process.platform === "win32" && settings.winNativeTitleBar) {
|
|
||||||
delete options.frame;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settings.transparent) {
|
if (settings.transparent) {
|
||||||
options.transparent = true;
|
options.transparent = true;
|
||||||
options.backgroundColor = "#00000000";
|
options.backgroundColor = "#00000000";
|
||||||
|
@ -24,10 +24,9 @@ import { Heart } from "@components/Heart";
|
|||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import IpcEvents from "@utils/IpcEvents";
|
import IpcEvents from "@utils/IpcEvents";
|
||||||
import Logger from "@utils/Logger";
|
import Logger from "@utils/Logger";
|
||||||
import { Margins } from "@utils/margins";
|
|
||||||
import { closeModal, Modals, openModal } from "@utils/modal";
|
import { closeModal, Modals, openModal } from "@utils/modal";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { Forms } from "@webpack/common";
|
import { Forms, Margins } from "@webpack/common";
|
||||||
|
|
||||||
const CONTRIBUTOR_BADGE = "https://media.discordapp.net/stickers/1026517526106087454.webp";
|
const CONTRIBUTOR_BADGE = "https://media.discordapp.net/stickers/1026517526106087454.webp";
|
||||||
|
|
||||||
@ -151,7 +150,7 @@ export default definePlugin({
|
|||||||
<Forms.FormText>
|
<Forms.FormText>
|
||||||
This Badge is a special perk for Vencord Donors
|
This Badge is a special perk for Vencord Donors
|
||||||
</Forms.FormText>
|
</Forms.FormText>
|
||||||
<Forms.FormText className={Margins.top20}>
|
<Forms.FormText className={Margins.marginTop20}>
|
||||||
Please consider supporting the development of Vencord by becoming a donor. It would mean a lot!!
|
Please consider supporting the development of Vencord by becoming a donor. It would mean a lot!!
|
||||||
</Forms.FormText>
|
</Forms.FormText>
|
||||||
</div>
|
</div>
|
||||||
|
@ -43,7 +43,7 @@ export default definePlugin({
|
|||||||
{
|
{
|
||||||
find: '"Menu API',
|
find: '"Menu API',
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /function.{0,80}type===(\i)\).{0,50}navigable:.+?Menu API/s,
|
match: /function.{0,80}type===(.{1,3})\..{1,3}\).{0,50}navigable:.+?Menu API/s,
|
||||||
replace: (m, mod) => {
|
replace: (m, mod) => {
|
||||||
let nicenNames = "";
|
let nicenNames = "";
|
||||||
const redefines = [] as string[];
|
const redefines = [] as string[];
|
||||||
|
@ -18,9 +18,6 @@
|
|||||||
|
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import * as Webpack from "@webpack";
|
|
||||||
import { extract, filters, findAll, search } from "@webpack";
|
|
||||||
import { React } from "@webpack/common";
|
|
||||||
|
|
||||||
const WEB_ONLY = (f: string) => () => {
|
const WEB_ONLY = (f: string) => () => {
|
||||||
throw new Error(`'${f}' is Discord Desktop only.`);
|
throw new Error(`'${f}' is Discord Desktop only.`);
|
||||||
@ -32,48 +29,19 @@ export default definePlugin({
|
|||||||
authors: [Devs.Ven],
|
authors: [Devs.Ven],
|
||||||
|
|
||||||
getShortcuts() {
|
getShortcuts() {
|
||||||
function newFindWrapper(filterFactory: (props: any) => Webpack.FilterFn) {
|
|
||||||
const cache = new Map<string, any>();
|
|
||||||
|
|
||||||
return function (filterProps: any) {
|
|
||||||
const cacheKey = String(filterProps);
|
|
||||||
if (cache.has(cacheKey)) return cache.get(cacheKey);
|
|
||||||
|
|
||||||
const matches = findAll(filterFactory(filterProps));
|
|
||||||
|
|
||||||
const result = (() => {
|
|
||||||
switch (matches.length) {
|
|
||||||
case 0: return null;
|
|
||||||
case 1: return matches[0];
|
|
||||||
default:
|
|
||||||
const uniqueMatches = [...new Set(matches)];
|
|
||||||
if (uniqueMatches.length > 1)
|
|
||||||
console.warn(`Warning: This filter matches ${matches.length} modules. Make it more specific!\n`, uniqueMatches);
|
|
||||||
|
|
||||||
return matches[0];
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
if (result && cacheKey) cache.set(cacheKey, result);
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
toClip: IS_WEB ? WEB_ONLY("toClip") : window.DiscordNative.clipboard.copy,
|
||||||
|
fromClip: IS_WEB ? WEB_ONLY("fromClip") : window.DiscordNative.clipboard.read,
|
||||||
wp: Vencord.Webpack,
|
wp: Vencord.Webpack,
|
||||||
wpc: Webpack.wreq.c,
|
wpc: Vencord.Webpack.wreq.c,
|
||||||
wreq: Webpack.wreq,
|
wreq: Vencord.Webpack.wreq,
|
||||||
wpsearch: search,
|
wpsearch: Vencord.Webpack.search,
|
||||||
wpex: extract,
|
wpex: Vencord.Webpack.extract,
|
||||||
wpexs: (code: string) => Vencord.Webpack.extract(Vencord.Webpack.findModuleId(code)!),
|
wpexs: (code: string) => Vencord.Webpack.extract(Vencord.Webpack.findModuleId(code)!),
|
||||||
find: newFindWrapper(f => f),
|
findByProps: Vencord.Webpack.findByProps,
|
||||||
findAll,
|
find: Vencord.Webpack.find,
|
||||||
findByProps: newFindWrapper(filters.byProps),
|
Plugins: Vencord.Plugins,
|
||||||
findAllByProps: (...props: string[]) => findAll(filters.byProps(...props)),
|
React: Vencord.Webpack.Common.React,
|
||||||
findByCode: newFindWrapper(filters.byCode),
|
|
||||||
findAllByCode: (code: string) => findAll(filters.byCode(code)),
|
|
||||||
PluginsApi: Vencord.Plugins,
|
|
||||||
plugins: Vencord.Plugins.plugins,
|
|
||||||
React,
|
|
||||||
Settings: Vencord.Settings,
|
Settings: Vencord.Settings,
|
||||||
Api: Vencord.Api,
|
Api: Vencord.Api,
|
||||||
reload: () => location.reload(),
|
reload: () => location.reload(),
|
||||||
|
@ -1,134 +0,0 @@
|
|||||||
/*
|
|
||||||
* Vencord, a modification for Discord's desktop app
|
|
||||||
* Copyright (c) 2022 Vendicated and contributors
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { showNotification } from "@api/Notifications";
|
|
||||||
import { definePluginSettings } from "@api/settings";
|
|
||||||
import { Devs } from "@utils/constants";
|
|
||||||
import Logger from "@utils/Logger";
|
|
||||||
import { closeAllModals } from "@utils/modal";
|
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
|
||||||
import { maybePromptToUpdate } from "@utils/updater";
|
|
||||||
import { FluxDispatcher, NavigationRouter } from "@webpack/common";
|
|
||||||
import type { ReactElement } from "react";
|
|
||||||
|
|
||||||
const CrashHandlerLogger = new Logger("CrashHandler");
|
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
|
||||||
attemptToPreventCrashes: {
|
|
||||||
type: OptionType.BOOLEAN,
|
|
||||||
description: "Whether to attempt to prevent Discord crashes.",
|
|
||||||
default: true
|
|
||||||
},
|
|
||||||
attemptToNavigateToHome: {
|
|
||||||
type: OptionType.BOOLEAN,
|
|
||||||
description: "Whether to attempt to navigate to the home when preventing Discord crashes.",
|
|
||||||
default: false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default definePlugin({
|
|
||||||
name: "CrashHandler",
|
|
||||||
description: "Utility plugin for handling and possibly recovering from Crashes without a restart",
|
|
||||||
authors: [Devs.Nuckyz],
|
|
||||||
enabledByDefault: true,
|
|
||||||
|
|
||||||
popAllModals: undefined as (() => void) | undefined,
|
|
||||||
|
|
||||||
settings,
|
|
||||||
|
|
||||||
patches: [
|
|
||||||
{
|
|
||||||
find: ".Messages.ERRORS_UNEXPECTED_CRASH",
|
|
||||||
replacement: {
|
|
||||||
match: /(?=this\.setState\()/,
|
|
||||||
replace: "$self.handleCrash(this)||"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
find: 'dispatch({type:"MODAL_POP_ALL"})',
|
|
||||||
replacement: {
|
|
||||||
match: /(?<=(?<popAll>\i)=function\(\){\(0,\i\.\i\)\(\);\i\.\i\.dispatch\({type:"MODAL_POP_ALL"}\).+};)/,
|
|
||||||
replace: "$self.popAllModals=$<popAll>;"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
handleCrash(_this: ReactElement & { forceUpdate: () => void; }) {
|
|
||||||
try {
|
|
||||||
maybePromptToUpdate("Uh oh, Discord has just crashed... but good news, there is a Vencord update available that might fix this issue! Would you like to update now?", true);
|
|
||||||
|
|
||||||
if (settings.store.attemptToPreventCrashes) {
|
|
||||||
this.handlePreventCrash(_this);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
} catch (err) {
|
|
||||||
CrashHandlerLogger.error("Failed to handle crash", err);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
handlePreventCrash(_this: ReactElement & { forceUpdate: () => void; }) {
|
|
||||||
try {
|
|
||||||
showNotification({
|
|
||||||
color: "#eed202",
|
|
||||||
title: "Discord has crashed!",
|
|
||||||
body: "Attempting to recover...",
|
|
||||||
});
|
|
||||||
} catch { }
|
|
||||||
|
|
||||||
try {
|
|
||||||
FluxDispatcher.dispatch({ type: "CONTEXT_MENU_CLOSE" });
|
|
||||||
} catch (err) {
|
|
||||||
CrashHandlerLogger.debug("Failed to close open context menu.", err);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
this.popAllModals?.();
|
|
||||||
} catch (err) {
|
|
||||||
CrashHandlerLogger.debug("Failed to close old modals.", err);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
closeAllModals();
|
|
||||||
} catch (err) {
|
|
||||||
CrashHandlerLogger.debug("Failed to close all open modals.", err);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
FluxDispatcher.dispatch({ type: "USER_PROFILE_MODAL_CLOSE" });
|
|
||||||
} catch (err) {
|
|
||||||
CrashHandlerLogger.debug("Failed to close user popout.", err);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
FluxDispatcher.dispatch({ type: "LAYER_POP_ALL" });
|
|
||||||
} catch (err) {
|
|
||||||
CrashHandlerLogger.debug("Failed to pop all layers.", err);
|
|
||||||
}
|
|
||||||
if (settings.store.attemptToNavigateToHome) {
|
|
||||||
try {
|
|
||||||
NavigationRouter.transitionTo("/channels/@me");
|
|
||||||
} catch (err) {
|
|
||||||
CrashHandlerLogger.debug("Failed to navigate to home", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
_this.forceUpdate();
|
|
||||||
} catch (err) {
|
|
||||||
CrashHandlerLogger.debug("Failed to update crash handler component.", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
@ -19,7 +19,6 @@
|
|||||||
import { definePluginSettings } from "@api/settings";
|
import { definePluginSettings } from "@api/settings";
|
||||||
import { Link } from "@components/Link";
|
import { Link } from "@components/Link";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { isTruthy } from "@utils/guards";
|
|
||||||
import { useAwaiter } from "@utils/misc";
|
import { useAwaiter } from "@utils/misc";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { filters, findByCodeLazy, findByPropsLazy, mapMangledModuleLazy } from "@webpack";
|
import { filters, findByCodeLazy, findByPropsLazy, mapMangledModuleLazy } from "@webpack";
|
||||||
@ -57,11 +56,11 @@ interface ActivityAssets {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface Activity {
|
interface Activity {
|
||||||
state?: string;
|
state: string;
|
||||||
details?: string;
|
details?: string;
|
||||||
timestamps?: {
|
timestamps?: {
|
||||||
start?: number;
|
start?: Number;
|
||||||
end?: number;
|
end?: Number;
|
||||||
};
|
};
|
||||||
assets?: ActivityAssets;
|
assets?: ActivityAssets;
|
||||||
buttons?: Array<string>;
|
buttons?: Array<string>;
|
||||||
@ -71,7 +70,7 @@ interface Activity {
|
|||||||
button_urls?: Array<string>;
|
button_urls?: Array<string>;
|
||||||
};
|
};
|
||||||
type: ActivityType;
|
type: ActivityType;
|
||||||
flags: number;
|
flags: Number;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ActivityType {
|
enum ActivityType {
|
||||||
@ -94,13 +93,13 @@ const numOpt = (description: string) => ({
|
|||||||
onChange: setRpc
|
onChange: setRpc
|
||||||
}) as const;
|
}) as const;
|
||||||
|
|
||||||
const choice = (label: string, value: any, _default?: boolean) => ({
|
const choice = (label: string, value: any, _default?: Boolean) => ({
|
||||||
label,
|
label,
|
||||||
value,
|
value,
|
||||||
default: _default
|
default: _default
|
||||||
}) as const;
|
}) as const;
|
||||||
|
|
||||||
const choiceOpt = <T,>(description: string, options: T) => ({
|
const choiceOpt = (description: string, options) => ({
|
||||||
type: OptionType.SELECT,
|
type: OptionType.SELECT,
|
||||||
description,
|
description,
|
||||||
onChange: setRpc,
|
onChange: setRpc,
|
||||||
@ -174,13 +173,13 @@ async function createActivity(): Promise<Activity | undefined> {
|
|||||||
activity.buttons = [
|
activity.buttons = [
|
||||||
buttonOneText,
|
buttonOneText,
|
||||||
buttonTwoText
|
buttonTwoText
|
||||||
].filter(isTruthy);
|
].filter(Boolean);
|
||||||
|
|
||||||
activity.metadata = {
|
activity.metadata = {
|
||||||
button_urls: [
|
button_urls: [
|
||||||
buttonOneURL,
|
buttonOneURL,
|
||||||
buttonTwoURL
|
buttonTwoURL
|
||||||
].filter(isTruthy)
|
].filter(Boolean)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,10 +206,12 @@ async function createActivity(): Promise<Activity | undefined> {
|
|||||||
delete activity[k];
|
delete activity[k];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WHAT DO YOU WANT FROM ME
|
||||||
|
// eslint-disable-next-line consistent-return
|
||||||
return activity;
|
return activity;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function setRpc(disable?: boolean) {
|
async function setRpc(disable?: Boolean) {
|
||||||
const activity: Activity | undefined = await createActivity();
|
const activity: Activity | undefined = await createActivity();
|
||||||
|
|
||||||
FluxDispatcher.dispatch({
|
FluxDispatcher.dispatch({
|
||||||
|
@ -20,12 +20,11 @@ import { migratePluginSettings, Settings } from "@api/settings";
|
|||||||
import { CheckedTextInput } from "@components/CheckedTextInput";
|
import { CheckedTextInput } from "@components/CheckedTextInput";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import Logger from "@utils/Logger";
|
import Logger from "@utils/Logger";
|
||||||
import { Margins } from "@utils/margins";
|
|
||||||
import { makeLazy } from "@utils/misc";
|
import { makeLazy } from "@utils/misc";
|
||||||
import { ModalContent, ModalHeader, ModalRoot, openModal } from "@utils/modal";
|
import { ModalContent, ModalHeader, ModalRoot, openModal } from "@utils/modal";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { findByCodeLazy, findByPropsLazy } from "@webpack";
|
import { findByCodeLazy, findByPropsLazy } from "@webpack";
|
||||||
import { Forms, GuildStore, Menu, PermissionStore, React, Toasts, Tooltip, UserStore } from "@webpack/common";
|
import { Forms, GuildStore, Margins, Menu, PermissionStore, React, Toasts, Tooltip, UserStore } from "@webpack/common";
|
||||||
|
|
||||||
const MANAGE_EMOJIS_AND_STICKERS = 1n << 30n;
|
const MANAGE_EMOJIS_AND_STICKERS = 1n << 30n;
|
||||||
|
|
||||||
@ -97,7 +96,7 @@ function CloneModal({ id, name: emojiName, isAnimated }: { id: string; name: str
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Forms.FormTitle className={Margins.top20}>Custom Name</Forms.FormTitle>
|
<Forms.FormTitle className={Margins.marginTop20}>Custom Name</Forms.FormTitle>
|
||||||
<CheckedTextInput
|
<CheckedTextInput
|
||||||
value={name}
|
value={name}
|
||||||
onChange={setName}
|
onChange={setName}
|
||||||
|
@ -34,7 +34,7 @@ interface Activity {
|
|||||||
state: string;
|
state: string;
|
||||||
details?: string;
|
details?: string;
|
||||||
timestamps?: {
|
timestamps?: {
|
||||||
start?: number;
|
start?: Number;
|
||||||
};
|
};
|
||||||
assets?: ActivityAssets;
|
assets?: ActivityAssets;
|
||||||
buttons?: Array<string>;
|
buttons?: Array<string>;
|
||||||
@ -43,8 +43,8 @@ interface Activity {
|
|||||||
metadata?: {
|
metadata?: {
|
||||||
button_urls?: Array<string>;
|
button_urls?: Array<string>;
|
||||||
};
|
};
|
||||||
type: number;
|
type: Number;
|
||||||
flags: number;
|
flags: Number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TrackData {
|
interface TrackData {
|
||||||
|
@ -24,7 +24,7 @@ migratePluginSettings("NoDevtoolsWarning", "STFU");
|
|||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "NoDevtoolsWarning",
|
name: "NoDevtoolsWarning",
|
||||||
description: "Disables the 'HOLD UP' banner in the console. As a side effect, also prevents Discord from hiding your token, which prevents random logouts.",
|
description: "Disables the 'HOLD UP' banner in the console",
|
||||||
authors: [Devs.Ven],
|
authors: [Devs.Ven],
|
||||||
patches: [{
|
patches: [{
|
||||||
find: "setDevtoolsCallbacks",
|
find: "setDevtoolsCallbacks",
|
||||||
|
@ -16,13 +16,11 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { migratePluginSettings } from "@api/settings";
|
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
|
|
||||||
migratePluginSettings("NoF1", "No F1");
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "NoF1",
|
name: "No F1",
|
||||||
description: "Disables F1 help bind.",
|
description: "Disables F1 help bind.",
|
||||||
authors: [Devs.Cyn],
|
authors: [Devs.Cyn],
|
||||||
patches: [
|
patches: [
|
||||||
|
@ -16,13 +16,11 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { migratePluginSettings } from "@api/settings";
|
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
|
|
||||||
migratePluginSettings("NoRPC", "No RPC");
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "NoRPC",
|
name: "No RPC",
|
||||||
description: "Disables Discord's RPC server.",
|
description: "Disables Discord's RPC server.",
|
||||||
authors: [Devs.Cyn],
|
authors: [Devs.Cyn],
|
||||||
target: "DESKTOP",
|
target: "DESKTOP",
|
||||||
|
250
src/plugins/relationshipNotifier.ts
Normal file
250
src/plugins/relationshipNotifier.ts
Normal file
@ -0,0 +1,250 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2023 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { showNotification } from "@api/Notifications";
|
||||||
|
import { definePluginSettings } from "@api/settings";
|
||||||
|
import { Devs } from "@utils/constants";
|
||||||
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
|
import { FluxDispatcher, UserUtils } from "@webpack/common";
|
||||||
|
import { User } from "discord-types/general";
|
||||||
|
|
||||||
|
enum RelationshipType {
|
||||||
|
NONE = 0,
|
||||||
|
FRIEND = 1,
|
||||||
|
BLOCKED = 2,
|
||||||
|
PENDING_INCOMING = 3,
|
||||||
|
PENDING_OUTGOING = 4,
|
||||||
|
IMPLICIT = 5
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RelationshipPayload {
|
||||||
|
type: "RELATIONSHIP_ADD" | "RELATIONSHIP_REMOVE" | "RELATIONSHIP_UPDATE";
|
||||||
|
relationship: {
|
||||||
|
id: string;
|
||||||
|
type: RelationshipType;
|
||||||
|
since?: Date;
|
||||||
|
nickname?: string;
|
||||||
|
user?: User;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const settings = definePluginSettings({
|
||||||
|
friend: {
|
||||||
|
type: OptionType.SELECT,
|
||||||
|
description: "Show a notification when a friend is added or removed",
|
||||||
|
options: [{
|
||||||
|
label: "Friend added and removed",
|
||||||
|
value: "ALL",
|
||||||
|
default: true,
|
||||||
|
}, {
|
||||||
|
label: "Only when added",
|
||||||
|
value: "CREATE",
|
||||||
|
}, {
|
||||||
|
label: "Only when removed",
|
||||||
|
value: "REMOVE",
|
||||||
|
}, {
|
||||||
|
label: "No notifications",
|
||||||
|
value: "NONE",
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
outgoingRequest: {
|
||||||
|
type: OptionType.SELECT,
|
||||||
|
description: "Show a notification when you send or cancel a friend request",
|
||||||
|
options: [{
|
||||||
|
label: "Request sent and cancelled",
|
||||||
|
value: "ALL",
|
||||||
|
default: true,
|
||||||
|
}, {
|
||||||
|
label: "Only when sent",
|
||||||
|
value: "CREATE",
|
||||||
|
}, {
|
||||||
|
label: "Only when cancelled",
|
||||||
|
value: "REMOVE",
|
||||||
|
}, {
|
||||||
|
label: "No notifications",
|
||||||
|
value: "NONE",
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
incomingRequest: {
|
||||||
|
type: OptionType.SELECT,
|
||||||
|
description: "Show a notification when an incoming request is received or cancelled",
|
||||||
|
options: [{
|
||||||
|
label: "Request received and cancelled",
|
||||||
|
value: "ALL",
|
||||||
|
default: true,
|
||||||
|
}, {
|
||||||
|
label: "Only when received",
|
||||||
|
value: "CREATE",
|
||||||
|
}, {
|
||||||
|
label: "Only when cancelled",
|
||||||
|
value: "REMOVE",
|
||||||
|
}, {
|
||||||
|
label: "No notifications",
|
||||||
|
value: "NONE",
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
block: {
|
||||||
|
type: OptionType.SELECT,
|
||||||
|
description: "Show a notification when you block or unblock a user",
|
||||||
|
options: [{
|
||||||
|
label: "Blocking and unblocking",
|
||||||
|
value: "ALL",
|
||||||
|
default: true,
|
||||||
|
}, {
|
||||||
|
label: "Only when blocking",
|
||||||
|
value: "CREATE",
|
||||||
|
}, {
|
||||||
|
label: "Only when unblocking",
|
||||||
|
value: "REMOVE",
|
||||||
|
}, {
|
||||||
|
label: "No notifications",
|
||||||
|
value: "NONE",
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "RelationshipNotifier",
|
||||||
|
authors: [Devs.Megu],
|
||||||
|
description: "Receive notifications for friend requests, removals, blocks, etc.",
|
||||||
|
settings,
|
||||||
|
|
||||||
|
start() {
|
||||||
|
FluxDispatcher.subscribe("RELATIONSHIP_ADD", onRelationshipUpdate);
|
||||||
|
FluxDispatcher.subscribe("RELATIONSHIP_UPDATE", onRelationshipUpdate);
|
||||||
|
FluxDispatcher.subscribe("RELATIONSHIP_REMOVE", onRelationshipRemove);
|
||||||
|
},
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
FluxDispatcher.unsubscribe("RELATIONSHIP_ADD", onRelationshipUpdate);
|
||||||
|
FluxDispatcher.unsubscribe("RELATIONSHIP_UPDATE", onRelationshipUpdate);
|
||||||
|
FluxDispatcher.unsubscribe("RELATIONSHIP_REMOVE", onRelationshipRemove);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
async function onRelationshipUpdate({ relationship }: RelationshipPayload) {
|
||||||
|
if (!relationship.id) return;
|
||||||
|
const user = await UserUtils.fetchUser(relationship.id);
|
||||||
|
if (!user) return;
|
||||||
|
|
||||||
|
function onClick() {
|
||||||
|
FluxDispatcher.dispatch({
|
||||||
|
type: "USER_PROFILE_MODAL_OPEN",
|
||||||
|
userId: user.id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (relationship.type) {
|
||||||
|
case RelationshipType.FRIEND: {
|
||||||
|
if (!["ALL", "CREATE"].includes(settings.store.friend)) break;
|
||||||
|
showNotification({
|
||||||
|
title: "Friend Added",
|
||||||
|
body: `${user.username} is now your friend.`,
|
||||||
|
icon: user.getAvatarURL(),
|
||||||
|
onClick,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case RelationshipType.PENDING_INCOMING: {
|
||||||
|
if (!["ALL", "CREATE"].includes(settings.store.incomingRequest)) break;
|
||||||
|
showNotification({
|
||||||
|
title: "Friend Request Received",
|
||||||
|
body: `${user.username} sent you a friend request.`,
|
||||||
|
icon: user.getAvatarURL(),
|
||||||
|
onClick,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case RelationshipType.PENDING_OUTGOING: {
|
||||||
|
if (!["ALL", "CREATE"].includes(settings.store.outgoingRequest)) break;
|
||||||
|
showNotification({
|
||||||
|
title: "Friend Request Sent",
|
||||||
|
body: `You sent a friend request to ${user.username}`,
|
||||||
|
icon: user.getAvatarURL(),
|
||||||
|
onClick
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case RelationshipType.BLOCKED: {
|
||||||
|
if (!["ALL", "CREATE"].includes(settings.store.block)) break;
|
||||||
|
showNotification({
|
||||||
|
title: "User Blocked",
|
||||||
|
body: `You just blocked ${user.username}`,
|
||||||
|
icon: user.getAvatarURL(),
|
||||||
|
onClick
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onRelationshipRemove({ relationship }: RelationshipPayload) {
|
||||||
|
if (!relationship.id) return;
|
||||||
|
const user = await UserUtils.fetchUser(relationship.id);
|
||||||
|
if (!user) return;
|
||||||
|
|
||||||
|
function onClick() {
|
||||||
|
FluxDispatcher.dispatch({
|
||||||
|
type: "USER_PROFILE_MODAL_OPEN",
|
||||||
|
userId: user.id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (relationship.type) {
|
||||||
|
case RelationshipType.FRIEND: {
|
||||||
|
if (!["ALL", "REMOVE"].includes(settings.store.friend)) break;
|
||||||
|
showNotification({
|
||||||
|
title: "Friend Removed",
|
||||||
|
body: `${user.username} is no longer on your friends list.`,
|
||||||
|
icon: user.getAvatarURL(),
|
||||||
|
onClick,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case RelationshipType.PENDING_INCOMING: {
|
||||||
|
if (!["ALL", "REMOVE"].includes(settings.store.incomingRequest)) break;
|
||||||
|
showNotification({
|
||||||
|
title: "Friend Request Cancelled",
|
||||||
|
body: `${user.username} cancelled their friend request.`,
|
||||||
|
icon: user.getAvatarURL(),
|
||||||
|
onClick,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case RelationshipType.PENDING_OUTGOING: {
|
||||||
|
if (!["ALL", "REMOVE"].includes(settings.store.outgoingRequest)) break;
|
||||||
|
showNotification({
|
||||||
|
title: "Friend Request Cancelled",
|
||||||
|
body: `You cancelled your friend request to ${user.username}`,
|
||||||
|
icon: user.getAvatarURL(),
|
||||||
|
onClick
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case RelationshipType.BLOCKED: {
|
||||||
|
if (!["ALL", "REMOVE"].includes(settings.store.block)) break;
|
||||||
|
showNotification({
|
||||||
|
title: "User Unblocked",
|
||||||
|
body: `You just unblocked ${user.username}`,
|
||||||
|
icon: user.getAvatarURL(),
|
||||||
|
onClick
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -30,10 +30,10 @@ export default definePlugin({
|
|||||||
|
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: ".removeObscurity=function",
|
find: ".revealSpoiler=function",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /\.removeObscurity=function\((\i)\){/,
|
match: /\.revealSpoiler=function\((.{1,2})\){/,
|
||||||
replace: ".removeObscurity=function($1){$self.reveal($1);"
|
replace: ".revealSpoiler=function($1){$self.reveal($1);"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -1,91 +0,0 @@
|
|||||||
/*
|
|
||||||
* Vencord, a modification for Discord's desktop app
|
|
||||||
* Copyright (c) 2023 Vendicated and contributors
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { DataStore } from "@api/index";
|
|
||||||
import { Devs, SUPPORT_CHANNEL_ID } from "@utils/constants";
|
|
||||||
import { makeCodeblock } from "@utils/misc";
|
|
||||||
import definePlugin from "@utils/types";
|
|
||||||
import { isOutdated } from "@utils/updater";
|
|
||||||
import { Alerts, FluxDispatcher, Forms, UserStore } from "@webpack/common";
|
|
||||||
|
|
||||||
import gitHash from "~git-hash";
|
|
||||||
import plugins from "~plugins";
|
|
||||||
|
|
||||||
import settings from "./settings";
|
|
||||||
|
|
||||||
const REMEMBER_DISMISS_KEY = "Vencord-SupportHelper-Dismiss";
|
|
||||||
|
|
||||||
export default definePlugin({
|
|
||||||
name: "SupportHelper",
|
|
||||||
required: true,
|
|
||||||
description: "Helps me provide support to you",
|
|
||||||
authors: [Devs.Ven],
|
|
||||||
|
|
||||||
commands: [{
|
|
||||||
name: "vencord-debug",
|
|
||||||
description: "Send Vencord Debug info",
|
|
||||||
predicate: ctx => ctx.channel.id === SUPPORT_CHANNEL_ID,
|
|
||||||
execute() {
|
|
||||||
const { RELEASE_CHANNEL } = window.GLOBAL_ENV;
|
|
||||||
|
|
||||||
const debugInfo = `
|
|
||||||
**Vencord Debug Info**
|
|
||||||
|
|
||||||
> Discord Branch: ${RELEASE_CHANNEL}
|
|
||||||
> Client: ${typeof DiscordNative === "undefined" ? window.armcord ? "Armcord" : `Web (${navigator.userAgent})` : `Desktop (Electron v${settings.electronVersion})`}
|
|
||||||
> Platform: ${window.navigator.platform}
|
|
||||||
> Vencord Version: ${gitHash}${settings.additionalInfo}
|
|
||||||
> Outdated: ${isOutdated}
|
|
||||||
> Enabled Plugins:
|
|
||||||
${makeCodeblock(Object.keys(plugins).filter(Vencord.Plugins.isPluginEnabled).join(", "))}
|
|
||||||
`;
|
|
||||||
|
|
||||||
return {
|
|
||||||
content: debugInfo.trim()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}],
|
|
||||||
|
|
||||||
rememberDismiss() {
|
|
||||||
DataStore.set(REMEMBER_DISMISS_KEY, gitHash);
|
|
||||||
},
|
|
||||||
|
|
||||||
start() {
|
|
||||||
FluxDispatcher.subscribe("CHANNEL_SELECT", async ({ channelId }) => {
|
|
||||||
if (channelId !== SUPPORT_CHANNEL_ID) return;
|
|
||||||
|
|
||||||
const myId = BigInt(UserStore.getCurrentUser().id);
|
|
||||||
if (Object.values(Devs).some(d => d.id === myId)) return;
|
|
||||||
|
|
||||||
if (isOutdated && gitHash !== await DataStore.get(REMEMBER_DISMISS_KEY)) {
|
|
||||||
Alerts.show({
|
|
||||||
title: "Hold on!",
|
|
||||||
body: <div>
|
|
||||||
<Forms.FormText>You are using an outdated version of Vencord! Chances are, your issue is already fixed.</Forms.FormText>
|
|
||||||
<Forms.FormText>
|
|
||||||
Please first update using the Updater Page in Settings, or use the VencordInstaller (Update Vencord Button)
|
|
||||||
to do so, in case you can't access the Updater page.
|
|
||||||
</Forms.FormText>
|
|
||||||
</div>,
|
|
||||||
onCancel: this.rememberDismiss,
|
|
||||||
onConfirm: this.rememberDismiss
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
@ -24,7 +24,7 @@ import { findByCodeLazy } from "@webpack";
|
|||||||
import { GuildMemberStore, React, RelationshipStore } from "@webpack/common";
|
import { GuildMemberStore, React, RelationshipStore } from "@webpack/common";
|
||||||
import { User } from "discord-types/general";
|
import { User } from "discord-types/general";
|
||||||
|
|
||||||
const Avatar = findByCodeLazy('"top",spacing:');
|
const Avatar = findByCodeLazy(".Positions.TOP,spacing:");
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
const settings = definePluginSettings({
|
||||||
showAvatars: {
|
showAvatars: {
|
||||||
@ -105,7 +105,7 @@ export default definePlugin({
|
|||||||
}}>
|
}}>
|
||||||
{settings.store.showAvatars && <div style={{ marginTop: "4px" }}>
|
{settings.store.showAvatars && <div style={{ marginTop: "4px" }}>
|
||||||
<Avatar
|
<Avatar
|
||||||
size="SIZE_16"
|
size={Avatar.Sizes.SIZE_16}
|
||||||
src={user.getAvatarURL(guildId, 128)} />
|
src={user.getAvatarURL(guildId, 128)} />
|
||||||
</div>}
|
</div>}
|
||||||
{GuildMemberStore.getNick(guildId!, user.id) || !guildId && RelationshipStore.getNickname(user.id) || user.username}
|
{GuildMemberStore.getNick(guildId!, user.id) || !guildId && RelationshipStore.getNickname(user.id) || user.username}
|
||||||
|
@ -48,7 +48,7 @@ export default definePlugin({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
// channel mentions
|
// channel mentions
|
||||||
find: ".shouldCloseDefaultModals",
|
find: ".EMOJI_IN_MESSAGE_HOVER",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /onClick:(\i)(?=,.{0,30}className:"channelMention")/,
|
match: /onClick:(\i)(?=,.{0,30}className:"channelMention")/,
|
||||||
replace: "onClick:(_vcEv)=>(_vcEv.detail>=2||_vcEv.target.className.includes('MentionText'))&&($1)()",
|
replace: "onClick:(_vcEv)=>(_vcEv.detail>=2||_vcEv.target.className.includes('MentionText'))&&($1)()",
|
||||||
|
@ -20,11 +20,10 @@ import { Settings } from "@api/settings";
|
|||||||
import { ErrorCard } from "@components/ErrorCard";
|
import { ErrorCard } from "@components/ErrorCard";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import Logger from "@utils/Logger";
|
import Logger from "@utils/Logger";
|
||||||
import { Margins } from "@utils/margins";
|
|
||||||
import { wordsToTitle } from "@utils/text";
|
import { wordsToTitle } from "@utils/text";
|
||||||
import definePlugin, { OptionType, PluginOptionsItem } from "@utils/types";
|
import definePlugin, { OptionType, PluginOptionsItem } from "@utils/types";
|
||||||
import { findByPropsLazy } from "@webpack";
|
import { findByPropsLazy } from "@webpack";
|
||||||
import { Button, ChannelStore, FluxDispatcher, Forms, SelectedChannelStore, useMemo, UserStore } from "@webpack/common";
|
import { Button, ChannelStore, FluxDispatcher, Forms, Margins, SelectedChannelStore, useMemo, UserStore } from "@webpack/common";
|
||||||
|
|
||||||
interface VoiceState {
|
interface VoiceState {
|
||||||
userId: string;
|
userId: string;
|
||||||
@ -305,7 +304,7 @@ export default definePlugin({
|
|||||||
</Forms.FormText>
|
</Forms.FormText>
|
||||||
{hasEnglishVoices && (
|
{hasEnglishVoices && (
|
||||||
<>
|
<>
|
||||||
<Forms.FormTitle className={Margins.top20} tag="h3">Play Example Sounds</Forms.FormTitle>
|
<Forms.FormTitle className={Margins.marginTop20} tag="h3">Play Example Sounds</Forms.FormTitle>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "grid",
|
display: "grid",
|
||||||
|
@ -20,11 +20,10 @@ import { addButton, removeButton } from "@api/MessagePopover";
|
|||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Flex } from "@components/Flex";
|
import { Flex } from "@components/Flex";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { Margins } from "@utils/margins";
|
|
||||||
import { copyWithToast } from "@utils/misc";
|
import { copyWithToast } from "@utils/misc";
|
||||||
import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { Button, ChannelStore, Forms, Parser, Text } from "@webpack/common";
|
import { Button, ChannelStore, Forms, Margins, Parser, Text } from "@webpack/common";
|
||||||
import { Message } from "discord-types/general";
|
import { Message } from "discord-types/general";
|
||||||
|
|
||||||
|
|
||||||
@ -99,7 +98,7 @@ function openViewRawModal(msg: Message) {
|
|||||||
<>
|
<>
|
||||||
<Forms.FormTitle tag="h5">Content</Forms.FormTitle>
|
<Forms.FormTitle tag="h5">Content</Forms.FormTitle>
|
||||||
<CodeBlock content={msg.content} lang="" />
|
<CodeBlock content={msg.content} lang="" />
|
||||||
<Forms.FormDivider className={Margins.bottom20} />
|
<Forms.FormDivider className={Margins.marginBottom20} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
@ -22,7 +22,6 @@ import gitRemote from "~git-remote";
|
|||||||
export const WEBPACK_CHUNK = "webpackChunkdiscord_app";
|
export const WEBPACK_CHUNK = "webpackChunkdiscord_app";
|
||||||
export const REACT_GLOBAL = "Vencord.Webpack.Common.React";
|
export const REACT_GLOBAL = "Vencord.Webpack.Common.React";
|
||||||
export const VENCORD_USER_AGENT = `Vencord/${gitHash}${gitRemote ? ` (https://github.com/${gitRemote})` : ""}`;
|
export const VENCORD_USER_AGENT = `Vencord/${gitHash}${gitRemote ? ` (https://github.com/${gitRemote})` : ""}`;
|
||||||
export const SUPPORT_CHANNEL_ID = "1026515880080842772";
|
|
||||||
|
|
||||||
// Add yourself here if you made a plugin
|
// Add yourself here if you made a plugin
|
||||||
export const Devs = /* #__PURE__*/ Object.freeze({
|
export const Devs = /* #__PURE__*/ Object.freeze({
|
||||||
@ -201,9 +200,5 @@ export const Devs = /* #__PURE__*/ Object.freeze({
|
|||||||
lewisakura: {
|
lewisakura: {
|
||||||
name: "lewisakura",
|
name: "lewisakura",
|
||||||
id: 96269247411400704n
|
id: 96269247411400704n
|
||||||
},
|
|
||||||
cloudburst: {
|
|
||||||
name: "cloudburst",
|
|
||||||
id: 892128204150685769n
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
/*
|
|
||||||
* Vencord, a modification for Discord's desktop app
|
|
||||||
* Copyright (c) 2023 Vendicated and contributors
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export function isTruthy<T>(item: T): item is Exclude<T, 0 | "" | false | null | undefined> {
|
|
||||||
return Boolean(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isNonNullish<T>(item: T): item is Exclude<T, null | undefined> {
|
|
||||||
return item != null;
|
|
||||||
}
|
|
@ -141,8 +141,8 @@ export function humanFriendlyJoin(elements: any[], mapper: (e: any) => string =
|
|||||||
* Calls .join(" ") on the arguments
|
* Calls .join(" ") on the arguments
|
||||||
* classes("one", "two") => "one two"
|
* classes("one", "two") => "one two"
|
||||||
*/
|
*/
|
||||||
export function classes(...classes: Array<string | null | undefined>) {
|
export function classes(...classes: string[]) {
|
||||||
return classes.filter(Boolean).join(" ");
|
return classes.filter(c => typeof c === "string").join(" ");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -117,7 +117,6 @@ const ModalAPI = mapMangledModuleLazy("onCloseRequest:null!=", {
|
|||||||
openModal: filters.byCode("onCloseRequest:null!="),
|
openModal: filters.byCode("onCloseRequest:null!="),
|
||||||
closeModal: filters.byCode("onCloseCallback&&"),
|
closeModal: filters.byCode("onCloseCallback&&"),
|
||||||
openModalLazy: m => m?.length === 1 && filters.byCode(".apply(this,arguments)")(m),
|
openModalLazy: m => m?.length === 1 && filters.byCode(".apply(this,arguments)")(m),
|
||||||
closeAllModals: filters.byCode(".value.key,")
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -143,10 +142,3 @@ export function openModal(render: RenderFunction, options?: ModalOptions, contex
|
|||||||
export function closeModal(modalKey: string, contextKey?: string): void {
|
export function closeModal(modalKey: string, contextKey?: string): void {
|
||||||
return ModalAPI.closeModal(modalKey, contextKey);
|
return ModalAPI.closeModal(modalKey, contextKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Close all open modals
|
|
||||||
*/
|
|
||||||
export function closeAllModals(): void {
|
|
||||||
return ModalAPI.closeAllModals();
|
|
||||||
}
|
|
||||||
|
@ -75,10 +75,6 @@ export interface PluginDef {
|
|||||||
* Whether this plugin is required and forcefully enabled
|
* Whether this plugin is required and forcefully enabled
|
||||||
*/
|
*/
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
/**
|
|
||||||
* Whether this plugin should be enabled by default, but can be disabled
|
|
||||||
*/
|
|
||||||
enabledByDefault?: boolean;
|
|
||||||
/**
|
/**
|
||||||
* Set this if your plugin only works on Browser or Desktop, not both
|
* Set this if your plugin only works on Browser or Desktop, not both
|
||||||
*/
|
*/
|
||||||
@ -233,12 +229,9 @@ type PluginSettingType<O extends PluginSettingDef> = O extends PluginSettingStri
|
|||||||
O extends PluginSettingSliderDef ? number :
|
O extends PluginSettingSliderDef ? number :
|
||||||
O extends PluginSettingComponentDef ? any :
|
O extends PluginSettingComponentDef ? any :
|
||||||
never;
|
never;
|
||||||
type PluginSettingDefaultType<O extends PluginSettingDef> = O extends PluginSettingSelectDef ? (
|
|
||||||
O["options"] extends { default?: boolean; }[] ? O["options"][number]["value"] : undefined
|
|
||||||
) : O extends { default: infer T; } ? T : undefined;
|
|
||||||
|
|
||||||
type SettingsStore<D extends SettingsDefinition> = {
|
type SettingsStore<D extends SettingsDefinition> = {
|
||||||
[K in keyof D]: PluginSettingType<D[K]> | PluginSettingDefaultType<D[K]>;
|
[K in keyof D]: PluginSettingType<D[K]>;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** An instance of defined plugin settings */
|
/** An instance of defined plugin settings */
|
||||||
|
@ -77,25 +77,3 @@ export async function rebuild() {
|
|||||||
return oldHashes["patcher.js"] !== newHashes["patcher.js"] ||
|
return oldHashes["patcher.js"] !== newHashes["patcher.js"] ||
|
||||||
oldHashes["preload.js"] !== newHashes["preload.js"];
|
oldHashes["preload.js"] !== newHashes["preload.js"];
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function maybePromptToUpdate(confirmMessage: string, checkForDev = false) {
|
|
||||||
if (IS_WEB) return;
|
|
||||||
if (checkForDev && IS_DEV) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const isOutdated = await checkForUpdates();
|
|
||||||
if (isOutdated) {
|
|
||||||
const wantsUpdate = confirm(confirmMessage);
|
|
||||||
if (wantsUpdate && isNewer) return alert("Your local copy has more recent commits. Please stash or reset them.");
|
|
||||||
if (wantsUpdate) {
|
|
||||||
await update();
|
|
||||||
const needFullRestart = await rebuild();
|
|
||||||
if (needFullRestart) DiscordNative.app.relaunch();
|
|
||||||
else location.reload();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
UpdateLogger.error(err);
|
|
||||||
alert("That also failed :( Try updating or re-installing with the installer!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -32,10 +32,10 @@ export const Forms = {
|
|||||||
FormText: waitForComponent<t.FormText>("FormText", m => m.Types?.INPUT_PLACEHOLDER),
|
FormText: waitForComponent<t.FormText>("FormText", m => m.Types?.INPUT_PLACEHOLDER),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Card = waitForComponent<t.Card>("Card", m => m.Types?.PRIMARY && m.defaultProps);
|
export const Card = waitForComponent<t.Card>("Card", m => m.Types?.PRIMARY === "cardPrimary");
|
||||||
export const Button = waitForComponent<t.Button>("Button", ["Hovers", "Looks", "Sizes"]);
|
export const Button = waitForComponent<t.Button>("Button", ["Hovers", "Looks", "Sizes"]);
|
||||||
export const Switch = waitForComponent<t.Switch>("Switch", filters.byCode("tooltipNote", "ringTarget"));
|
export const Switch = waitForComponent<t.Switch>("Switch", filters.byCode("tooltipNote", "ringTarget"));
|
||||||
export const Tooltip = waitForComponent<t.Tooltip>("Tooltip", filters.byCode("shouldShowTooltip:!1", "clickableOnMobile||"));
|
export const Tooltip = waitForComponent<t.Tooltip>("Tooltip", ["Positions", "Colors"]);
|
||||||
export const Timestamp = waitForComponent<t.Timestamp>("Timestamp", filters.byCode(".Messages.MESSAGE_EDITED_TIMESTAMP_A11Y_LABEL.format"));
|
export const Timestamp = waitForComponent<t.Timestamp>("Timestamp", filters.byCode(".Messages.MESSAGE_EDITED_TIMESTAMP_A11Y_LABEL.format"));
|
||||||
export const TextInput = waitForComponent<t.TextInput>("TextInput", ["defaultProps", "Sizes", "contextType"]);
|
export const TextInput = waitForComponent<t.TextInput>("TextInput", ["defaultProps", "Sizes", "contextType"]);
|
||||||
export const TextArea = waitForComponent<t.TextArea>("TextArea", filters.byCode("handleSetRef", "textArea"));
|
export const TextArea = waitForComponent<t.TextArea>("TextArea", filters.byCode("handleSetRef", "textArea"));
|
||||||
@ -45,12 +45,12 @@ export const Text = waitForComponent<t.Text>("Text", m => {
|
|||||||
return (s.length < 1500 && s.includes("data-text-variant") && s.includes("always-white"));
|
return (s.length < 1500 && s.includes("data-text-variant") && s.includes("always-white"));
|
||||||
});
|
});
|
||||||
export const Select = waitForComponent<t.Select>("Select", filters.byCode("optionClassName", "popoutPosition", "autoFocus", "maxVisibleItems"));
|
export const Select = waitForComponent<t.Select>("Select", filters.byCode("optionClassName", "popoutPosition", "autoFocus", "maxVisibleItems"));
|
||||||
const searchableSelectFilter = filters.byCode("autoFocus", ".Messages.SELECT");
|
|
||||||
export const SearchableSelect = waitForComponent<t.SearchableSelect>("SearchableSelect", m =>
|
|
||||||
m.render && searchableSelectFilter(m.render)
|
|
||||||
);
|
|
||||||
export const Slider = waitForComponent<t.Slider>("Slider", filters.byCode("closestMarkerIndex", "stickToMarkers"));
|
export const Slider = waitForComponent<t.Slider>("Slider", filters.byCode("closestMarkerIndex", "stickToMarkers"));
|
||||||
export const Flex = waitForComponent<t.Flex>("Flex", ["Justify", "Align", "Wrap"]);
|
export const Flex = waitForComponent<t.Flex>("Flex", ["Justify", "Align", "Wrap"]);
|
||||||
|
|
||||||
export const ButtonWrapperClasses = findByPropsLazy("buttonWrapper", "buttonContent") as Record<string, string>;
|
export const ButtonWrapperClasses = findByPropsLazy("buttonWrapper", "buttonContent") as Record<string, string>;
|
||||||
|
/**
|
||||||
|
* @deprecated Use @utils/margins instead
|
||||||
|
*/
|
||||||
|
export const Margins: t.Margins = findByPropsLazy("marginTop20");
|
||||||
export const ButtonLooks: t.ButtonLooks = findByPropsLazy("BLANK", "FILLED", "INVERTED");
|
export const ButtonLooks: t.ButtonLooks = findByPropsLazy("BLANK", "FILLED", "INVERTED");
|
||||||
|
51
src/webpack/common/types/components.d.ts
vendored
51
src/webpack/common/types/components.d.ts
vendored
@ -90,17 +90,16 @@ export type Tooltip = ComponentType<{
|
|||||||
|
|
||||||
/** Tooltip.Colors.BLACK */
|
/** Tooltip.Colors.BLACK */
|
||||||
color?: string;
|
color?: string;
|
||||||
/** TooltipPositions.TOP */
|
/** Tooltip.Positions.TOP */
|
||||||
position?: string;
|
position?: string;
|
||||||
|
|
||||||
tooltipClassName?: string;
|
tooltipClassName?: string;
|
||||||
tooltipContentClassName?: string;
|
tooltipContentClassName?: string;
|
||||||
}> & {
|
}> & {
|
||||||
|
Positions: Record<"BOTTOM" | "CENTER" | "LEFT" | "RIGHT" | "TOP" | "WINDOW_CENTER", string>;
|
||||||
Colors: Record<"BLACK" | "BRAND" | "CUSTOM" | "GREEN" | "GREY" | "PRIMARY" | "RED" | "YELLOW", string>;
|
Colors: Record<"BLACK" | "BRAND" | "CUSTOM" | "GREEN" | "GREY" | "PRIMARY" | "RED" | "YELLOW", string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TooltipPositions = Record<"BOTTOM" | "CENTER" | "LEFT" | "RIGHT" | "TOP" | "WINDOW_CENTER", string>;
|
|
||||||
|
|
||||||
export type Card = ComponentType<PropsWithChildren<HTMLProps<HTMLDivElement> & {
|
export type Card = ComponentType<PropsWithChildren<HTMLProps<HTMLDivElement> & {
|
||||||
editable?: boolean;
|
editable?: boolean;
|
||||||
outline?: boolean;
|
outline?: boolean;
|
||||||
@ -235,49 +234,6 @@ export type Select = ComponentType<PropsWithChildren<{
|
|||||||
"aria-labelledby"?: boolean;
|
"aria-labelledby"?: boolean;
|
||||||
}>>;
|
}>>;
|
||||||
|
|
||||||
export type SearchableSelect = ComponentType<PropsWithChildren<{
|
|
||||||
placeholder?: string;
|
|
||||||
options: ReadonlyArray<SelectOption>; // TODO
|
|
||||||
value?: SelectOption;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* - 0 ~ Filled
|
|
||||||
* - 1 ~ Custom
|
|
||||||
*/
|
|
||||||
look?: 0 | 1;
|
|
||||||
className?: string;
|
|
||||||
popoutClassName?: string;
|
|
||||||
wrapperClassName?: string;
|
|
||||||
popoutPosition?: "top" | "left" | "right" | "bottom" | "center" | "window_center";
|
|
||||||
optionClassName?: string;
|
|
||||||
|
|
||||||
autoFocus?: boolean;
|
|
||||||
isDisabled?: boolean;
|
|
||||||
clearable?: boolean;
|
|
||||||
closeOnSelect?: boolean;
|
|
||||||
clearOnSelect?: boolean;
|
|
||||||
multi?: boolean;
|
|
||||||
|
|
||||||
onChange(value: any): void;
|
|
||||||
onSearchChange?(value: string): void;
|
|
||||||
|
|
||||||
onClose?(): void;
|
|
||||||
onOpen?(): void;
|
|
||||||
onBlur?(): void;
|
|
||||||
|
|
||||||
renderOptionPrefix?(option: SelectOption): ReactNode;
|
|
||||||
renderOptionSuffix?(option: SelectOption): ReactNode;
|
|
||||||
|
|
||||||
filter?(option: SelectOption[], query: string): SelectOption[];
|
|
||||||
|
|
||||||
centerCaret?: boolean;
|
|
||||||
debounceTime?: number;
|
|
||||||
maxVisibleItems?: number;
|
|
||||||
popoutWidth?: number;
|
|
||||||
|
|
||||||
"aria-labelledby"?: boolean;
|
|
||||||
}>>;
|
|
||||||
|
|
||||||
export type Slider = ComponentType<PropsWithChildren<{
|
export type Slider = ComponentType<PropsWithChildren<{
|
||||||
initialValue: number;
|
initialValue: number;
|
||||||
defaultValue?: number;
|
defaultValue?: number;
|
||||||
@ -322,4 +278,7 @@ export type Flex = ComponentType<PropsWithChildren<any>> & {
|
|||||||
Direction: Record<"VERTICAL" | "HORIZONTAL" | "HORIZONTAL_REVERSE", string>;
|
Direction: Record<"VERTICAL" | "HORIZONTAL" | "HORIZONTAL_REVERSE", string>;
|
||||||
Justify: Record<"START" | "END" | "CENTER" | "BETWEEN" | "AROUND", string>;
|
Justify: Record<"START" | "END" | "CENTER" | "BETWEEN" | "AROUND", string>;
|
||||||
Wrap: Record<"NO_WRAP" | "WRAP" | "WRAP_REVERSE", string>;
|
Wrap: Record<"NO_WRAP" | "WRAP" | "WRAP_REVERSE", string>;
|
||||||
|
|
||||||
|
Content: ComponentType<PropsWithChildren<any>>;
|
||||||
|
Sidebar: ComponentType<PropsWithChildren<any>>;
|
||||||
};
|
};
|
||||||
|
@ -307,6 +307,13 @@ export function findByPropsLazy(...props: string[]) {
|
|||||||
return findLazy(filters.byProps(...props));
|
return findLazy(filters.byProps(...props));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find all modules that have the specified properties
|
||||||
|
*/
|
||||||
|
export function findAllByProps(...props: string[]) {
|
||||||
|
return findAll(filters.byProps(...props));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find a function by its code
|
* Find a function by its code
|
||||||
*/
|
*/
|
||||||
|
Reference in New Issue
Block a user