Compare commits
22 Commits
componentU
...
v1.3.1
Author | SHA1 | Date | |
---|---|---|---|
|
f89027f46a | ||
|
07a0ebb1d2 | ||
|
f09b44b0d5 | ||
|
b607eebcb7 | ||
|
0936ca2985 | ||
|
13bde79ec8 | ||
|
b592defaaf | ||
|
73354973a3 | ||
|
e12c0e546c | ||
|
088a8bd1b6 | ||
|
51adb26d01 | ||
|
cb980a1cad | ||
|
69b10c1f07 | ||
|
8e9ba7c7ee | ||
|
12e3c9234d | ||
|
1d8dcef394 | ||
|
4fe2845234 | ||
|
5e71ed286e | ||
|
5edbd2391d | ||
|
8472c3823e | ||
|
2103e52115 | ||
|
afbfb641e8 |
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "vencord",
|
"name": "vencord",
|
||||||
"private": "true",
|
"private": "true",
|
||||||
"version": "1.2.9",
|
"version": "1.3.1",
|
||||||
"description": "The cutest Discord client mod",
|
"description": "The cutest Discord client mod",
|
||||||
"homepage": "https://github.com/Vendicated/Vencord#readme",
|
"homepage": "https://github.com/Vendicated/Vencord#readme",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
|
@ -151,7 +151,6 @@ async function parseFile(fileName: string) {
|
|||||||
case "required":
|
case "required":
|
||||||
case "enabledByDefault":
|
case "enabledByDefault":
|
||||||
data[key] = value.kind === SyntaxKind.TrueKeyword;
|
data[key] = value.kind === SyntaxKind.TrueKeyword;
|
||||||
if (!data[key] && value.kind !== SyntaxKind.FalseKeyword) throw fail(`${key} is not a boolean literal`);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,4 +58,10 @@ export default {
|
|||||||
getVersions: () => process.versions as Partial<NodeJS.ProcessVersions>,
|
getVersions: () => process.versions as Partial<NodeJS.ProcessVersions>,
|
||||||
openExternal: (url: string) => invoke<void>(IpcEvents.OPEN_EXTERNAL, url)
|
openExternal: (url: string) => invoke<void>(IpcEvents.OPEN_EXTERNAL, url)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
pluginHelpers: {
|
||||||
|
OpenInApp: {
|
||||||
|
resolveRedirect: (url: string) => invoke<string>(IpcEvents.OPEN_IN_APP__RESOLVE_REDIRECT, url),
|
||||||
|
},
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import "./updater";
|
import "./updater";
|
||||||
|
import "./ipcPlugins";
|
||||||
|
|
||||||
import { debounce } from "@utils/debounce";
|
import { debounce } from "@utils/debounce";
|
||||||
import { IpcEvents } from "@utils/IpcEvents";
|
import { IpcEvents } from "@utils/IpcEvents";
|
||||||
|
46
src/main/ipcPlugins.ts
Normal file
46
src/main/ipcPlugins.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* 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 { IpcEvents } from "@utils/IpcEvents";
|
||||||
|
import { ipcMain } from "electron";
|
||||||
|
import { request } from "https";
|
||||||
|
|
||||||
|
// #region OpenInApp
|
||||||
|
// These links don't support CORS, so this has to be native
|
||||||
|
const validRedirectUrls = /^https:\/\/(spotify\.link|s\.team)\/.+$/;
|
||||||
|
|
||||||
|
function getRedirect(url: string) {
|
||||||
|
return new Promise<string>((resolve, reject) => {
|
||||||
|
const req = request(new URL(url), { method: "HEAD" }, res => {
|
||||||
|
resolve(
|
||||||
|
res.headers.location
|
||||||
|
? getRedirect(res.headers.location)
|
||||||
|
: url
|
||||||
|
);
|
||||||
|
});
|
||||||
|
req.on("error", reject);
|
||||||
|
req.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ipcMain.handle(IpcEvents.OPEN_IN_APP__RESOLVE_REDIRECT, async (_, url: string) => {
|
||||||
|
if (!validRedirectUrls.test(url)) return url;
|
||||||
|
|
||||||
|
return getRedirect(url);
|
||||||
|
});
|
||||||
|
// #endregion
|
@ -31,7 +31,8 @@ export const ALLOWED_PROTOCOLS = [
|
|||||||
"https:",
|
"https:",
|
||||||
"http:",
|
"http:",
|
||||||
"steam:",
|
"steam:",
|
||||||
"spotify:"
|
"spotify:",
|
||||||
|
"com.epicgames.launcher:",
|
||||||
];
|
];
|
||||||
|
|
||||||
export const IS_VANILLA = /* @__PURE__ */ process.argv.includes("--vanilla");
|
export const IS_VANILLA = /* @__PURE__ */ process.argv.includes("--vanilla");
|
||||||
|
@ -45,7 +45,7 @@ function ToggleIconOff() {
|
|||||||
className={RegisteredGamesClasses.overlayToggleIconOff}
|
className={RegisteredGamesClasses.overlayToggleIconOff}
|
||||||
height="24"
|
height="24"
|
||||||
width="24"
|
width="24"
|
||||||
viewBox="0 0 32 26"
|
viewBox="0 2.2 32 26"
|
||||||
aria-hidden={true}
|
aria-hidden={true}
|
||||||
role="img"
|
role="img"
|
||||||
>
|
>
|
||||||
@ -77,7 +77,7 @@ function ToggleIconOn({ forceWhite }: { forceWhite?: boolean; }) {
|
|||||||
className={RegisteredGamesClasses.overlayToggleIconOn}
|
className={RegisteredGamesClasses.overlayToggleIconOn}
|
||||||
height="24"
|
height="24"
|
||||||
width="24"
|
width="24"
|
||||||
viewBox="0 0 32 26"
|
viewBox="0 2.2 32 26"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
className={forceWhite ? "" : RegisteredGamesClasses.fill}
|
className={forceWhite ? "" : RegisteredGamesClasses.fill}
|
||||||
@ -119,7 +119,7 @@ function ToggleActivityComponentWithBackground({ activity }: { activity: Ignored
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`${TryItOutClasses.tryItOutBadge} ${BaseShapeRoundClasses.baseShapeRound}`}
|
className={`${TryItOutClasses.tryItOutBadge} ${BaseShapeRoundClasses.baseShapeRound}`}
|
||||||
style={{ padding: "0px 2px" }}
|
style={{ padding: "0px 2px", height: 28 }}
|
||||||
>
|
>
|
||||||
<ToggleActivityComponent activity={activity} forceWhite={true} />
|
<ToggleActivityComponent activity={activity} forceWhite={true} />
|
||||||
</div>
|
</div>
|
||||||
@ -157,10 +157,16 @@ export default definePlugin({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: ".overlayBadge",
|
find: ".overlayBadge",
|
||||||
replacement: {
|
replacement: [
|
||||||
match: /(?<=\(\)\.badgeContainer.+?(\i)\.name}\):null)/,
|
{
|
||||||
replace: (_, props) => `,$self.renderToggleActivityButton(${props})`
|
match: /(?<=\(\)\.badgeContainer,children:).{0,50}?name:(\i)\.name.+?null/,
|
||||||
|
replace: (m, props) => `[${m},$self.renderToggleActivityButton(${props})]`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
match: /(?<=\(\)\.badgeContainer,children:).{0,50}?name:(\i\.application)\.name.+?null/,
|
||||||
|
replace: (m, props) => `${m},$self.renderToggleActivityButton(${props})`
|
||||||
}
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: '.displayName="LocalActivityStore"',
|
find: '.displayName="LocalActivityStore"',
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
* 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 { classNameFactory } from "@api/Styles";
|
||||||
import { FluxDispatcher, React, useRef, useState } from "@webpack/common";
|
import { FluxDispatcher, React, useRef, useState } from "@webpack/common";
|
||||||
|
|
||||||
import { ELEMENT_ID } from "../constants";
|
import { ELEMENT_ID } from "../constants";
|
||||||
@ -33,6 +34,8 @@ export interface MagnifierProps {
|
|||||||
instance: any;
|
instance: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const cl = classNameFactory("vc-imgzoom-");
|
||||||
|
|
||||||
export const Magnifier: React.FC<MagnifierProps> = ({ instance, size: initialSize, zoom: initalZoom }) => {
|
export const Magnifier: React.FC<MagnifierProps> = ({ instance, size: initialSize, zoom: initalZoom }) => {
|
||||||
const [ready, setReady] = useState(false);
|
const [ready, setReady] = useState(false);
|
||||||
|
|
||||||
@ -156,7 +159,7 @@ export const Magnifier: React.FC<MagnifierProps> = ({ instance, size: initialSiz
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="vc-imgzoom-lens"
|
className={cl("lens", { "nearest-neighbor": settings.store.nearestNeighbour, square: settings.store.square })}
|
||||||
style={{
|
style={{
|
||||||
opacity,
|
opacity,
|
||||||
width: size.current + "px",
|
width: size.current + "px",
|
||||||
|
@ -23,7 +23,7 @@ import { makeRange } from "@components/PluginSettings/components";
|
|||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { debounce } from "@utils/debounce";
|
import { debounce } from "@utils/debounce";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { Menu, React, ReactDOM } from "@webpack/common";
|
import { ContextMenu, Menu, React, ReactDOM } from "@webpack/common";
|
||||||
import type { Root } from "react-dom/client";
|
import type { Root } from "react-dom/client";
|
||||||
|
|
||||||
import { Magnifier, MagnifierProps } from "./components/Magnifier";
|
import { Magnifier, MagnifierProps } from "./components/Magnifier";
|
||||||
@ -50,6 +50,18 @@ export const settings = definePluginSettings({
|
|||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
nearestNeighbour: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Use Nearest Neighbour Interpolation when scaling images",
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
square: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Make the lens square",
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
|
||||||
zoom: {
|
zoom: {
|
||||||
description: "Zoom of the lens",
|
description: "Zoom of the lens",
|
||||||
type: OptionType.SLIDER,
|
type: OptionType.SLIDER,
|
||||||
@ -78,9 +90,17 @@ export const settings = definePluginSettings({
|
|||||||
const imageContextMenuPatch: NavContextMenuPatchCallback = children => () => {
|
const imageContextMenuPatch: NavContextMenuPatchCallback = children => () => {
|
||||||
children.push(
|
children.push(
|
||||||
<Menu.MenuGroup id="image-zoom">
|
<Menu.MenuGroup id="image-zoom">
|
||||||
{/* thanks SpotifyControls */}
|
<Menu.MenuCheckboxItem
|
||||||
|
id="vc-square"
|
||||||
|
label="Square Lens"
|
||||||
|
checked={settings.store.square}
|
||||||
|
action={() => {
|
||||||
|
settings.store.square = !settings.store.square;
|
||||||
|
ContextMenu.close();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<Menu.MenuControlItem
|
<Menu.MenuControlItem
|
||||||
id="zoom"
|
id="vc-zoom"
|
||||||
label="Zoom"
|
label="Zoom"
|
||||||
control={(props, ref) => (
|
control={(props, ref) => (
|
||||||
<Menu.MenuSliderControl
|
<Menu.MenuSliderControl
|
||||||
@ -94,7 +114,7 @@ const imageContextMenuPatch: NavContextMenuPatchCallback = children => () => {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<Menu.MenuControlItem
|
<Menu.MenuControlItem
|
||||||
id="size"
|
id="vc-size"
|
||||||
label="Lens Size"
|
label="Lens Size"
|
||||||
control={(props, ref) => (
|
control={(props, ref) => (
|
||||||
<Menu.MenuSliderControl
|
<Menu.MenuSliderControl
|
||||||
@ -108,7 +128,7 @@ const imageContextMenuPatch: NavContextMenuPatchCallback = children => () => {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<Menu.MenuControlItem
|
<Menu.MenuControlItem
|
||||||
id="zoom-speed"
|
id="vc-zoom-speed"
|
||||||
label="Zoom Speed"
|
label="Zoom Speed"
|
||||||
control={(props, ref) => (
|
control={(props, ref) => (
|
||||||
<Menu.MenuSliderControl
|
<Menu.MenuSliderControl
|
||||||
|
@ -11,6 +11,14 @@
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vc-imgzoom-square {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-imgzoom-nearest-neighbor > img {
|
||||||
|
image-rendering: pixelated; /* https://googlechrome.github.io/samples/image-rendering-pixelated/index.html */
|
||||||
|
}
|
||||||
|
|
||||||
/* make the carousel take up less space so we can click the backdrop and exit out of it */
|
/* make the carousel take up less space so we can click the backdrop and exit out of it */
|
||||||
[class|="carouselModal"] {
|
[class|="carouselModal"] {
|
||||||
height: fit-content;
|
height: fit-content;
|
||||||
|
@ -132,7 +132,7 @@ export default definePlugin({
|
|||||||
find: ".Messages.MESSAGE_EDITED,",
|
find: ".Messages.MESSAGE_EDITED,",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /var .,.,.=(.)\.className,.=.\.message,.=.\.children,.=.\.content,.=.\.onUpdate/gm,
|
match: /var .,.,.=(.)\.className,.=.\.message,.=.\.children,.=.\.content,.=.\.onUpdate/gm,
|
||||||
replace: "try {$1 && $self.INV_REGEX.test($1.content[0]) ? $1.content.push($self.indicator()) : null } catch {};$&"
|
replace: "try {$1 && $self.INV_REGEX.test($1.message.content) ? $1.content.push($self.indicator()) : null } catch {};$&"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -200,8 +200,11 @@ export default definePlugin({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (urlCheck?.length)
|
if (urlCheck?.length) {
|
||||||
message.embeds.push(await this.getEmbed(new URL(urlCheck[0])));
|
const embed = await this.getEmbed(new URL(urlCheck[0]));
|
||||||
|
if (embed)
|
||||||
|
message.embeds.push(embed);
|
||||||
|
}
|
||||||
|
|
||||||
this.updateMessage(message);
|
this.updateMessage(message);
|
||||||
},
|
},
|
||||||
|
@ -21,7 +21,7 @@ import { makeRange } from "@components/PluginSettings/components/SettingSliderCo
|
|||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { sleep } from "@utils/misc";
|
import { sleep } from "@utils/misc";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { SelectedChannelStore, UserStore } from "@webpack/common";
|
import { RelationshipStore, SelectedChannelStore, UserStore } from "@webpack/common";
|
||||||
import { Message, ReactionEmoji } from "discord-types/general";
|
import { Message, ReactionEmoji } from "discord-types/general";
|
||||||
|
|
||||||
interface IMessageCreate {
|
interface IMessageCreate {
|
||||||
@ -37,6 +37,7 @@ interface IReactionAdd {
|
|||||||
optimistic: boolean;
|
optimistic: boolean;
|
||||||
channelId: string;
|
channelId: string;
|
||||||
messageId: string;
|
messageId: string;
|
||||||
|
messageAuthorId: string;
|
||||||
userId: "195136840355807232";
|
userId: "195136840355807232";
|
||||||
emoji: ReactionEmoji;
|
emoji: ReactionEmoji;
|
||||||
}
|
}
|
||||||
@ -71,6 +72,11 @@ const settings = definePluginSettings({
|
|||||||
description: "Ignore bots",
|
description: "Ignore bots",
|
||||||
type: OptionType.BOOLEAN,
|
type: OptionType.BOOLEAN,
|
||||||
default: true
|
default: true
|
||||||
|
},
|
||||||
|
ignoreBlocked: {
|
||||||
|
description: "Ignore blocked users",
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
default: true
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -85,6 +91,7 @@ export default definePlugin({
|
|||||||
if (optimistic || type !== "MESSAGE_CREATE") return;
|
if (optimistic || type !== "MESSAGE_CREATE") return;
|
||||||
if (message.state === "SENDING") return;
|
if (message.state === "SENDING") return;
|
||||||
if (settings.store.ignoreBots && message.author?.bot) return;
|
if (settings.store.ignoreBots && message.author?.bot) return;
|
||||||
|
if (settings.store.ignoreBlocked && RelationshipStore.isBlocked(message.author?.id)) return;
|
||||||
if (!message.content) return;
|
if (!message.content) return;
|
||||||
if (channelId !== SelectedChannelStore.getChannelId()) return;
|
if (channelId !== SelectedChannelStore.getChannelId()) return;
|
||||||
|
|
||||||
@ -96,9 +103,10 @@ export default definePlugin({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
MESSAGE_REACTION_ADD({ optimistic, type, channelId, userId, emoji }: IReactionAdd) {
|
MESSAGE_REACTION_ADD({ optimistic, type, channelId, userId, messageAuthorId, emoji }: IReactionAdd) {
|
||||||
if (optimistic || type !== "MESSAGE_REACTION_ADD") return;
|
if (optimistic || type !== "MESSAGE_REACTION_ADD") return;
|
||||||
if (settings.store.ignoreBots && UserStore.getUser(userId)?.bot) return;
|
if (settings.store.ignoreBots && UserStore.getUser(userId)?.bot) return;
|
||||||
|
if (settings.store.ignoreBlocked && RelationshipStore.isBlocked(messageAuthorId)) return;
|
||||||
if (channelId !== SelectedChannelStore.getChannelId()) return;
|
if (channelId !== SelectedChannelStore.getChannelId()) return;
|
||||||
|
|
||||||
const name = emoji.name.toLowerCase();
|
const name = emoji.name.toLowerCase();
|
||||||
|
147
src/plugins/openInApp.ts
Normal file
147
src/plugins/openInApp.ts
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
/*
|
||||||
|
* 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 { definePluginSettings } from "@api/Settings";
|
||||||
|
import { Devs } from "@utils/constants";
|
||||||
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
|
import { showToast, Toasts } from "@webpack/common";
|
||||||
|
import { MouseEvent } from "react";
|
||||||
|
|
||||||
|
const ShortUrlMatcher = /^https:\/\/(spotify\.link|s\.team)\/.+$/;
|
||||||
|
const SpotifyMatcher = /^https:\/\/open\.spotify\.com\/(track|album|artist|playlist|user)\/(.+)(?:\?.+?)?$/;
|
||||||
|
const SteamMatcher = /^https:\/\/(steamcommunity\.com|(?:help|store)\.steampowered\.com)\/.+$/;
|
||||||
|
const EpicMatcher = /^https:\/\/store\.epicgames\.com\/(.+)$/;
|
||||||
|
|
||||||
|
const settings = definePluginSettings({
|
||||||
|
spotify: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Open Spotify links in the Spotify app",
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
steam: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Open Steam links in the Steam app",
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
epic: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Open Epic Games links in the Epic Games Launcher",
|
||||||
|
default: true,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "OpenInApp",
|
||||||
|
description: "Open Spotify, Steam and Epic Games URLs in their respective apps instead of your browser",
|
||||||
|
authors: [Devs.Ven],
|
||||||
|
settings,
|
||||||
|
|
||||||
|
patches: [
|
||||||
|
{
|
||||||
|
find: '"MaskedLinkStore"',
|
||||||
|
replacement: {
|
||||||
|
match: /return ((\i)\.apply\(this,arguments\))(?=\}function \i.{0,200}\.trusted)/,
|
||||||
|
replace: "return $self.handleLink(...arguments).then(handled => handled || $1)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Make Spotify profile activity links open in app on web
|
||||||
|
{
|
||||||
|
find: "WEB_OPEN(",
|
||||||
|
predicate: () => !IS_DISCORD_DESKTOP && settings.store.spotify,
|
||||||
|
replacement: {
|
||||||
|
match: /\i\.\i\.isProtocolRegistered\(\)(.{0,100})window.open/g,
|
||||||
|
replace: "true$1VencordNative.native.openExternal"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: ".CONNECTED_ACCOUNT_VIEWED,",
|
||||||
|
replacement: {
|
||||||
|
match: /(?<=href:\i,onClick:function\(\)\{)(?=return \i=(\i)\.type,.{0,50}CONNECTED_ACCOUNT_VIEWED)/,
|
||||||
|
replace: "$self.handleAccountView(arguments[0],$1.type,$1.id);"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
async handleLink(data: { href: string; }, event: MouseEvent) {
|
||||||
|
if (!data) return false;
|
||||||
|
|
||||||
|
let url = data.href;
|
||||||
|
if (!IS_WEB && ShortUrlMatcher.test(url)) {
|
||||||
|
event.preventDefault();
|
||||||
|
// CORS jumpscare
|
||||||
|
url = await VencordNative.pluginHelpers.OpenInApp.resolveRedirect(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
spotify: {
|
||||||
|
if (!settings.store.spotify) break spotify;
|
||||||
|
|
||||||
|
const match = SpotifyMatcher.exec(url);
|
||||||
|
if (!match) break spotify;
|
||||||
|
|
||||||
|
const [, type, id] = match;
|
||||||
|
VencordNative.native.openExternal(`spotify:${type}:${id}`);
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
steam: {
|
||||||
|
if (!settings.store.steam) break steam;
|
||||||
|
|
||||||
|
if (!SteamMatcher.test(url)) break steam;
|
||||||
|
|
||||||
|
VencordNative.native.openExternal(`steam://openurl/${url}`);
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
// Steam does not focus itself so show a toast so it's slightly less confusing
|
||||||
|
showToast("Opened link in Steam", Toasts.Type.SUCCESS);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
epic: {
|
||||||
|
if (!settings.store.epic) break epic;
|
||||||
|
|
||||||
|
const match = EpicMatcher.exec(url);
|
||||||
|
if (!match) break epic;
|
||||||
|
|
||||||
|
VencordNative.native.openExternal(`com.epicgames.launcher://store/${match[1]}`);
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// in case short url didn't end up being something we can handle
|
||||||
|
if (event.defaultPrevented) {
|
||||||
|
window.open(url, "_blank");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
handleAccountView(event: { preventDefault(): void; }, platformType: string, userId: string) {
|
||||||
|
if (platformType === "spotify" && settings.store.spotify) {
|
||||||
|
VencordNative.native.openExternal(`spotify:user:${userId}`);
|
||||||
|
event.preventDefault();
|
||||||
|
} else if (platformType === "steam" && settings.store.steam) {
|
||||||
|
VencordNative.native.openExternal(`steam://openurl/https://steamcommunity.com/profiles/${userId}`);
|
||||||
|
showToast("Opened link in Steam", Toasts.Type.SUCCESS);
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
@ -52,7 +52,7 @@ export function CompactPronounsChatComponentWrapper({ message }: { message: Mess
|
|||||||
}
|
}
|
||||||
|
|
||||||
function PronounsChatComponent({ message }: { message: Message; }) {
|
function PronounsChatComponent({ message }: { message: Message; }) {
|
||||||
const result = useFormattedPronouns(message.author.id);
|
const [result] = useFormattedPronouns(message.author.id);
|
||||||
|
|
||||||
return result
|
return result
|
||||||
? (
|
? (
|
||||||
@ -64,7 +64,7 @@ function PronounsChatComponent({ message }: { message: Message; }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function CompactPronounsChatComponent({ message }: { message: Message; }) {
|
export function CompactPronounsChatComponent({ message }: { message: Message; }) {
|
||||||
const result = useFormattedPronouns(message.author.id);
|
const [result] = useFormattedPronouns(message.author.id);
|
||||||
|
|
||||||
return result
|
return result
|
||||||
? (
|
? (
|
||||||
|
@ -26,6 +26,11 @@ import { CompactPronounsChatComponentWrapper, PronounsChatComponentWrapper } fro
|
|||||||
import { useProfilePronouns } from "./pronoundbUtils";
|
import { useProfilePronouns } from "./pronoundbUtils";
|
||||||
import { settings } from "./settings";
|
import { settings } from "./settings";
|
||||||
|
|
||||||
|
const PRONOUN_TOOLTIP_PATCH = {
|
||||||
|
match: /text:(.{0,10}.Messages\.USER_PROFILE_PRONOUNS)(?=,)/,
|
||||||
|
replace: '$& + (typeof vcPronounSource !== "undefined" ? ` (${vcPronounSource})` : "")'
|
||||||
|
};
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "PronounDB",
|
name: "PronounDB",
|
||||||
authors: [Devs.Tyman, Devs.TheKodeToad, Devs.Ven],
|
authors: [Devs.Tyman, Devs.TheKodeToad, Devs.Ven],
|
||||||
@ -50,18 +55,24 @@ export default definePlugin({
|
|||||||
// Patch the profile popout username header to use our pronoun hook instead of Discord's pronouns
|
// Patch the profile popout username header to use our pronoun hook instead of Discord's pronouns
|
||||||
{
|
{
|
||||||
find: ".userTagNoNickname",
|
find: ".userTagNoNickname",
|
||||||
replacement: {
|
replacement: [
|
||||||
match: /=(\i)\.pronouns/,
|
{
|
||||||
replace: "=$self.useProfilePronouns($1.user.id,$1.pronouns)"
|
match: /,(\i)=(\i)\.pronouns/,
|
||||||
}
|
replace: ",[$1,vcPronounSource]=$self.useProfilePronouns($2.user.id)"
|
||||||
|
},
|
||||||
|
PRONOUN_TOOLTIP_PATCH
|
||||||
|
]
|
||||||
},
|
},
|
||||||
// Patch the profile modal username header to use our pronoun hook instead of Discord's pronouns
|
// Patch the profile modal username header to use our pronoun hook instead of Discord's pronouns
|
||||||
{
|
{
|
||||||
find: ".USER_PROFILE_ACTIVITY",
|
find: ".USER_PROFILE_ACTIVITY",
|
||||||
replacement: {
|
replacement: [
|
||||||
match: /\).showPronouns/,
|
{
|
||||||
replace: ").showPronouns||true;const vcPronounce=$self.useProfilePronouns(arguments[0].user.id,arguments[0].displayProfile?.pronouns);if(arguments[0].displayProfile&&vcPronounce)arguments[0].displayProfile.pronouns=vcPronounce"
|
match: /getGlobalName\(\i\);(?<=displayProfile.{0,200})/,
|
||||||
}
|
replace: "$&const [vcPronounce,vcPronounSource]=$self.useProfilePronouns(arguments[0].user.id);if(arguments[0].displayProfile&&vcPronounce)arguments[0].displayProfile.pronouns=vcPronounce;"
|
||||||
|
},
|
||||||
|
PRONOUN_TOOLTIP_PATCH
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
|
@ -29,6 +29,9 @@ import { PronounCode, PronounMapping, PronounsResponse } from "./types";
|
|||||||
|
|
||||||
const UserProfileStore = findStoreLazy("UserProfileStore");
|
const UserProfileStore = findStoreLazy("UserProfileStore");
|
||||||
|
|
||||||
|
type PronounsWithSource = [string | null, string];
|
||||||
|
const EmptyPronouns: PronounsWithSource = [null, ""];
|
||||||
|
|
||||||
export const enum PronounsFormat {
|
export const enum PronounsFormat {
|
||||||
Lowercase = "LOWERCASE",
|
Lowercase = "LOWERCASE",
|
||||||
Capitalized = "CAPITALIZED"
|
Capitalized = "CAPITALIZED"
|
||||||
@ -62,45 +65,48 @@ function getDiscordPronouns(id: string) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useFormattedPronouns(id: string, discordPronouns: string = getDiscordPronouns(id)): string | null {
|
export function useFormattedPronouns(id: string): PronounsWithSource {
|
||||||
const [result] = useAwaiter(() => fetchPronouns(id, discordPronouns), {
|
// Discord is so stupid you can put tons of newlines in pronouns
|
||||||
fallbackValue: getCachedPronouns(id, discordPronouns),
|
const discordPronouns = getDiscordPronouns(id)?.trim().replace(NewLineRe, " ");
|
||||||
|
|
||||||
|
const [result] = useAwaiter(() => fetchPronouns(id), {
|
||||||
|
fallbackValue: getCachedPronouns(id),
|
||||||
onError: e => console.error("Fetching pronouns failed: ", e)
|
onError: e => console.error("Fetching pronouns failed: ", e)
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result && result !== "unspecified")
|
if (settings.store.pronounSource === PronounSource.PreferDiscord && discordPronouns)
|
||||||
return Object.hasOwn(PronounMapping, result)
|
return [discordPronouns, "Discord"];
|
||||||
? formatPronouns(result) // PronounDB
|
|
||||||
: result; // Discord
|
|
||||||
|
|
||||||
return null;
|
if (result && result !== "unspecified")
|
||||||
|
return [formatPronouns(result), "PronounDB"];
|
||||||
|
|
||||||
|
return [discordPronouns, "Discord"];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useProfilePronouns(id: string, discordPronouns: string) {
|
export function useProfilePronouns(id: string): PronounsWithSource {
|
||||||
const pronouns = useFormattedPronouns(id, discordPronouns);
|
const pronouns = useFormattedPronouns(id);
|
||||||
|
|
||||||
if (!settings.store.showInProfile) return null;
|
if (!settings.store.showInProfile) return EmptyPronouns;
|
||||||
if (!settings.store.showSelf && id === UserStore.getCurrentUser().id) return null;
|
if (!settings.store.showSelf && id === UserStore.getCurrentUser().id) return EmptyPronouns;
|
||||||
|
|
||||||
return pronouns;
|
return pronouns;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Gets the cached pronouns, if you're too impatient for a promise!
|
const NewLineRe = /\n+/g;
|
||||||
export function getCachedPronouns(id: string, discordPronouns: string): string | null {
|
|
||||||
if (settings.store.pronounSource === PronounSource.PreferDiscord && discordPronouns)
|
|
||||||
return discordPronouns;
|
|
||||||
|
|
||||||
|
// Gets the cached pronouns, if you're too impatient for a promise!
|
||||||
|
export function getCachedPronouns(id: string): string | null {
|
||||||
const cached = cache[id];
|
const cached = cache[id];
|
||||||
if (cached && cached !== "unspecified") return cached;
|
if (cached && cached !== "unspecified") return cached;
|
||||||
|
|
||||||
return discordPronouns || cached || null;
|
return cached || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetches the pronouns for one id, returning a promise that resolves if it was cached, or once the request is completed
|
// Fetches the pronouns for one id, returning a promise that resolves if it was cached, or once the request is completed
|
||||||
export function fetchPronouns(id: string, discordPronouns: string): Promise<string> {
|
export function fetchPronouns(id: string): Promise<string> {
|
||||||
return new Promise(res => {
|
return new Promise(res => {
|
||||||
const cached = getCachedPronouns(id, discordPronouns);
|
const cached = getCachedPronouns(id);
|
||||||
if (cached) return res(cached);
|
if (cached) return res(cached);
|
||||||
|
|
||||||
// If there is already a request added, then just add this callback to it
|
// If there is already a request added, then just add this callback to it
|
||||||
|
@ -147,6 +147,13 @@ function CompactConnectionComponent({ connection, theme }: { connection: Connect
|
|||||||
className="vc-user-connection"
|
className="vc-user-connection"
|
||||||
href={url}
|
href={url}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
onClick={e => {
|
||||||
|
if (Vencord.Plugins.isPluginEnabled("OpenInApp")) {
|
||||||
|
const OpenInApp = Vencord.Plugins.plugins.OpenInApp as any as typeof import("../openInApp").default;
|
||||||
|
// handleLink will .preventDefault() if applicable
|
||||||
|
OpenInApp.handleLink(e.currentTarget, e);
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{img}
|
{img}
|
||||||
</a>
|
</a>
|
||||||
|
@ -342,32 +342,27 @@ export default definePlugin({
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: "Guild voice channel without guild id.",
|
find: "useNotificationSettingsItem: channel cannot be undefined",
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
// Render our HiddenChannelLockScreen component instead of the main stage channel component
|
// Render our HiddenChannelLockScreen component instead of the main stage channel component
|
||||||
match: /Guild voice channel without guild id.+?children:(?<=(\i)\.getGuildId\(\).+?)(?=.{0,20}?}\)}function)/,
|
match: /"124px".+?children:(?<=var (\i)=\i\.channel.+?)(?=.{0,20}?}\)}function)/,
|
||||||
replace: (m, channel) => `${m}$self.isHiddenChannel(${channel})?$self.HiddenChannelLockScreen(${channel}):`
|
replace: (m, channel) => `${m}$self.isHiddenChannel(${channel})?$self.HiddenChannelLockScreen(${channel}):`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Disable useless components for the HiddenChannelLockScreen of stage channels
|
// Disable useless components for the HiddenChannelLockScreen of stage channels
|
||||||
match: /render(?!Header).{0,30}?:(?<=(\i)\.getGuildId\(\).+?Guild voice channel without guild id.+?)/g,
|
match: /render(?:BottomLeft|BottomCenter|BottomRight|ChatToasts):(?<=var (\i)=\i\.channel.+?)/g,
|
||||||
replace: (m, channel) => `${m}$self.isHiddenChannel(${channel})?null:`
|
replace: (m, channel) => `${m}$self.isHiddenChannel(${channel})?null:`
|
||||||
},
|
},
|
||||||
// Prevent Discord from replacing our route if we aren't connected to the stage channel
|
|
||||||
{
|
|
||||||
match: /(?=!\i&&!\i&&!\i.{0,80}?(\i)\.getGuildId\(\).{0,50}?Guild voice channel without guild id)(?<=if\()/,
|
|
||||||
replace: (_, channel) => `!$self.isHiddenChannel(${channel})&&`
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
// Disable gradients for the HiddenChannelLockScreen of stage channels
|
// Disable gradients for the HiddenChannelLockScreen of stage channels
|
||||||
match: /Guild voice channel without guild id.+?disableGradients:(?<=(\i)\.getGuildId\(\).+?)/,
|
match: /"124px".+?disableGradients:(?<=(\i)\.getGuildId\(\).+?)/,
|
||||||
replace: (m, channel) => `${m}$self.isHiddenChannel(${channel})||`
|
replace: (m, channel) => `${m}$self.isHiddenChannel(${channel})||`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Disable strange styles applied to the header for the HiddenChannelLockScreen of stage channels
|
// Disable strange styles applied to the header for the HiddenChannelLockScreen of stage channels
|
||||||
match: /Guild voice channel without guild id.+?style:(?<=(\i)\.getGuildId\(\).+?)/,
|
match: /"124px".+?style:(?<=(\i)\.getGuildId\(\).+?)/,
|
||||||
replace: (m, channel) => `${m}$self.isHiddenChannel(${channel})?undefined:`
|
replace: (m, channel) => `${m}$self.isHiddenChannel(${channel})?void 0:`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Remove the divider and amount of users in stage channel components for the HiddenChannelLockScreen
|
// Remove the divider and amount of users in stage channel components for the HiddenChannelLockScreen
|
||||||
@ -376,7 +371,7 @@ export default definePlugin({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Remove the open chat button for the HiddenChannelLockScreen
|
// Remove the open chat button for the HiddenChannelLockScreen
|
||||||
match: /"recents".+?null,(?=.+?channelId:(\i)\.id,showRequestToSpeakSidebar)/,
|
match: /"recents".+?&&(?=\(.+?channelId:(\i)\.id,showRequestToSpeakSidebar)/,
|
||||||
replace: (m, channel) => `${m}!$self.isHiddenChannel(${channel})&&`
|
replace: (m, channel) => `${m}!$self.isHiddenChannel(${channel})&&`
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -23,6 +23,7 @@ import { Flex } from "@components/Flex";
|
|||||||
import { ImageIcon, LinkIcon, OpenExternalIcon } from "@components/Icons";
|
import { ImageIcon, LinkIcon, OpenExternalIcon } from "@components/Icons";
|
||||||
import { Link } from "@components/Link";
|
import { Link } from "@components/Link";
|
||||||
import { debounce } from "@utils/debounce";
|
import { debounce } from "@utils/debounce";
|
||||||
|
import { openImageModal } from "@utils/discord";
|
||||||
import { classes, copyWithToast } from "@utils/misc";
|
import { classes, copyWithToast } from "@utils/misc";
|
||||||
import { ContextMenu, FluxDispatcher, Forms, Menu, React, useEffect, useState, useStateFromStores } from "@webpack/common";
|
import { ContextMenu, FluxDispatcher, Forms, Menu, React, useEffect, useState, useStateFromStores } from "@webpack/common";
|
||||||
|
|
||||||
@ -231,7 +232,7 @@ function AlbumContextMenu({ track }: { track: Track; }) {
|
|||||||
id="view-cover"
|
id="view-cover"
|
||||||
label="View Album Cover"
|
label="View Album Cover"
|
||||||
// trolley
|
// trolley
|
||||||
action={() => (Vencord.Plugins.plugins.ViewIcons as any).openImage(track.album.image.url)}
|
action={() => openImageModal(track.album.image.url)}
|
||||||
icon={ImageIcon}
|
icon={ImageIcon}
|
||||||
/>
|
/>
|
||||||
<Menu.MenuControlItem
|
<Menu.MenuControlItem
|
||||||
|
@ -89,7 +89,7 @@ export const SpotifyStore = proxyLazy(() => {
|
|||||||
public isSettingPosition = false;
|
public isSettingPosition = false;
|
||||||
|
|
||||||
public openExternal(path: string) {
|
public openExternal(path: string) {
|
||||||
const url = Settings.plugins.SpotifyControls.useSpotifyUris
|
const url = Settings.plugins.SpotifyControls.useSpotifyUris || Vencord.Plugins.isPluginEnabled("OpenInApp")
|
||||||
? "spotify:" + path.replaceAll("/", (_, idx) => idx === 0 ? "" : ":")
|
? "spotify:" + path.replaceAll("/", (_, idx) => idx === 0 ? "" : ":")
|
||||||
: "https://open.spotify.com" + path;
|
: "https://open.spotify.com" + path;
|
||||||
|
|
||||||
|
@ -75,7 +75,7 @@ function MentionWrapper({ data, UserMention, RoleMention, parse, props }: Mentio
|
|||||||
const mention = children?.[0]?.props?.children;
|
const mention = children?.[0]?.props?.children;
|
||||||
if (typeof mention !== "string") return;
|
if (typeof mention !== "string") return;
|
||||||
|
|
||||||
const id = mention.match(/<@(\d+)>/)?.[1];
|
const id = mention.match(/<@!?(\d+)>/)?.[1];
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
|
|
||||||
if (fetching.has(id))
|
if (fetching.has(id))
|
||||||
|
@ -156,6 +156,8 @@ export default definePlugin({
|
|||||||
const myChanId = SelectedChannelStore.getVoiceChannelId();
|
const myChanId = SelectedChannelStore.getVoiceChannelId();
|
||||||
const myId = UserStore.getCurrentUser().id;
|
const myId = UserStore.getCurrentUser().id;
|
||||||
|
|
||||||
|
if (ChannelStore.getChannel(myChanId!)?.type === 13 /* Stage Channel */) return;
|
||||||
|
|
||||||
for (const state of voiceStates) {
|
for (const state of voiceStates) {
|
||||||
const { userId, channelId, oldChannelId } = state;
|
const { userId, channelId, oldChannelId } = state;
|
||||||
const isMe = userId === myId;
|
const isMe = userId === myId;
|
||||||
|
@ -35,7 +35,7 @@ interface UserContextProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface GuildContextProps {
|
interface GuildContextProps {
|
||||||
guild: Guild;
|
guild?: Guild;
|
||||||
}
|
}
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
const settings = definePluginSettings({
|
||||||
@ -100,7 +100,8 @@ const UserContext: NavContextMenuPatchCallback = (children, { user, guildId }: U
|
|||||||
action={() => openImage(BannerStore.getGuildMemberAvatarURLSimple({
|
action={() => openImage(BannerStore.getGuildMemberAvatarURLSimple({
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
avatar: memberAvatar,
|
avatar: memberAvatar,
|
||||||
guildId
|
guildId,
|
||||||
|
canAnimate: true
|
||||||
}, true))}
|
}, true))}
|
||||||
icon={ImageIcon}
|
icon={ImageIcon}
|
||||||
/>
|
/>
|
||||||
@ -109,7 +110,10 @@ const UserContext: NavContextMenuPatchCallback = (children, { user, guildId }: U
|
|||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
const GuildContext: NavContextMenuPatchCallback = (children, { guild: { id, icon, banner } }: GuildContextProps) => () => {
|
const GuildContext: NavContextMenuPatchCallback = (children, { guild }: GuildContextProps) => () => {
|
||||||
|
if(!guild) return;
|
||||||
|
|
||||||
|
const { id, icon, banner } = guild;
|
||||||
if (!banner && !icon) return;
|
if (!banner && !icon) return;
|
||||||
|
|
||||||
children.splice(-1, 0, (
|
children.splice(-1, 0, (
|
||||||
@ -181,8 +185,8 @@ export default definePlugin({
|
|||||||
// style: { backgroundImage: shouldShowBanner ? "url(".concat(bannerUrl,
|
// style: { backgroundImage: shouldShowBanner ? "url(".concat(bannerUrl,
|
||||||
match: /style:\{(?=backgroundImage:(\i&&\i)\?"url\("\.concat\((\i),)/,
|
match: /style:\{(?=backgroundImage:(\i&&\i)\?"url\("\.concat\((\i),)/,
|
||||||
replace:
|
replace:
|
||||||
// onClick: () => shouldShowBanner && openImage(bannerUrl), style: { cursor: shouldShowBanner ? "pointer" : void 0,
|
// onClick: () => shouldShowBanner && ev.target.style.backgroundImage && openImage(bannerUrl), style: { cursor: shouldShowBanner ? "pointer" : void 0,
|
||||||
'onClick:()=>$1&&$self.openImage($2),style:{cursor:$1?"pointer":void 0,'
|
'onClick:ev=>$1&&ev.target.style.backgroundImage&&$self.openImage($2),style:{cursor:$1?"pointer":void 0,'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -47,9 +47,10 @@ const settings = definePluginSettings({
|
|||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "WebContextMenus",
|
name: "WebContextMenus",
|
||||||
description: "Re-adds context menus missing in the web version of Discord: Images, ChatInputBar, Links, 'Copy Link', 'Open Link', 'Copy Image', 'Save Image'",
|
description: "Re-adds context menus missing in the web version of Discord: Links & Images (Copy/Open Link/Image), Text Area (Copy, Cut, Paste, SpellCheck)",
|
||||||
authors: [Devs.Ven],
|
authors: [Devs.Ven],
|
||||||
enabledByDefault: true,
|
enabledByDefault: true,
|
||||||
|
required: IS_VENCORD_DESKTOP,
|
||||||
|
|
||||||
settings,
|
settings,
|
||||||
|
|
||||||
@ -146,36 +147,30 @@ export default definePlugin({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: ':"command-suggestions"',
|
find: 'navId:"textarea-context"',
|
||||||
|
all: true,
|
||||||
predicate: () => settings.store.addBack,
|
predicate: () => settings.store.addBack,
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
|
||||||
// desktopOnlyEntries = makeEntries(), spellcheckChildren = desktopOnlyEntries[0], languageChildren = desktopOnlyEntries[1]
|
|
||||||
match: /\i=.{0,30}text:\i,target:\i,onHeightUpdate:\i\}\),2\),(\i)=\i\[0\],(\i)=\i\[1\]/,
|
|
||||||
// set spellcheckChildren & languageChildren to empty arrays, so just in case patch 3 fails, we don't
|
|
||||||
// reference undefined variables
|
|
||||||
replace: "$1=[],$2=[]",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
// if (!IS_DESKTOP) return null;
|
// if (!IS_DESKTOP) return null;
|
||||||
match: /if\(!\i\.\i\)return null;/,
|
match: /if\(!\i\.\i\)return null;/,
|
||||||
replace: ""
|
replace: ""
|
||||||
},
|
},
|
||||||
{
|
|
||||||
// do not add menu items for entries removed in patch 1. Using a lookbehind for group 1 is slow,
|
|
||||||
// so just capture and add back
|
|
||||||
match: /("submit-button".+?)(\(0,\i\.jsx\)\(\i\.MenuGroup,\{children:\i\}\),){2}/,
|
|
||||||
replace: "$1"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
// Change calls to DiscordNative.clipboard to us instead
|
// Change calls to DiscordNative.clipboard to us instead
|
||||||
match: /\b\i\.default\.(copy|cut|paste)/g,
|
match: /\b\i\.default\.(copy|cut|paste)/g,
|
||||||
replace: "$self.$1"
|
replace: "$self.$1"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: '"add-to-dictionary"',
|
||||||
|
predicate: () => settings.store.addBack,
|
||||||
|
replacement: {
|
||||||
|
match: /var \i=\i\.text,/,
|
||||||
|
replace: "return [null,null];$&"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Maybe add spellcheck for VencordDesktop
|
|
||||||
],
|
],
|
||||||
|
|
||||||
async copyImage(url: string) {
|
async copyImage(url: string) {
|
||||||
|
@ -30,4 +30,6 @@ export const enum IpcEvents {
|
|||||||
UPDATE = "VencordUpdate",
|
UPDATE = "VencordUpdate",
|
||||||
BUILD = "VencordBuild",
|
BUILD = "VencordBuild",
|
||||||
OPEN_MONACO_EDITOR = "VencordOpenMonacoEditor",
|
OPEN_MONACO_EDITOR = "VencordOpenMonacoEditor",
|
||||||
|
|
||||||
|
OPEN_IN_APP__RESOLVE_REDIRECT = "VencordOIAResolveRedirect",
|
||||||
}
|
}
|
||||||
|
@ -77,6 +77,17 @@ export const Toasts = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show a simple toast. If you need more options, use Toasts.show manually
|
||||||
|
*/
|
||||||
|
export function showToast(message: string, type = ToastType.MESSAGE) {
|
||||||
|
Toasts.show({
|
||||||
|
id: Toasts.genId(),
|
||||||
|
message,
|
||||||
|
type
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export const UserUtils = {
|
export const UserUtils = {
|
||||||
fetchUser: findByCodeLazy(".USER(", "getUser") as (id: string) => Promise<User>,
|
fetchUser: findByCodeLazy(".USER(", "getUser") as (id: string) => Promise<User>,
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user