diff --git a/src/api/SettingsSync/cloudSync.ts b/src/api/SettingsSync/cloudSync.ts index 4d3ef986e..29b16bc5b 100644 --- a/src/api/SettingsSync/cloudSync.ts +++ b/src/api/SettingsSync/cloudSync.ts @@ -20,17 +20,25 @@ import { ManifestEntry, SyncRequest, SyncResponse } from "./types"; const logger = new Logger("SettingsSync:Cloud", "#39b7e0"); const MANIFEST_STORE_KEY = "Vencord_cloudManifest"; +const API_VERSION_STORE_KEY = "Vencord_cloudApiVersions"; type ApiVersion = "v2" | "v1"; -let apiVersion: ApiVersion = "v2"; +async function loadApiVersionMap(): Promise> { + return await DataStore.get>(API_VERSION_STORE_KEY) ?? {}; +} async function getApiVersion(): Promise { - return apiVersion; + const map = await loadApiVersionMap(); + return map[getCloudUrl().origin] ?? "v2"; } async function setApiVersion(version: ApiVersion) { - apiVersion = version; + await DataStore.update>(API_VERSION_STORE_KEY, map => { + map ??= {}; + map[getCloudUrl().origin] = version; + return map; + }); } function toBase64(data: Uint8Array): string { diff --git a/src/equicordplugins/betterBlockedUsers/index.tsx b/src/equicordplugins/betterBlockedUsers/index.tsx index e1f22a079..dc1ecae54 100644 --- a/src/equicordplugins/betterBlockedUsers/index.tsx +++ b/src/equicordplugins/betterBlockedUsers/index.tsx @@ -22,17 +22,18 @@ export default definePlugin({ patches: [ { find: '"],{numberOfBlockedUsers:', + group: true, replacement: [ { - match: /(?<=listType:\i,numberOfUsers:\i\}=(\i).{0,800}numberOfIgnoredUsers:\i\}\)\}\)\]\}\))/, - replace: ",$1.listType==='blocked'?$self.renderSearchInput():null" + match: /(?<=\(0,\i\.jsx\)\(\i,\{listType:(\i),numberOfUsers:\i\.length\}\),)/, + replace: "$1==='blocked'?$self.renderSearchInput():null," }, { match: /(?<=\{userId:(\i).*?\.globalName.{0,25}\}\)\]\}\),)(\(.*?loading:\i\}\))/, replace: "$self.renderUser($1,$2)", }, { - match: /(?<=\}=(\i).{0,10}(\i).useState.{0,5};)/, + match: /(?<=userIds:\i,listType:\i\}=(\i).{0,30}(\i)\.useState\(\d+\);)/, replace: "let [searchResults,setSearchResults]=$2.useState([]);$self.setUpdateFunc($1,setSearchResults);" }, { @@ -50,17 +51,19 @@ export default definePlugin({ updateFunc(searchResults); }, []); - return { - const search = (e.target as HTMLInputElement).value.toLowerCase().trim(); - setValue(search); - lastSearch = search; - const searchResults = this.getFilteredUsers(search); - updateFunc(searchResults); - }} value={value} - >; + return
+ { + const search = (e.target as HTMLInputElement).value.toLowerCase().trim(); + setValue(search); + lastSearch = search; + const searchResults = this.getFilteredUsers(search); + updateFunc(searchResults); + }} value={value} + /> +
; }, renderUser(userId: string, rest: any) { return ( diff --git a/src/equicordplugins/betterBlockedUsers/styles.css b/src/equicordplugins/betterBlockedUsers/styles.css index 467e184f8..1f5408738 100644 --- a/src/equicordplugins/betterBlockedUsers/styles.css +++ b/src/equicordplugins/betterBlockedUsers/styles.css @@ -1,3 +1,8 @@ [class*="usersList"] [class*="text"] { user-select: text !important; +} + +.vc-bbu-search, +.vc-bbu-search * { + border-radius: 0 !important; } \ No newline at end of file diff --git a/src/equicordplugins/cleanChannelName/index.ts b/src/equicordplugins/cleanChannelName/index.ts index 9193ac648..6a26b1add 100644 --- a/src/equicordplugins/cleanChannelName/index.ts +++ b/src/equicordplugins/cleanChannelName/index.ts @@ -7,11 +7,32 @@ import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; import { Channel } from "@vencord/discord-types"; +import { ChannelStore } from "@webpack/common"; + +const SMALL_CAPS: Record = { + "ᴀ": "a", "ʙ": "b", "ᴄ": "c", "ᴅ": "d", "ᴇ": "e", "ꜰ": "f", "ɢ": "g", "ʜ": "h", "ɪ": "i", "ᴊ": "j", + "ᴋ": "k", "ʟ": "l", "ᴍ": "m", "ɴ": "n", "ᴏ": "o", "ᴘ": "p", "ǫ": "q", "ʀ": "r", "ꜱ": "s", "ᴛ": "t", + "ᴜ": "u", "ᴠ": "v", "ᴡ": "w", "x": "x", "ʏ": "y", "ᴢ": "z", +}; + +const ORIGINAL_NAME = Symbol("cleanChannelName.original"); + +let editingChannelId: string | null = null; + +function computeClean(name: string, type: number): string { + return name + .normalize("NFKC") + .replace(/[ᴀʙᴄᴅᴇꜰɢʜɪᴊᴋʟᴍɴᴏᴘǫʀꜱᴛᴜᴠᴡxʏᴢ]/g, m => SMALL_CAPS[m]) + .replace(/[^ -~]?\p{Extended_Pictographic}[^ -~]?/ug, "") + .replace(/-?[^\p{Letter} -~]-?/ug, [2, 4].includes(type) ? " " : "-") + .replace(/(^-|-$)/g, "") + .replace(/-+/g, "-"); +} export default definePlugin({ name: "CleanChannelName", authors: [Devs.AutumnVN], - description: "remove all emoji and decor shit from channel names", + description: "Remove emoji and decoration from channel names. Reverts to the original while you're editing the channel.", tags: ["Appearance", "Customisation", "Chat", "Emotes", "Servers"], patches: [ { @@ -19,27 +40,41 @@ export default definePlugin({ replacement: { match: /(?<=getChannel\(\i\)\{if\(null!=\i\)return )\i\(\i\)/, replace: "$self.cleanChannelName($&)", - } - } + }, + }, ], + flux: { + CHANNEL_SETTINGS_INIT({ channelId }: { channelId: string; }) { + editingChannelId = channelId; + (ChannelStore as any).emitChange?.(); + }, + CHANNEL_SETTINGS_CLOSE() { + editingChannelId = null; + (ChannelStore as any).emitChange?.(); + }, + }, + cleanChannelName(channel?: Channel) { - if (channel) { - channel.name = channel.name - .normalize("NFKC") - .replace(/[ᴀʙᴄᴅᴇꜰɢʜɪᴊᴋʟᴍɴᴏᴘǫʀꜱᴛᴜᴠᴡxʏᴢ]/g, match => { - const smallCapsToNormal = { - "ᴀ": "a", "ʙ": "b", "ᴄ": "c", "ᴅ": "d", "ᴇ": "e", "ꜰ": "f", "ɢ": "g", "ʜ": "h", "ɪ": "i", "ᴊ": "j", - "ᴋ": "k", "ʟ": "l", "ᴍ": "m", "ɴ": "n", "ᴏ": "o", "ᴘ": "p", "ǫ": "q", "ʀ": "r", "ꜱ": "s", "ᴛ": "t", - "ᴜ": "u", "ᴠ": "v", "ᴡ": "w", "x": "x", "ʏ": "y", "ᴢ": "z" - }; - return smallCapsToNormal[match]; - }) - .replace(/[^\u0020-\u007E]?\p{Extended_Pictographic}[^\u0020-\u007E]?/ug, "") - .replace(/-?[^\p{Letter}\u0020-\u007E]-?/ug, [2, 4].includes(channel.type) ? " " : "-") - .replace(/(^-|-$)/g, "") - .replace(/-+/g, "-"); - } + if (channel == null) return channel; + const c = channel as any; + + if (c[ORIGINAL_NAME] !== undefined) return channel; + + c[ORIGINAL_NAME] = channel.name; + + Object.defineProperty(channel, "name", { + configurable: true, + enumerable: true, + get() { + if (editingChannelId === channel.id) return c[ORIGINAL_NAME]; + return computeClean(c[ORIGINAL_NAME], channel.type); + }, + set(value: string) { + c[ORIGINAL_NAME] = value; + }, + }); + return channel; - } + }, }); diff --git a/src/equicordplugins/githubRepos/index.tsx b/src/equicordplugins/githubRepos/index.tsx index cb2b3d58b..be371e203 100644 --- a/src/equicordplugins/githubRepos/index.tsx +++ b/src/equicordplugins/githubRepos/index.tsx @@ -13,7 +13,7 @@ import { classNameFactory } from "@utils/css"; import definePlugin, { OptionType } from "@utils/types"; import { User } from "@vencord/discord-types"; import { findByCodeLazy } from "@webpack"; -import { React } from "@webpack/common"; +import { React, UserProfileStore } from "@webpack/common"; import { ProfilePopoutComponent } from "./components/ProfilePopoutComponent"; import { ProfileTabComponent } from "./components/ProfileTabComponent"; @@ -48,8 +48,8 @@ export default definePlugin({ { find: "#{intl::USER_PROFILE_ACTIVITY}", replacement: { - match: /\.MUTUAL_GUILDS\}\)\)(?=,(\i))/, - replace: '$&,$1.push({text:"GitHub",section:"GITHUB"})', + match: /(\i)\.id!==\i\?\.id&&\i&&\(.{0,300}\.MUTUAL_GUILDS\}\)\)(?=,(\i))/, + replace: '$&,$self.shouldShowGitHub($1.id)&&$2.push({text:"GitHub",section:"GITHUB"})', } }, // User Profile Modal v2 tab content @@ -61,6 +61,9 @@ export default definePlugin({ } } ], + shouldShowGitHub(userId: string) { + return UserProfileStore.getUserProfile(userId)?.connectedAccounts?.some((c: any) => c.type === "github") ?? false; + }, renderProfileCollection: (props: { user: User; displayProfile?: any; }) => { return ( settings.store.patchCommunity, }, { match: /(case \i\.\i\.GAME_SERVERS:.{0,50}\.CHANNEL.{0,25}return)(.*?);/, - replace: "$1[$2, vc_SidebarChat];", + replace: "$1[$2, $self.renderSidebar()];", predicate: () => settings.store.patchCommunity, }, ], diff --git a/src/equicordplugins/statusPresets/fixActionBar.css b/src/equicordplugins/statusPresets/fixActionBar.css deleted file mode 100644 index 231a70c1f..000000000 --- a/src/equicordplugins/statusPresets/fixActionBar.css +++ /dev/null @@ -1,3 +0,0 @@ -[class*="outerContainer"] [class*="container"] [class*="actionBar"] { - gap: 0; -} \ No newline at end of file diff --git a/src/equicordplugins/statusPresets/index.tsx b/src/equicordplugins/statusPresets/index.tsx index d21aced34..35a55bb63 100644 --- a/src/equicordplugins/statusPresets/index.tsx +++ b/src/equicordplugins/statusPresets/index.tsx @@ -23,46 +23,30 @@ import { getUserSettingLazy } from "@api/UserSettings"; import ErrorBoundary from "@components/ErrorBoundary"; import { EquicordDevs } from "@utils/constants"; import { proxyLazy } from "@utils/lazy"; -import { classes } from "@utils/misc"; import { openModalLazy } from "@utils/modal"; import { useForceUpdater } from "@utils/react"; import definePlugin, { OptionType, StartAt } from "@utils/types"; import { extractAndLoadChunksLazy, findComponentByCodeLazy, findModuleId, wreq } from "@webpack"; -import { Clickable, Menu, OverridePremiumTypeStore, Toasts, useState } from "@webpack/common"; - -import managedStyle from "./fixActionBar.css?managed"; - -const settings = definePluginSettings({ - StatusPresets: { - type: OptionType.COMPONENT, - description: "Status Presets", - component: () => <>, - default: {} - } -}); +import { Menu, OverridePremiumTypeStore, Toasts } from "@webpack/common"; interface Emoji { animated: boolean; - id: bigint | null; + id: string | null; name: string; } -const PlusSmallIcon = findComponentByCodeLazy("0-2h-5V6Z"); - interface DiscordStatus { emojiInfo: Emoji | null; text: string; clearAfter: "TODAY" | number | null; - status: "online" | "dnd" | "idle" | "invisible"; } -// TODO: find clearCustomStatusHint original css/svg or replace -let PMenu; +const PMenu = findComponentByCodeLazy("#{intl::MORE_OPTIONS}", ",renderSubmenu:"); const EmojiComponent = findComponentByCodeLazy(/\.translateSurrogatesToInlineEmoji\(\i\.name\);/); const CustomStatusSettings = getUserSettingLazy("status", "customStatus")!; const StatusModule = proxyLazy(() => { - const id = findModuleId(".customStatusInputTitle,"); + const id = findModuleId("#{intl::SAVE}", '"custom-status-input"', '"Invalid custom status clear timeout"),'); return wreq(Number(id)); }); @@ -77,7 +61,6 @@ const openCustomStatusModalLazy = () => openModalLazy(async () => { function getExpirationMs(expiration: "TODAY" | number) { if (expiration !== "TODAY") return Date.now() + expiration; - const now = new Date(); return new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1).getTime(); } @@ -92,69 +75,64 @@ function setStatus(status: DiscordStatus) { }); } -const ClearStatusButton = () => { e.stopPropagation(); CustomStatusSettings?.updateSetting(null); }}>; - -function StatusIcon({ isHovering, status }: { isHovering: boolean; status: DiscordStatus; }) { - return
{isHovering ? - - : (status.emojiInfo != null ? :
)}
; -} - -const RenderStatusMenuItem = ({ status, update, disabled }: { status: DiscordStatus; update: () => void; disabled: boolean; }) => { - const [isHovering, setIsHovering] = useState(false); - const handleMouseOver = () => { - setIsHovering(true); - }; - - const handleMouseOut = () => { - setIsHovering(false); - }; - - return
- { - e.stopPropagation(); - settings.store.StatusPresets[status.text] = undefined; // setting to undefined to remove it. - update(); - }}> -
{status.text}
-
; -}; - const StatusSubMenuComponent = () => { const premiumType = OverridePremiumTypeStore.getState().premiumTypeActual ?? 0; const update = useForceUpdater(); - return { }}> - {Object.entries((settings.store.StatusPresets as { [k: string]: DiscordStatus | undefined; })).map(([index, status]) => status != null ? (status.emojiInfo?.id != null && premiumType > 0 || status.emojiInfo?.id == null) && setStatus(status)} - render={() => } - /> : null)} - ; + + return ( + { }}> + {Object.entries((settings.store.StatusPresets as { [k: string]: DiscordStatus | undefined; })).map(([index, status]) => + status != null ? ( + (status.emojiInfo?.id == null || premiumType > 0) && setStatus(status)} + icon={status.emojiInfo != null + ? () => + : undefined + } + disabled={status.emojiInfo?.id != null && premiumType === 0} + > + { + const newPresets = JSON.parse(JSON.stringify(settings.store.StatusPresets)); + delete newPresets[status.text]; + settings.store.StatusPresets = newPresets; + update(); + }} + /> + + ) : null + )} + + ); }; +const settings = definePluginSettings({ + StatusPresets: { + type: OptionType.COMPONENT, + description: "Status Presets", + component: () => <>, + default: {} + } +}); + export default definePlugin({ name: "StatusPresets", description: "Allows you to remember your statuses and set them later", tags: ["Activity", "Utility"], authors: [EquicordDevs.iamme], - settings: settings, + settings, dependencies: ["UserSettingsAPI"], - managedStyle, patches: [ { find: '="custom-status-input";', replacement: { - match: /(?<=(\i)\?\.state\?\?.*?)\{text:\i\.\i\.\i\(\i\.\i#{intl::SAVE}\)/, - replace: "$self.renderRememberButton($1),$&" + match: /(?<=\[(\i).{0,6}\.useState\(\i\?\.state\?\?""\),\[(\i).{0,25}\?\?null\),\[(\i).*?)\{text:\i\.\i\.\i\(\i\.\i#{intl::SAVE}\)/, + replace: "$self.renderRememberButton({text:$1,emojiInfo:$2,clearAfter:$3}),$&" } }, { @@ -167,14 +145,12 @@ export default definePlugin({ { find: "#{intl::MORE_OPTIONS}),...", replacement: { - match: /(?<=targetElementRef:\i,spacing:.{0,150}className:\i\.\i,children:(\i)\(\)\}\)\})/, - replace: "$self.PMenu=$1;" + match: /\i:\(\)=>\i,(?=.*?function (\i).{0,100}renderSubmenu:\i,ref:)/, + replace: "$&PMenu:()=>$1," } }, ], - set PMenu(value: any) { - PMenu = value; - }, + render() { const status = CustomStatusSettings.getSetting(); return ( @@ -185,9 +161,7 @@ export default definePlugin({ id="sp-custom/presets-status" action="PRESS_SET_STATUS" onClick={openCustomStatusModalLazy} - icon={ - () =>
- } + icon={() =>
} label="Set Custom Status" renderSubmenu={StatusSubMenuComponent} /> @@ -196,19 +170,13 @@ export default definePlugin({ id="sp-edit/presets-status" action="PRESS_EDIT_CUSTOM_STATUS" onClick={openCustomStatusModalLazy} - hint={} - icon={ - () => status.emoji != null ? ( - - ) : null - } + icon={() => status.emoji != null ? ( + + ) : null} label="Edit Custom Status" renderSubmenu={StatusSubMenuComponent} - />} + /> + } ); }, diff --git a/src/equicordplugins/statusPresets/style.css b/src/equicordplugins/statusPresets/style.css index 3498d8821..6caa9dd9f 100644 --- a/src/equicordplugins/statusPresets/style.css +++ b/src/equicordplugins/statusPresets/style.css @@ -2,12 +2,23 @@ opacity: 0.5; } -.vc-sp-item:hover { - background-color: var(--menu-item-default-hover-bg) !important; - color: var(--white) !important; -} - .vc-sp-icon { transform: rotate(45deg); - margin-top: 4px; + width: 16px; + height: 16px; + color: var(--interactive-normal); + opacity: 0; } + +[data-menu-item="true"]:hover .vc-sp-icon { + opacity: 1; +} + +[data-menu-item="true"]:hover .vc-sp-icon:hover { + color: var(--status-danger); +} + +[data-menu-item="true"][id^="sp-custom-status-submenu"] img.emoji { + width: 16px !important; + height: 16px !important; +} \ No newline at end of file diff --git a/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx b/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx index 741b4447d..9348cb61e 100644 --- a/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx +++ b/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx @@ -27,7 +27,7 @@ import { classes } from "@utils/misc"; import { formatDuration } from "@utils/text"; import type { Channel, RoleOrUserPermission } from "@vencord/discord-types"; import { findByPropsLazy, findComponentByCodeLazy, findCssClassesLazy } from "@webpack"; -import { EmojiStore, FluxDispatcher, GuildMemberStore, GuildStore, Parser, PermissionsBits, PermissionStore, SnowflakeUtils, Text, Timestamp, Tooltip, useEffect, useState } from "@webpack/common"; +import { EmojiStore, FluxDispatcher, GuildMemberStore, GuildStore, Parser, PermissionsBits, PermissionStore, SnowflakeUtils, Timestamp, Tooltip, useEffect, useState } from "@webpack/common"; import { ComponentType } from "react"; import { cl, settings } from ".."; diff --git a/src/plugins/viewRaw/index.tsx b/src/plugins/viewRaw/index.tsx index 90751f8ff..b0e8f2e39 100644 --- a/src/plugins/viewRaw/index.tsx +++ b/src/plugins/viewRaw/index.tsx @@ -141,7 +141,6 @@ function MakeContextCallback(name: "Guild" | "Role" | "User" | "Channel" | "Mess const isMessage = name === "Message"; if (isMessage && !settings.store.messageContextMenu) return; - // typescript parser goes crazy if this is inline const id = `vc-view-${name.toLowerCase()}-raw`; const action = isMessage