Merge branch 'dev'
This commit is contained in:
commit
e418ed4f41
26 changed files with 1842 additions and 59 deletions
|
|
@ -34,6 +34,7 @@ import { debounce } from "@shared/debounce";
|
|||
import { IS_WINDOWS } from "@utils/constants";
|
||||
import { createAndAppendStyle } from "@utils/css";
|
||||
import { StartAt } from "@utils/types";
|
||||
import { SettingsRouter } from "@webpack/common";
|
||||
|
||||
import { get as dsGet } from "./api/DataStore";
|
||||
import { popNotice, showNotice } from "./api/Notices";
|
||||
|
|
@ -45,7 +46,6 @@ import { localStorage } from "./utils/localStorage";
|
|||
import { relaunch } from "./utils/native";
|
||||
import { checkForUpdates, isOutdated as getIsOutdated, update, UpdateLogger } from "./utils/updater";
|
||||
import { onceReady } from "./webpack";
|
||||
import { openUserSettingsPanel } from "./webpack/common";
|
||||
import { patches } from "./webpack/patchWebpack";
|
||||
|
||||
if (IS_REPORTER) {
|
||||
|
|
@ -66,7 +66,7 @@ async function syncSettings() {
|
|||
title: "Cloud Settings",
|
||||
body: "Cloud sync was disabled because this account isn't connected to the cloud App. You can enable it again by connecting this account in Cloud Settings. (note: it will store your preferences separately)",
|
||||
color: "var(--yellow-360)",
|
||||
onClick: () => openUserSettingsPanel("equicord_cloud")
|
||||
onClick: () => SettingsRouter.openUserSettings("equicord_cloud_panel")
|
||||
});
|
||||
// Disable cloud sync globally
|
||||
Settings.cloud.authenticated = false;
|
||||
|
|
@ -85,7 +85,7 @@ async function syncSettings() {
|
|||
body: "We've noticed you have cloud integrations enabled in another client! Due to limitations, you will " +
|
||||
"need to re-authenticate to continue using them. Click here to go to the settings page to do so!",
|
||||
color: "var(--yellow-360)",
|
||||
onClick: () => openUserSettingsPanel("equicord_cloud")
|
||||
onClick: () => SettingsRouter.openUserSettings("equicord_cloud_panel")
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ interface IMessageCreate {
|
|||
message: Message;
|
||||
}
|
||||
|
||||
const SILENT_PING_FLAG = 1 << 12;
|
||||
const SILENT_PING_FLAG = 1 << 12;
|
||||
|
||||
function Icon(enabled?: boolean): JSX.Element {
|
||||
return <svg
|
||||
|
|
@ -168,7 +168,7 @@ export default definePlugin({
|
|||
if (message.state === "SENDING" || message.content === "" || message.author.id === currentUser.id || (channelId === currentChannelId && WindowStore.isFocused()) || userStatus !== settings.store.statusToUse) {
|
||||
return;
|
||||
}
|
||||
if (settings.store.respectSilentPings && (message.flags & SILENT_PING_FLAG)) {return;}
|
||||
if (settings.store.respectSilentPings && (message.flags & SILENT_PING_FLAG)) { return; }
|
||||
const mentioned = MessageStore.getMessage(channelId, message.id)?.mentioned;
|
||||
if ((settings.store.guilds.split(", ").includes(guildId) || settings.store.channels.split(", ").includes(channelId)) && mentioned) {
|
||||
await showNotification(message, guildId);
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ export let colors: Record<string, string> = {};
|
|||
})();
|
||||
|
||||
// needed for color picker to be available without opening settings (ty pindms!!)
|
||||
const requireSettingsMenu = extractAndLoadChunksLazy(['"AppSkeleton"'], /createPromise:.{0,20}(\i\.\i\("?.+?"?\).*?).then\(\i\.bind\(\i,"?(.+?)"?\)\).{0,50}\}\),(?=.{0,15}createPromise.*?\}\),)/);
|
||||
const requireSettingsMenu = extractAndLoadChunksLazy(['type:"USER_SETTINGS_MODAL_OPEN"']);
|
||||
const ColorIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
|
|
|
|||
237
src/equicordplugins/dragify/ghost.tsx
Normal file
237
src/equicordplugins/dragify/ghost.tsx
Normal file
|
|
@ -0,0 +1,237 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { classNameFactory } from "@utils/css";
|
||||
import { classes } from "@utils/misc";
|
||||
import { findComponentByCodeLazy } from "@webpack";
|
||||
import { createRoot, React, useEffect, useState, useStateFromStores, VoiceStateStore } from "@webpack/common";
|
||||
|
||||
export type GhostState = {
|
||||
visible: boolean;
|
||||
x: number;
|
||||
y: number;
|
||||
kind: "user" | "channel" | "guild";
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
iconUrl?: string;
|
||||
symbol?: string;
|
||||
badge?: string;
|
||||
entityId?: string;
|
||||
exiting: boolean;
|
||||
};
|
||||
|
||||
const ScreenArrowIcon = findComponentByCodeLazy("3V5Zm16") as React.ComponentType<{
|
||||
className?: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
size?: number;
|
||||
color?: string;
|
||||
colorClass?: string;
|
||||
}>;
|
||||
|
||||
const cl = classNameFactory("vc-dragify-");
|
||||
|
||||
let ghostRoot: ReturnType<typeof createRoot> | null = null;
|
||||
let ghostMountNode: HTMLDivElement | null = null;
|
||||
let ghostRaf: number | null = null;
|
||||
let ghostPendingPos: { x: number; y: number; } | null = null;
|
||||
let ghostHideTimer: number | null = null;
|
||||
|
||||
let ghostState: GhostState = {
|
||||
visible: false,
|
||||
x: 0,
|
||||
y: 0,
|
||||
kind: "channel",
|
||||
title: "",
|
||||
exiting: false
|
||||
};
|
||||
|
||||
const ghostListeners = new Set<() => void>();
|
||||
|
||||
function notifyGhost() {
|
||||
ghostListeners.forEach(listener => listener());
|
||||
}
|
||||
|
||||
function setGhostState(next: Partial<GhostState>) {
|
||||
ghostState = { ...ghostState, ...next };
|
||||
notifyGhost();
|
||||
}
|
||||
|
||||
export function isGhostVisible(): boolean {
|
||||
return ghostState.visible;
|
||||
}
|
||||
|
||||
export function scheduleGhostPosition(x: number, y: number) {
|
||||
ghostPendingPos = { x, y };
|
||||
if (ghostRaf !== null) return;
|
||||
setGhostState({ x, y });
|
||||
ghostRaf = requestAnimationFrame(() => {
|
||||
if (ghostPendingPos) setGhostState({ x: ghostPendingPos.x, y: ghostPendingPos.y });
|
||||
ghostPendingPos = null;
|
||||
ghostRaf = null;
|
||||
});
|
||||
}
|
||||
|
||||
export function hideGhost() {
|
||||
if (!ghostState.visible) return;
|
||||
if (ghostHideTimer !== null) {
|
||||
clearTimeout(ghostHideTimer);
|
||||
ghostHideTimer = null;
|
||||
}
|
||||
setGhostState({ exiting: true });
|
||||
ghostHideTimer = window.setTimeout(() => {
|
||||
ghostHideTimer = null;
|
||||
setGhostState({ visible: false, exiting: false });
|
||||
}, 200);
|
||||
}
|
||||
|
||||
export function showGhost(next: Omit<GhostState, "visible" | "x" | "y">, position?: { x: number; y: number; }) {
|
||||
if (ghostHideTimer !== null) {
|
||||
clearTimeout(ghostHideTimer);
|
||||
ghostHideTimer = null;
|
||||
}
|
||||
if (position) scheduleGhostPosition(position.x, position.y);
|
||||
setGhostState({ ...next, visible: true, exiting: false });
|
||||
}
|
||||
|
||||
export function mountGhost() {
|
||||
if (typeof document === "undefined") return;
|
||||
const { body } = document;
|
||||
if (!body) return;
|
||||
|
||||
if (ghostRoot) {
|
||||
if (ghostMountNode && !ghostMountNode.isConnected) body.appendChild(ghostMountNode);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ghostMountNode) ghostMountNode = document.createElement("div");
|
||||
if (!ghostMountNode.isConnected) body.appendChild(ghostMountNode);
|
||||
|
||||
ghostRoot = createRoot(ghostMountNode);
|
||||
ghostRoot.render(
|
||||
<div className={cl("ghost-container")}>
|
||||
<ErrorBoundary noop>
|
||||
<DragGhost />
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function unmountGhost() {
|
||||
ghostRoot?.unmount();
|
||||
ghostRoot = null;
|
||||
|
||||
ghostMountNode?.remove();
|
||||
ghostMountNode = null;
|
||||
|
||||
if (ghostRaf !== null) {
|
||||
cancelAnimationFrame(ghostRaf);
|
||||
ghostRaf = null;
|
||||
}
|
||||
ghostPendingPos = null;
|
||||
|
||||
if (ghostHideTimer !== null) {
|
||||
clearTimeout(ghostHideTimer);
|
||||
ghostHideTimer = null;
|
||||
}
|
||||
setGhostState({ visible: false, exiting: false });
|
||||
}
|
||||
|
||||
const DragGhost = () => {
|
||||
const [state, setState] = useState(ghostState);
|
||||
useEffect(() => {
|
||||
const listener = () => setState({ ...ghostState });
|
||||
ghostListeners.add(listener);
|
||||
return () => {
|
||||
ghostListeners.delete(listener);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const voiceState = useStateFromStores(
|
||||
[VoiceStateStore],
|
||||
() => (state.kind === "user" && state.entityId
|
||||
? VoiceStateStore.getVoiceStateForUser(state.entityId)
|
||||
: null)
|
||||
);
|
||||
const inVoice = voiceState?.channelId;
|
||||
const isMuted = voiceState && (voiceState.selfMute || voiceState.mute || voiceState.selfDeaf || voiceState.deaf);
|
||||
const isStreaming = voiceState?.selfStream;
|
||||
|
||||
if (!state.visible) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classes(cl("ghost"), state.exiting ? cl("ghost-exit") : "")}
|
||||
style={{ transform: `translate3d(${state.x}px, ${state.y}px, 0)` }}
|
||||
>
|
||||
<div className={cl("card")}>
|
||||
<div className={cl("icon")}>
|
||||
{state.iconUrl
|
||||
? <img className={cl("icon-image")} src={state.iconUrl} alt="" />
|
||||
: <span className={cl("icon-text")}>{state.symbol ?? "#"}</span>
|
||||
}
|
||||
</div>
|
||||
<div className={cl("body")}>
|
||||
<div className={cl("title-row")}>
|
||||
<div className={cl("title")}>{state.title}</div>
|
||||
{state.kind === "user" && inVoice
|
||||
? (isMuted
|
||||
? <VoiceMutedIcon className={classes(cl("voice-icon"), cl("voice-icon-muted"))} />
|
||||
: <VoiceStateIcon className={cl("voice-icon")} />)
|
||||
: null}
|
||||
{state.kind === "user" && inVoice && isStreaming
|
||||
? <ScreenArrowIcon
|
||||
className={classes(cl("voice-icon"), cl("voice-icon-stream"))}
|
||||
size={14}
|
||||
width={14}
|
||||
height={14}
|
||||
/>
|
||||
: null}
|
||||
</div>
|
||||
{state.subtitle && <div className={cl("subtitle")}>{state.subtitle}</div>}
|
||||
</div>
|
||||
<div className={cl("badge")}>{state.badge ?? state.kind}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
function VoiceStateIcon({ className, size = 14 }: { className?: string; size?: number; }) {
|
||||
return (
|
||||
<svg
|
||||
className={className}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={size}
|
||||
height={size}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M7 2a1 1 0 0 0-1 1v18a1 1 0 1 0 2 0V3a1 1 0 0 0-1-1ZM11 6a1 1 0 1 1 2 0v12a1 1 0 1 1-2 0V6ZM1 8a1 1 0 0 1 2 0v8a1 1 0 1 1-2 0V8ZM16 5a1 1 0 1 1 2 0v14a1 1 0 1 1-2 0V5ZM22 8a1 1 0 0 0-1 1v6a1 1 0 1 0 2 0V9a1 1 0 0 0-1-1Z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function VoiceMutedIcon({ className, size = 14 }: { className?: string; size?: number; }) {
|
||||
return (
|
||||
<svg
|
||||
className={className}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={size}
|
||||
height={size}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M22.7 2.7a1 1 0 0 0-1.4-1.4l-20 20a1 1 0 1 0 1.4 1.4l20-20ZM6.85 13.15a.5.5 0 0 1-.85-.36V3a1 1 0 0 1 2 0v8.8a.5.5 0 0 1-.15.35l-1 1ZM11 17.2v.8a1 1 0 1 0 2 0v-1.8a.5.5 0 0 0-.85-.35l-1 1a.5.5 0 0 0-.15.36ZM11 7.8V6a1 1 0 1 1 2 0v.8a.5.5 0 0 1-.15.35l-1 1a.5.5 0 0 1-.85-.36ZM17.15 10.85a.5.5 0 0 1 .85.36V19a1 1 0 1 1-2 0v-6.8a.5.5 0 0 1 .15-.35l1-1ZM2 7a1 1 0 0 0-1 1v8a1 1 0 1 0 2 0V8a1 1 0 0 0-1-1ZM21 9a1 1 0 1 1 2 0v6a1 1 0 1 1-2 0V9Z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
1225
src/equicordplugins/dragify/index.tsx
Normal file
1225
src/equicordplugins/dragify/index.tsx
Normal file
File diff suppressed because it is too large
Load diff
149
src/equicordplugins/dragify/style.css
Normal file
149
src/equicordplugins/dragify/style.css
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
.vc-dragify-ghost-container {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.vc-dragify-ghost {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
pointer-events: none;
|
||||
color: var(--text-normal, #fff);
|
||||
opacity: 1;
|
||||
animation: vc-dragify-fade-in 200ms ease-out forwards;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.vc-dragify-ghost-exit {
|
||||
animation: vc-dragify-fade-out 200ms ease-in forwards;
|
||||
}
|
||||
|
||||
.vc-dragify-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 6px 10px;
|
||||
min-width: 220px;
|
||||
max-width: 320px;
|
||||
border-radius: 6px;
|
||||
background: rgb(0 0 0 / 70%);
|
||||
border: 1px solid rgb(255 255 255 / 10%);
|
||||
box-shadow: 0 12px 40px rgb(0 0 0 / 35%);
|
||||
backdrop-filter: blur(12px);
|
||||
transform: translateZ(0);
|
||||
color: var(--text-default, #fff);
|
||||
font-family: var(--font-code);
|
||||
line-height: 1.4;
|
||||
animation: none;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.vc-dragify-icon {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 6px;
|
||||
background: rgb(255 255 255 / 10%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
color: var(--text-default, #fff);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.vc-dragify-icon-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.vc-dragify-icon-text {
|
||||
font-size: 13px;
|
||||
color: var(--text-default, #fff);
|
||||
}
|
||||
|
||||
.vc-dragify-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.vc-dragify-title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.vc-dragify-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--text-default, #fff);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.vc-dragify-voice-icon {
|
||||
flex-shrink: 0;
|
||||
color: var(--interactive-normal, #b9bbbe);
|
||||
}
|
||||
|
||||
.vc-dragify-voice-icon-muted {
|
||||
color: var(--status-danger, #ed4245);
|
||||
}
|
||||
|
||||
.vc-dragify-voice-icon-stream {
|
||||
color: var(--status-positive, #3ba55d);
|
||||
}
|
||||
|
||||
.vc-dragify-subtitle {
|
||||
font-size: 11px;
|
||||
color: var(--text-muted);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.vc-dragify-badge {
|
||||
font-size: 10px;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: var(--text-muted);
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
@keyframes vc-dragify-fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes vc-dragify-fade-out {
|
||||
from {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.vc-dragify-ghost {
|
||||
animation: none;
|
||||
}
|
||||
|
||||
.vc-dragify-ghost-exit {
|
||||
animation: none;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
182
src/equicordplugins/dragify/utils.ts
Normal file
182
src/equicordplugins/dragify/utils.ts
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import type { Channel, Guild, User } from "@vencord/discord-types";
|
||||
|
||||
const userMentionRegex = /<@!?(\d{17,20})>/;
|
||||
const userProfileUrlRegex = /discord(?:(?:app)?\.com|:\/\/-?)\/users\/(\d{17,20})/;
|
||||
const userAvatarRegex = /cdn\.discordapp\.com\/(?:avatars|users)\/(\d{17,20})\//;
|
||||
const guildUserAvatarRegex = /cdn\.discordapp\.com\/guilds\/\d{17,20}\/users\/(\d{17,20})\/avatars\//;
|
||||
const channelMentionRegex = /<#(\d{17,20})>/;
|
||||
const channelUrlRegex = /discord(?:(?:app)?\.com|:\/\/-?)\/channels\/(?:(@me)|(\d{17,20}))\/(\d{17,20})/;
|
||||
const channelPathRegex = /\/channels\/(@me|\d{17,20})\/(\d{17,20})/;
|
||||
const guildIconRegex = /cdn\.discordapp\.com\/icons\/(\d{17,20})\//;
|
||||
|
||||
export type DropEntity =
|
||||
| { kind: "user"; id: string; }
|
||||
| { kind: "channel"; id: string; guildId?: string; }
|
||||
| { kind: "guild"; id: string; };
|
||||
|
||||
interface DragifyPayload {
|
||||
id?: string;
|
||||
userId?: string;
|
||||
channelId?: string;
|
||||
guildId?: string;
|
||||
type?: string;
|
||||
kind?: string;
|
||||
itemType?: string;
|
||||
}
|
||||
|
||||
type StoreSet = {
|
||||
ChannelStore: { getChannel(id: string): Channel | null | undefined; };
|
||||
GuildStore: { getGuild(id: string): Guild | null | undefined; };
|
||||
UserStore: { getUser(id: string): User | null | undefined; };
|
||||
};
|
||||
|
||||
export function tryParseJson<T = Record<string, unknown>>(value: string): T | null {
|
||||
if (!value || value.length < 2 || (value[0] !== "{" && value[0] !== "[")) return null;
|
||||
try {
|
||||
const parsed = JSON.parse(value);
|
||||
return typeof parsed === "object" && parsed ? parsed as T : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function extractSnowflakeFromString(value: string): string | null {
|
||||
const match = value.match(/\d{17,20}/);
|
||||
return match?.[0] ?? null;
|
||||
}
|
||||
|
||||
export function extractSnowflakes(values: string[]): string[] {
|
||||
const ids = new Set<string>();
|
||||
const regex = /\d{17,20}/g;
|
||||
for (const value of values) {
|
||||
const matches = value.match(regex);
|
||||
if (matches) matches.forEach(id => ids.add(id));
|
||||
}
|
||||
return Array.from(ids);
|
||||
}
|
||||
|
||||
export function extractStrings(dataTransfer: DataTransfer): string[] {
|
||||
const collected = new Set<string>();
|
||||
const add = (v?: string) => v && collected.add(v);
|
||||
add(dataTransfer.getData("text/plain"));
|
||||
add(dataTransfer.getData("text/uri-list"));
|
||||
add(dataTransfer.getData("text/html"));
|
||||
add(dataTransfer.getData("text/x-moz-url"));
|
||||
add(dataTransfer.getData("application/json"));
|
||||
for (const type of dataTransfer.types ?? []) add(dataTransfer.getData(type));
|
||||
|
||||
const split: string[] = [];
|
||||
for (const v of collected) {
|
||||
split.push(v);
|
||||
if (v.includes("\n")) split.push(...v.split(/\s+/).filter(Boolean));
|
||||
}
|
||||
return Array.from(new Set(split));
|
||||
}
|
||||
|
||||
export async function collectPayloadStrings(dataTransfer: DataTransfer): Promise<string[]> {
|
||||
const sync = extractStrings(dataTransfer);
|
||||
const asyncValues: string[] = [];
|
||||
const itemPromises = Array.from(dataTransfer.items ?? [])
|
||||
.filter(item => item.kind === "string")
|
||||
.map(item => new Promise<void>(resolve => item.getAsString(val => {
|
||||
if (val) asyncValues.push(val);
|
||||
resolve();
|
||||
})));
|
||||
await Promise.all(itemPromises);
|
||||
return Array.from(new Set([...sync, ...asyncValues]));
|
||||
}
|
||||
|
||||
export function parseFromStrings(payloads: string[], stores: StoreSet): DropEntity | null {
|
||||
if (payloads.length === 0) return null;
|
||||
|
||||
for (const value of payloads) {
|
||||
const parsed = tryParseJson<DragifyPayload>(value);
|
||||
if (parsed) {
|
||||
const id = parsed.id ?? parsed.userId ?? parsed.channelId ?? parsed.guildId;
|
||||
const type = (parsed.type ?? parsed.kind ?? parsed.itemType ?? "").toLowerCase();
|
||||
if (id) {
|
||||
if (type.includes("user")) return { kind: "user", id };
|
||||
if (type.includes("channel")) return { kind: "channel", id, guildId: parsed.guildId };
|
||||
if (type.includes("guild") || type.includes("server")) return { kind: "guild", id };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const value of payloads) {
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed) continue;
|
||||
const userFromMention = userMentionRegex.exec(trimmed);
|
||||
if (userFromMention) return { kind: "user", id: userFromMention[1] };
|
||||
const userFromProfile = userProfileUrlRegex.exec(trimmed);
|
||||
if (userFromProfile) return { kind: "user", id: userFromProfile[1] };
|
||||
const guildUserAvatar = guildUserAvatarRegex.exec(trimmed);
|
||||
if (guildUserAvatar) return { kind: "user", id: guildUserAvatar[1] };
|
||||
const userFromAvatar = userAvatarRegex.exec(trimmed);
|
||||
if (userFromAvatar) return { kind: "user", id: userFromAvatar[1] };
|
||||
}
|
||||
|
||||
for (const value of payloads) {
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed) continue;
|
||||
const channelFromMention = channelMentionRegex.exec(trimmed);
|
||||
if (channelFromMention) return { kind: "channel", id: channelFromMention[1] };
|
||||
const channelFromUrl = channelUrlRegex.exec(trimmed);
|
||||
if (channelFromUrl) {
|
||||
const guildId = channelFromUrl[1] === "@me" ? "@me" : channelFromUrl[2];
|
||||
return { kind: "channel", id: channelFromUrl[3], guildId: guildId ?? undefined };
|
||||
}
|
||||
}
|
||||
|
||||
for (const value of payloads) {
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed) continue;
|
||||
const guildFromIcon = guildIconRegex.exec(trimmed);
|
||||
if (guildFromIcon) return { kind: "guild", id: guildFromIcon[1] };
|
||||
}
|
||||
|
||||
const candidates = extractSnowflakes(payloads);
|
||||
for (const candidate of candidates) {
|
||||
if (stores.ChannelStore.getChannel(candidate)) return { kind: "channel", id: candidate };
|
||||
if (stores.GuildStore.getGuild(candidate)) return { kind: "guild", id: candidate };
|
||||
if (stores.UserStore.getUser(candidate)) return { kind: "user", id: candidate };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function extractChannelPath(value: string): { guildId?: string; channelId?: string; } | null {
|
||||
const channelPath = channelPathRegex.exec(value);
|
||||
if (!channelPath) return null;
|
||||
return {
|
||||
guildId: channelPath[1] === "@me" ? "@me" : channelPath[1],
|
||||
channelId: channelPath[2]
|
||||
};
|
||||
}
|
||||
|
||||
export function extractChannelFromUrl(value: string): { guildId?: string; channelId?: string; } | null {
|
||||
const channelFromUrl = channelUrlRegex.exec(value);
|
||||
if (!channelFromUrl) return null;
|
||||
const guildId = channelFromUrl[1] ?? channelFromUrl[2];
|
||||
return {
|
||||
guildId: guildId === "@me" ? "@me" : guildId,
|
||||
channelId: channelFromUrl[3]
|
||||
};
|
||||
}
|
||||
|
||||
export function extractUserFromAvatar(value: string): string | null {
|
||||
const guildUserAvatar = guildUserAvatarRegex.exec(value);
|
||||
if (guildUserAvatar?.[1]) return guildUserAvatar[1];
|
||||
const userAvatar = userAvatarRegex.exec(value);
|
||||
if (userAvatar?.[1]) return userAvatar[1];
|
||||
return null;
|
||||
}
|
||||
|
||||
export function extractUserFromProfile(value: string): string | null {
|
||||
const match = userProfileUrlRegex.exec(value);
|
||||
return match?.[1] ?? null;
|
||||
}
|
||||
|
|
@ -30,6 +30,7 @@ function GroupDmsIcon({ channel }: { channel: Channel; }) {
|
|||
aria-label={channel?.name || "Unnamed Group"}
|
||||
/> : <GroupDmsRecipientsIcon
|
||||
recipients={channel?.recipients ?? []}
|
||||
channel={channel}
|
||||
size="SIZE_40"
|
||||
isTyping={null}
|
||||
/>;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/*
|
||||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
|
@ -94,7 +94,7 @@ const handleMessage = ((channelId, message) => {
|
|||
const wordMatch: WordMatch | null = chooseRandomWord(msg);
|
||||
if (wordMatch === null) return;
|
||||
|
||||
const word = wordMatch.word;
|
||||
const { word } = wordMatch;
|
||||
const wordUpper = word.toUpperCase();
|
||||
const isUpper = word === wordUpper;
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export default definePlugin({
|
|||
{
|
||||
find: "#{intl::COPY_MESSAGE_LINK}",
|
||||
replacement: {
|
||||
match: /(?<=(?:.concat\(|\/\/\$\{))location\.host/,
|
||||
match: /(?<=\/\/\$\{)location\.host/,
|
||||
replace: "$self.normalizeHost(location.host)",
|
||||
},
|
||||
},
|
||||
|
|
@ -17,7 +17,7 @@ import RemixModal from "./RemixModal";
|
|||
import css from "./styles.css?managed";
|
||||
|
||||
const requireCreateStickerModal = extractAndLoadChunksLazy([".CREATE_STICKER_MODAL,", "isDisplayingIndividualStickers"]);
|
||||
const requireSettingsMenu = extractAndLoadChunksLazy(['"AppSkeleton"'], /createPromise:.{0,20}(\i\.\i\("?.+?"?\).*?).then\(\i\.bind\(\i,"?(.+?)"?\)\).{0,50}\}\),(?=.{0,15}createPromise.*?\}\),)/);
|
||||
const requireSettingsMenu = extractAndLoadChunksLazy(['type:"USER_SETTINGS_MODAL_OPEN"']);
|
||||
|
||||
const PendingReplyStore = findStoreLazy("PendingReplyStore");
|
||||
const validMediaTypes = ["image/png", "image/jpeg", "image/jpg", "image/webp"];
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ const cl = classNameFactory("vc-userpfp-");
|
|||
const DONO_URL = "https://ko-fi.com/coolesding";
|
||||
const INVITE_LINK = "userpfp-1129784704267210844";
|
||||
|
||||
export const requireSettingsMenu = extractAndLoadChunksLazy(['"AppSkeleton"'], /createPromise:.{0,20}(\i\.\i\("?.+?"?\).*?).then\(\i\.bind\(\i,"?(.+?)"?\)\).{0,50}\}\),(?=.{0,15}createPromise.*?\}\),)/);
|
||||
export const requireSettingsModal = extractAndLoadChunksLazy(['type:"USER_SETTINGS_MODAL_OPEN"']);
|
||||
export const KEY_DATASTORE = "vencord-custom-avatars";
|
||||
export const data = { avatars: {} as Record<string, string> };
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ export default definePlugin({
|
|||
patches: [
|
||||
{
|
||||
// TODO: bundler compat
|
||||
find: /\.renderLinkComponent[)?]\?/,
|
||||
find: ".renderLinkComponent",
|
||||
replacement: {
|
||||
// widthAndHeightPassed = w != null && w !== 0 && h == null || h === 0
|
||||
match: /(?<=\i=)(null!=\i&&0!==\i)&&(null!=\i&&0!==\i)/,
|
||||
|
|
|
|||
|
|
@ -100,14 +100,9 @@ export default definePlugin({
|
|||
{
|
||||
find: "this.renderArtisanalHack()",
|
||||
replacement: [
|
||||
{ // Fade in on layer
|
||||
// class Layer {
|
||||
// static contextType = ...;
|
||||
// ...
|
||||
// }
|
||||
// class OtherClass {
|
||||
match: /(static contextType=\i\.\i;.+?})(?=class)(?<=class (\i).+?)/,
|
||||
replace: (_, before, Layer) => `${before};${Layer}=$self.Layer;`,
|
||||
{
|
||||
match: /class (\i)(?= extends \i\.PureComponent.+?static contextType=.+?jsx\)\(\1,\{mode:)/,
|
||||
replace: "var $1=$self.Layer;class VencordPatchedOldFadeLayer",
|
||||
predicate: () => settings.store.disableFade
|
||||
},
|
||||
{ // Lazy-load contents
|
||||
|
|
|
|||
|
|
@ -164,12 +164,10 @@ export default definePlugin({
|
|||
},
|
||||
{
|
||||
find: "RPCServer:WSS",
|
||||
replacement: [
|
||||
{
|
||||
match: /\i\.error\(`Error: \$\{(\i)\.message\}/,
|
||||
replace: '!$1.message.includes("EADDRINUSE")&&$&'
|
||||
}
|
||||
]
|
||||
replacement: {
|
||||
match: /\i\.error\(`Error: \$\{(\i)\.message\}/,
|
||||
replace: '!$1.message.includes("EADDRINUSE")&&$&'
|
||||
}
|
||||
},
|
||||
{
|
||||
find: "Tried getting Dispatch instance before instantiated",
|
||||
|
|
|
|||
|
|
@ -103,6 +103,10 @@ export default definePlugin({
|
|||
].map(find => ({
|
||||
find,
|
||||
replacement: [
|
||||
{
|
||||
match: /(?<=userValue:)((\i)(?:.{0,10})?\.avatarDecoration)/,
|
||||
replace: "$self.useUserDecorAvatarDecoration($2)??$1"
|
||||
},
|
||||
{
|
||||
match: /(?<=userValue:)((\i)(?:.{0,10})?\.avatarDecoration)/,
|
||||
replace: "$self.useUserDecorAvatarDecoration($2)??$1"
|
||||
|
|
|
|||
|
|
@ -25,18 +25,20 @@ export default definePlugin({
|
|||
authors: [Devs.Nuckyz],
|
||||
patches: [
|
||||
{
|
||||
find: "#{intl::BOT_CALL_IDLE_DISCONNECT_2}",
|
||||
find: "this.idleTimeout.start(",
|
||||
replacement: {
|
||||
match: /,?(?=\i\(this,"idleTimeout",new \i\.\i\))/,
|
||||
replace: ";return;"
|
||||
match: /this\.idleTimeout\.(start|stop)/g,
|
||||
replace: "$self.noop"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: "handleIdleUpdate(){",
|
||||
replacement: {
|
||||
match: /(?<=_initialize\(\){)/,
|
||||
replace: "return;"
|
||||
match: "handleIdleUpdate(){",
|
||||
replace: "handleIdleUpdate(){return;"
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
|
||||
noop() { }
|
||||
});
|
||||
|
|
|
|||
|
|
@ -52,12 +52,10 @@ export default definePlugin({
|
|||
patches: [{
|
||||
// HEY THOR UPDATE SHOW ME YOUR NAME IF THIS SHIT CHANGES TY :)
|
||||
find: ".USER_MENTION)",
|
||||
replacement: [
|
||||
{
|
||||
match: /children:`@\$\{(\i\?\?\i)\}`(?<=\.useName\((\i)\).+?)/,
|
||||
replace: "children:$self.renderUsername({username:$1,user:$2,showMeYourNameMention:typeof showMeYourNameMention!=='undefined'?showMeYourNameMention:undefined})"
|
||||
}
|
||||
]
|
||||
replacement: {
|
||||
match: /children:`@\$\{(\i\?\?\i)\}`(?<=\.useName\((\i)\).+?)/,
|
||||
replace: "children:$self.renderUsername({username:$1,user:$2,showMeYourNameMention:typeof showMeYourNameMention!=='undefined'?showMeYourNameMention:undefined})"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: ".ROLE_MENTION)",
|
||||
|
|
|
|||
|
|
@ -26,8 +26,7 @@ interface ColorPickerWithSwatchesProps {
|
|||
|
||||
const ColorPickerWithSwatches = findComponentByCodeLazy<ColorPickerWithSwatchesProps>('id:"color-picker"');
|
||||
|
||||
// 701663 this is terrible imo
|
||||
export const requireSettingsMenu = extractAndLoadChunksLazy(['"AppSkeleton"'], /createPromise:.{0,20}(\i\.\i\("?.+?"?\).*?).then\(\i\.bind\(\i,"?(.+?)"?\)\).{0,50}\}\),(?=.{0,15}createPromise.*?\}\),)/);
|
||||
export const requireSettingsModal = extractAndLoadChunksLazy(['type:"USER_SETTINGS_MODAL_OPEN"']);
|
||||
|
||||
const cl = classNameFactory("vc-pindms-modal-");
|
||||
|
||||
|
|
@ -122,6 +121,6 @@ export function NewCategoryModal({ categoryId, modalProps, initialChannelId }: P
|
|||
|
||||
export const openCategoryModal = (categoryId: string | null, channelId: string | null) =>
|
||||
openModalLazy(async () => {
|
||||
await requireSettingsMenu();
|
||||
await requireSettingsModal();
|
||||
return modalProps => <NewCategoryModal categoryId={categoryId} modalProps={modalProps} initialChannelId={channelId} />;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -16,11 +16,6 @@ export interface Category {
|
|||
collapsed?: boolean;
|
||||
}
|
||||
|
||||
const CATEGORY_BASE_KEY = "PinDMsCategories-";
|
||||
const CATEGORY_MIGRATED_PINDMS_KEY = "PinDMsMigratedPinDMs";
|
||||
const CATEGORY_MIGRATED_KEY = "PinDMsMigratedOldCategories";
|
||||
const OLD_CATEGORY_KEY = "BetterPinDMsCategories-";
|
||||
|
||||
let forceUpdateDms: (() => void) | undefined = undefined;
|
||||
export let currentUserCategories: Category[] = [];
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import { findCssClassesLazy, findStoreLazy } from "@webpack";
|
|||
import { Clickable, ContextMenuApi, FluxDispatcher, Menu, React } from "@webpack/common";
|
||||
|
||||
import { contextMenus } from "./components/contextMenu";
|
||||
import { openCategoryModal, requireSettingsMenu } from "./components/CreateCategoryModal";
|
||||
import { openCategoryModal, requireSettingsModal } from "./components/CreateCategoryModal";
|
||||
import { DEFAULT_CHUNK_SIZE } from "./constants";
|
||||
import { canMoveCategory, canMoveCategoryInDirection, Category, categoryLen, collapseCategory, getAllUncollapsedChannels, getCategoryByIndex, getSections, init, isPinned, moveCategory, removeCategory, usePinnedDms } from "./data";
|
||||
|
||||
|
|
@ -173,7 +173,7 @@ export default definePlugin({
|
|||
categoryLen,
|
||||
getSections,
|
||||
getAllUncollapsedChannels,
|
||||
requireSettingsMenu,
|
||||
requireSettingsMenu: requireSettingsModal,
|
||||
|
||||
makeProps(instance, { sections }: { sections: number[]; }) {
|
||||
this._instance = instance;
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ export interface Session {
|
|||
const SessionsStore = findStoreLazy("SessionsStore") as {
|
||||
getSessions(): Record<string, Session>;
|
||||
};
|
||||
const { useStatusFillColor } = mapMangledModuleLazy("translate(${.5625*", {
|
||||
const { useStatusFillColor } = mapMangledModuleLazy([".5625*", "translate"], {
|
||||
useStatusFillColor: filters.byCode(".hex")
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ export default definePlugin({
|
|||
},
|
||||
},
|
||||
{
|
||||
find: /\.INVITES_DISABLED\)(?:\))?\|\|/,
|
||||
find: "INVITES_DISABLED)||",
|
||||
predicate: () => settings.store.showInvitesPaused,
|
||||
replacement: {
|
||||
match: /\i\.\i\.can\(\i\.\i.MANAGE_GUILD,\i\)/,
|
||||
|
|
|
|||
|
|
@ -131,7 +131,7 @@ export default definePlugin({
|
|||
replace: "$self.renderTypingUsers({ users: arguments[0]?.typingUserObjects, guildId: arguments[0]?.channel?.guild_id, children: $& })"
|
||||
},
|
||||
{
|
||||
match: /(?<=function \i\(\i\)\{)(?=[^}]+?\{channel:\i,isThreadCreation:\i=!1,...\i\})/,
|
||||
match: /(?<=function \i\(\i\)\{)(?=[^}]+?\{channel:\i,isThreadCreation:\i=!1,\.\.\.\i\})/,
|
||||
replace: "let typingUserObjects = $self.useTypingUsers(arguments[0]?.channel);"
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -205,12 +205,10 @@ export default definePlugin({
|
|||
// Banners
|
||||
{
|
||||
find: 'backgroundColor:"COMPLETE"',
|
||||
replacement: [
|
||||
{
|
||||
match: /(overflow:"visible",.{0,125}?!1\),)style:{(?=.+?backgroundImage:null!=(\i)\?`url\(\$\{\2\}\))/,
|
||||
replace: (_, rest, bannerSrc) => `${rest}onClick:()=>${bannerSrc}!=null&&$self.openBanner(${bannerSrc}),style:{cursor:${bannerSrc}!=null?"pointer":void 0,`
|
||||
}
|
||||
]
|
||||
replacement: {
|
||||
match: /(overflow:"visible",.{0,125}?!1\),)style:{(?=.+?backgroundImage:null!=(\i)\?`url\(\$\{\2\}\))/,
|
||||
replace: (_, rest, bannerSrc) => `${rest}onClick:()=>${bannerSrc}!=null&&$self.openBanner(${bannerSrc}),style:{cursor:${bannerSrc}!=null?"pointer":void 0,`
|
||||
}
|
||||
},
|
||||
// Group DMs top small & large icon
|
||||
{
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
import { Devs, IS_MAC } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { ComponentDispatch, FluxDispatcher, NavigationRouter, openUserSettingsPanel, SelectedGuildStore } from "@webpack/common";
|
||||
import { ComponentDispatch, FluxDispatcher, NavigationRouter, SelectedGuildStore, SettingsRouter } from "@webpack/common";
|
||||
|
||||
const KeyBinds = findByPropsLazy("JUMP_TO_GUILD", "SERVER_NEXT");
|
||||
|
||||
|
|
@ -55,7 +55,7 @@ export default definePlugin({
|
|||
break;
|
||||
case ",":
|
||||
e.preventDefault();
|
||||
openUserSettingsPanel("my_account");
|
||||
SettingsRouter.openUserSettings("my_account_panel");
|
||||
break;
|
||||
default:
|
||||
if (e.key >= "1" && e.key <= "9") {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue