diff --git a/packages/discord-types/src/utils.d.ts b/packages/discord-types/src/utils.d.ts index 1a8af6323..77ef1212a 100644 --- a/packages/discord-types/src/utils.d.ts +++ b/packages/discord-types/src/utils.d.ts @@ -6,15 +6,17 @@ import type { FluxEvents } from "./fluxEvents"; export { FluxEvents }; +type FluxEventsAutoComplete = LiteralUnion; + export interface FluxDispatcher { _actionHandlers: any; _interceptors: any; _subscriptions: any; addInterceptor(interceptor: any): void; - dispatch(event: { [key: string]: unknown; type: FluxEvents; }): Promise; + dispatch(event: { [key: string]: unknown; type: FluxEventsAutoComplete; }): Promise; isDispatching(): boolean; - subscribe(event: FluxEvents, callback: (data: any) => void): void; - unsubscribe(event: FluxEvents, callback: (data: any) => void): void; + subscribe(event: FluxEventsAutoComplete, callback: (data: any) => void): void; + unsubscribe(event: FluxEventsAutoComplete, callback: (data: any) => void): void; wait(callback: () => void): void; } diff --git a/src/components/Icons.tsx b/src/components/Icons.tsx index c38ba1a5c..76096ad84 100644 --- a/src/components/Icons.tsx +++ b/src/components/Icons.tsx @@ -128,6 +128,23 @@ export function InfoIcon(props: IconProps) { ); } +export function WarningIcon({ height = 32, width = 32, className }: IconProps) { + return ( + + + + ); +} + export function OwnerCrownIcon(props: IconProps) { return ( { +interface PluginCardProps { plugin: Plugin; - disabled: boolean; + disabled?: boolean; onRestartNeeded(name: string, key: string): void; isNew?: boolean; + onMouseEnter?: React.MouseEventHandler; + onMouseLeave?: React.MouseEventHandler; } export function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, onMouseLeave, isNew }: PluginCardProps) { const settings = Settings.plugins[plugin.name]; const pluginMeta = PluginMeta[plugin.name]; - const isEquicordPlugin = pluginMeta?.folderName?.startsWith("src/equicordplugins/") ?? false; - const isUserplugin = pluginMeta?.userPlugin ?? false; + const isEquicordPlugin = pluginMeta.folderName.startsWith("src/equicordplugins/") ?? false; + const isUserplugin = pluginMeta.userPlugin ?? false; const isEnabled = () => isPluginEnabled(plugin.name); diff --git a/src/components/settings/tabs/plugins/PluginModal.tsx b/src/components/settings/tabs/plugins/PluginModal.tsx index 59de61aa2..239a8d145 100644 --- a/src/components/settings/tabs/plugins/PluginModal.tsx +++ b/src/components/settings/tabs/plugins/PluginModal.tsx @@ -39,7 +39,7 @@ import { PluginMeta } from "~plugins"; import { OptionComponentMap } from "./components"; import { openContributorModal } from "./ContributorModal"; -import { GithubButton } from "./LinkIconButton"; +import { GithubButton, WebsiteButton } from "./LinkIconButton"; const cl = classNameFactory("vc-plugin-modal-"); @@ -146,6 +146,7 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti } const pluginMeta = PluginMeta[plugin.name]; + const isEquicordPlugin = pluginMeta.folderName.startsWith("src/equicordplugins/") ?? false; return ( @@ -160,6 +161,10 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti {plugin.description} {!pluginMeta.userPlugin && (
+ = { + desktop: "Discord Desktop app or Vesktop", + discordDesktop: "Discord Desktop app", + vesktop: "Vesktop & Equibop apps", + equibop: "Vesktop & Equibop apps", + web: "Vesktop & Equibop apps as well as the Web version of Discord", + dev: "Developer version of Equicord" +}; + function ExcludedPluginsList({ search }: { search: string; }) { const matchingExcludedPlugins = Object.entries(ExcludedPlugins) .filter(([name]) => name.toLowerCase().includes(search)); - const ExcludedReasons: Record<"web" | "discordDesktop" | "vesktop" | "equibop" | "desktop" | "dev", string> = { - desktop: "Discord Desktop app or Vesktop", - discordDesktop: "Discord Desktop app", - vesktop: "Vesktop & Equibop apps", - equibop: "Vesktop & Equibop apps", - web: "Vesktop & Equibop apps as well as the Web version of Discord", - dev: "Developer version of Equicord" - }; - return ( {matchingExcludedPlugins.length @@ -160,19 +160,7 @@ export default function PluginSettings() { })); }, []); - const depMap = useMemo(() => { - const o = {} as Record; - for (const plugin in Plugins) { - const deps = Plugins[plugin].dependencies; - if (deps) { - for (const dep of deps) { - o[dep] ??= []; - o[dep].push(plugin); - } - } - } - return o; - }, []); + const depMap = Vencord.Plugins.calculatePluginDependencyMap(); const sortedPlugins = useMemo(() => Object.values(Plugins) .sort((a, b) => a.name.localeCompare(b.name)), []); @@ -191,8 +179,8 @@ export default function PluginSettings() { const { status } = searchValue; const enabled = Vencord.Plugins.isPluginEnabled(plugin.name); const pluginMeta = PluginMeta[plugin.name]; - const isEquicordPlugin = pluginMeta?.folderName?.startsWith("src/equicordplugins/") ?? false; - const isUserplugin = pluginMeta?.userPlugin ?? false; + const isEquicordPlugin = pluginMeta.folderName.startsWith("src/equicordplugins/") ?? false; + const isUserplugin = pluginMeta.userPlugin ?? false; if (enabled && status === SearchStatus.DISABLED) return false; if (!enabled && status === SearchStatus.ENABLED) return false; @@ -241,7 +229,7 @@ export default function PluginSettings() { if (isRequired) { const tooltipText = p.required || !depMap[p.name] ? "This plugin is required for Equicord to function." - : makeDependencyList(depMap[p.name]?.filter(d => settings.plugins[d].enabled)); + : settings.plugins[d].enabled)} />; requiredPlugins.push( @@ -419,7 +407,7 @@ export default function PluginSettings() { ); } -function makeDependencyList(deps: string[]) { +export function PluginDependencyList({ deps }: { deps: string[]; }) { return ( This plugin is required by: diff --git a/src/equicordplugins/animalese/README.md b/src/equicordplugins/animalese/README.md index 02a59c96b..a660add88 100644 --- a/src/equicordplugins/animalese/README.md +++ b/src/equicordplugins/animalese/README.md @@ -1,18 +1,16 @@ -## vencord-animalese -animalese yapping in your discord client +## Animalese -> [!WARNING] -> don't go to Vencord support if you can't install this. +animalese yapping in your discord client ### demo -https://github.com/user-attachments/assets/3f1fa925-bbd2-451d-b63e-ea14dbe4be44 + ### settings ##### settings used in the video -![CleanShot 2024-12-26 at 22 11 49@2x](https://github.com/user-attachments/assets/35895fcc-d703-42cb-b0ef-bdef39ab15be) +![settings](https://cdn.nest.rip/uploads/32f09dd7-3cb2-4065-84ed-cbc5b193b2ec.png) you can change the settings in the settings but otherwise, enjoy the yap diff --git a/src/equicordplugins/chatButtonsPlus/index.tsx b/src/equicordplugins/chatButtonsPlus/index.tsx index 5a8836acf..9ea64a434 100644 --- a/src/equicordplugins/chatButtonsPlus/index.tsx +++ b/src/equicordplugins/chatButtonsPlus/index.tsx @@ -227,8 +227,7 @@ migratePluginSettings("ChatButtonsPlus", "Meow", "Woof"); export default definePlugin({ name: "ChatButtonsPlus", description: "Add custom chat buttons with personalized + messages and SVG icons", - authors: - [EquicordDevs.creations], + authors: [EquicordDevs.creations], settings, renderChatBarButton: ({ isMainChat }) => { diff --git a/src/equicordplugins/equicordHelper/README.md b/src/equicordplugins/equicordHelper/README.md index 9c1dcab8a..d371ef512 100644 --- a/src/equicordplugins/equicordHelper/README.md +++ b/src/equicordplugins/equicordHelper/README.md @@ -1,3 +1,3 @@ # EquicordHelper -Fixes various issues for crashing and adds other items that might help with support +Fixes various issues for crashing and adds other items such as plugin cards and buttons for managing plugins quickly in support diff --git a/src/equicordplugins/equicordHelper/index.tsx b/src/equicordplugins/equicordHelper/index.tsx index 0d068f6b3..0f33cd1a6 100644 --- a/src/equicordplugins/equicordHelper/index.tsx +++ b/src/equicordplugins/equicordHelper/index.tsx @@ -8,13 +8,10 @@ import "@equicordplugins/_misc/styles.css"; import { definePluginSettings } from "@api/Settings"; import { Devs, EquicordDevs } from "@utils/constants"; -import { Logger } from "@utils/Logger"; -import { isEquicordGuild, isEquicordSupport } from "@utils/misc"; import definePlugin, { OptionType } from "@utils/types"; -import { Button, Flex, showToast, Toasts } from "@webpack/common"; -import { JSX } from "react"; -import { toggleEnabled } from "./utils"; +import { PluginButtons } from "./pluginButtons"; +import { PluginCards } from "./pluginCards"; const settings = definePluginSettings({ disableCreateDMButton: { @@ -70,59 +67,11 @@ export default definePlugin({ }, ], renderMessageAccessory(props) { - return pluginToggleButtons(props); - } -}); - -function pluginToggleButtons(props) { - const buttons = [] as JSX.Element[]; - const msg = props.message.content?.toLowerCase() ?? ""; - - const contentWords = (msg.match(/`\w+`/g) ?? []).map(e => e.slice(1, -1)); - const matchedPlugins = Object.keys(Vencord.Plugins.plugins).filter(name => contentWords.includes(name.toLowerCase())); - const matchedPlugin = matchedPlugins.sort((a, b) => b.length - a.length)[0]; - const pluginData = matchedPlugin ? Vencord.Plugins.plugins[matchedPlugin] : null; - - const isEquicord = isEquicordGuild(props.channel.id) && isEquicordSupport(props.message.author.id); - const startsWithEnabled = msg.startsWith("enable"); - const startsWithDisabled = msg.startsWith("disable"); - - const shouldAddPluginButtons = pluginData && isEquicord && (startsWithEnabled || startsWithDisabled); - - if (shouldAddPluginButtons) { - if (pluginData.required || pluginData.name.endsWith("API")) return; - const isEnabled = Vencord.Plugins.isPluginEnabled(matchedPlugin); - - let label = `${matchedPlugin} is already ${isEnabled ? "enabled" : "disabled"}`; - let disabled = true; - - if ((startsWithDisabled && isEnabled) || (startsWithEnabled && !isEnabled)) { - label = `${isEnabled ? "Disable" : "Enable"} ${matchedPlugin}`; - disabled = false; - } - - buttons.push( - + return ( + <> + + + ); } - - return buttons.length - ? {buttons} - : null; -} +}); diff --git a/src/equicordplugins/equicordHelper/pluginButtons.tsx b/src/equicordplugins/equicordHelper/pluginButtons.tsx new file mode 100644 index 000000000..a7c26e0d0 --- /dev/null +++ b/src/equicordplugins/equicordHelper/pluginButtons.tsx @@ -0,0 +1,71 @@ +/* + * 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 { Logger } from "@utils/Logger"; +import { isEquicordGuild, isEquicordSupport } from "@utils/misc"; +import { Message } from "@vencord/discord-types"; +import { Button, showToast, Toasts } from "@webpack/common"; +import { JSX } from "react"; + +import { toggleEnabled } from "./utils"; + +export const PluginButtons = ErrorBoundary.wrap(function PluginCards({ message }: { message: Message; }) { + const pluginButtons = [] as JSX.Element[]; + const msg = message.content?.toLowerCase() ?? ""; + + const contentWords = (msg.match(/`\w+`/g) ?? []).map(e => e.slice(1, -1)); + const matchedPlugins = Object.keys(Vencord.Plugins.plugins).filter(name => contentWords.includes(name.toLowerCase())); + const matchedPlugin = matchedPlugins.sort((a, b) => b.length - a.length)[0]; + const pluginData = matchedPlugin ? Vencord.Plugins.plugins[matchedPlugin] : null; + + const isEquicord = isEquicordGuild(message.channel_id) && isEquicordSupport(message.author.id); + const startsWithEnabled = msg.startsWith("enable"); + const startsWithDisabled = msg.startsWith("disable"); + + const shouldAddPluginButtons = pluginData && isEquicord && (startsWithEnabled || startsWithDisabled); + + if (shouldAddPluginButtons) { + if (pluginData.required || pluginData.name.endsWith("API")) return; + const isEnabled = Vencord.Plugins.isPluginEnabled(matchedPlugin); + + let label = `${matchedPlugin} is already ${isEnabled ? "enabled" : "disabled"}`; + let disabled = true; + + if ((startsWithDisabled && isEnabled) || (startsWithEnabled && !isEnabled)) { + label = `${isEnabled ? "Disable" : "Enable"} ${matchedPlugin}`; + disabled = false; + } + + pluginButtons.push( + + ); + } + + if (pluginButtons.length === 0) return null; + + return ( +
+ {pluginButtons} +
+ ); +}, { noop: true }); diff --git a/src/equicordplugins/equicordHelper/pluginCards.tsx b/src/equicordplugins/equicordHelper/pluginCards.tsx new file mode 100644 index 000000000..17af02327 --- /dev/null +++ b/src/equicordplugins/equicordHelper/pluginCards.tsx @@ -0,0 +1,155 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { useSettings } from "@api/Settings"; +import ErrorBoundary from "@components/ErrorBoundary"; +import { WarningIcon } from "@components/Icons"; +import { AddonCard } from "@components/settings"; +import { ExcludedReasons, PluginDependencyList } from "@components/settings/tabs/plugins"; +import { PluginCard } from "@components/settings/tabs/plugins/PluginCard"; +import { EQUIBOT_USER_ID } from "@utils/constants"; +import { isEquicordGuild, isEquicordSupport } from "@utils/misc"; +import { Message } from "@vencord/discord-types"; +import { showToast, Tooltip, TooltipContainer } from "@webpack/common"; +import { JSX } from "react"; + +import plugins, { ExcludedPlugins } from "~plugins"; + + +export function ChatPluginCard({ url, description }: { url: string, description: string; }) { + const pluginNameFromUrl = new URL(url).pathname.split("/")[2]; + + const actualPluginName = Object.keys(plugins).find(name => + name.toLowerCase() === pluginNameFromUrl?.toLowerCase() + ); + + const pluginName = actualPluginName || pluginNameFromUrl; + + useSettings([`plugins.${pluginName ?? ""}.enabled`]); + + if (!pluginName) return null; + + const p = plugins[pluginName]; + const excludedPlugin = ExcludedPlugins[pluginName]; + + if (excludedPlugin || !p) { + const toolTipText = !p + ? `${pluginName} is only available on the ${ExcludedReasons[ExcludedPlugins[pluginName]]}` + : "This plugin is not on this version of Equicord. Try updating!"; + + const card = ( + { }} + disabled={true} + infoButton={} + /> + ); + + return description + ? {card} + : card; + } + + const onRestartNeeded = () => showToast("A restart is required for the change to take effect!"); + + const required = Vencord.Plugins.isPluginRequired(pluginName); + const dependents = Vencord.Plugins.calculatePluginDependencyMap()[p.name]?.filter(d => Vencord.Plugins.isPluginEnabled(d)); + + if (required) { + const tooltipText = p.required || !dependents.length + ? "This plugin is required for Equicord to function." + : ; + + return ( + + {({ onMouseLeave, onMouseEnter }) => + + } + + ); + } + + return ( + + ); +} + +export const PluginCards = ErrorBoundary.wrap(function PluginCards({ message }: { message: Message; }) { + const seenPlugins = new Set(); + const pluginCards: JSX.Element[] = []; + + // Process embeds + message.embeds?.forEach(embed => { + if (!embed.url?.startsWith("https://equicord.org/plugins/") && !embed.url?.startsWith("https://vencord.dev/plugins/")) return; + + const isEquicord = isEquicordGuild(message.channel_id) && isEquicordSupport(message.author.id); + if (!isEquicord) return; + + const pluginNameFromUrl = new URL(embed.url).pathname.split("/")[2]; + const actualPluginName = Object.keys(plugins).find(name => + name.toLowerCase() === pluginNameFromUrl?.toLowerCase() + ); + const pluginName = actualPluginName || pluginNameFromUrl; + + if (!pluginName || seenPlugins.has(pluginName)) return; + seenPlugins.add(pluginName); + + pluginCards.push( + + ); + }); + + // Process components + const components = (message.components?.[0] as any)?.components; + if (message.author.id === EQUIBOT_USER_ID && components?.length >= 4) { + const description = components[1]?.content; + const pluginUrl = components.find((c: any) => c?.components)?.components[0]?.url; + if (pluginUrl?.startsWith("https://equicord.org/plugins/") || pluginUrl?.startsWith("https://vencord.dev/plugins/")) { + const pluginNameFromUrl = new URL(pluginUrl).pathname.split("/")[2]; + const actualPluginName = Object.keys(plugins).find(name => + name.toLowerCase() === pluginNameFromUrl?.toLowerCase() + ); + const pluginName = actualPluginName || pluginNameFromUrl; + + if (pluginName && !seenPlugins.has(pluginName)) { + seenPlugins.add(pluginName); + pluginCards.push( + + ); + } + } + } + + if (pluginCards.length === 0) return null; + + return ( +
+ {pluginCards} +
+ ); +}, { noop: true }); diff --git a/src/equicordplugins/questify/index.tsx b/src/equicordplugins/questify/index.tsx index 00dd5a987..ae0fe1cbf 100644 --- a/src/equicordplugins/questify/index.tsx +++ b/src/equicordplugins/questify/index.tsx @@ -1191,7 +1191,7 @@ export default definePlugin({ { // The Quest Accepted button is disabled by default. If the user reloads the client, they need a way // to resume the automatic completion, so patch in optionally enabling it if the feature is enabled. - match: /(START_QUEST_CTA.{0,400}?disabled:)(!0)/, + match: /(fullWidth:!0}\)}\):.{0,150}?disabled:)(!0.{0,150}?"horizontal")/, replace: "$1$self.shouldDisableQuestAcceptedButton(arguments[0].quest)??$2" }, { diff --git a/src/equicordplugins/quoter/index.tsx b/src/equicordplugins/quoter/index.tsx index 9e2feeba3..d82b9d04d 100644 --- a/src/equicordplugins/quoter/index.tsx +++ b/src/equicordplugins/quoter/index.tsx @@ -1,10 +1,10 @@ /* * Vencord, a Discord client mod - * Copyright (c) 2024 Vendicated and contributors + * Copyright (c) 2025 Vendicated and contributors * SPDX-License-Identifier: GPL-3.0-or-later */ -import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu"; +import { findGroupChildrenByChildId } from "@api/ContextMenu"; import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; import { getCurrentChannel } from "@utils/discord"; @@ -20,44 +20,28 @@ enum ImageStyle { inspirational } -const messagePatch: NavContextMenuPatchCallback = (children, { message }) => { - recentmessage = message; - if (!message.content) return; - - const buttonElement = - { - openModal(props => ); - }} - />; - - const group = findGroupChildrenByChildId("copy-text", children); - if (!group) { - children.push(buttonElement); - return; - } - - group.splice( - group.findIndex(c => c?.props?.id === "copy-text") + 1, 0, buttonElement - ); -}; - -let recentmessage: Message; -let grayscale; -let setStyle: ImageStyle = ImageStyle.inspirational; -let customMessage: string = ""; - enum userIDOptions { displayName, userName, userId } + +interface QuoteImageOptions { + avatarUrl: string; + quoteOld: string; + grayScale: boolean; + imageStyle: ImageStyle; + author: { + username: string; + globalName?: string; + id: string; + }; + userIdentifier: userIDOptions; +} + + const settings = definePluginSettings({ - userIdentifier: - { + userIdentifier: { type: OptionType.SELECT, description: "What the author's name should be displayed as", options: [ @@ -71,14 +55,28 @@ const settings = definePluginSettings({ export default definePlugin({ name: "Quoter", description: "Adds the ability to create an inspirational quote image from a message", - authors: [Devs.Samwich], + authors: [Devs.Samwich, Devs.thororen], + settings, contextMenus: { - "message": messagePatch - }, - settings + "message": (children, { message }) => { + if (!message.content) return; + const buttonElement = ( + openModal(props => )} + /> + ); + + const group = findGroupChildrenByChildId("copy-text", children); + if (!group) children.push(buttonElement); + else group.splice(group.findIndex(c => c?.props?.id === "copy-text") + 1, 0, buttonElement); + } + } }); -function sizeUpgrade(url) { +function sizeUpgrade(url: string) { const u = new URL(url); u.searchParams.set("size", "512"); return u.toString(); @@ -87,68 +85,49 @@ function sizeUpgrade(url) { const preparingSentence: string[] = []; const lines: string[] = []; -async function createQuoteImage(avatarUrl: string, quoteOld: string, grayScale: boolean): Promise { +async function createQuoteImage(options: QuoteImageOptions): Promise { + const { avatarUrl, quoteOld, grayScale, imageStyle, author, userIdentifier } = options; + const quote = FixUpQuote(quoteOld); const canvas = document.createElement("canvas"); const ctx = canvas.getContext("2d"); + if (!ctx) throw new Error("Cant get 2d rendering context :("); - if (!ctx) { - throw new Error("Cant get 2d rendering context :("); - } - - let name: string = ""; - - switch (settings.store.userIdentifier) { + let name = ""; + switch (userIdentifier) { case userIDOptions.displayName: - // @ts-ignore - const meow = recentmessage.author.globalName; - if (meow) { - name = meow; - } - else { - name = recentmessage.author.username; - } + name = author.globalName || author.username; break; case userIDOptions.userName: - name = recentmessage.author.username; + name = author.username; break; case userIDOptions.userId: - name = recentmessage.author.id; + name = author.id; break; default: - name = "MAN WTF HAPPENED"; - break; + name = "Unknown"; } - switch (setStyle) { + switch (imageStyle) { case ImageStyle.inspirational: - const cardWidth = 1200; const cardHeight = 600; - canvas.width = cardWidth; canvas.height = cardHeight; ctx.fillStyle = "#000"; - ctx.fillRect(0, 0, canvas.width, canvas.height); + ctx.fillRect(0, 0, cardWidth, cardHeight); const avatarBlob = await fetchImageAsBlob(avatarUrl); - const fadeBlob = await fetchImageAsBlob("https://raw.githubusercontent.com/Equicord/Equibored/refs/heads/main/icons/quoter/quoter.png"); + const fadeBlob = await fetchImageAsBlob("https://raw.githubusercontent.com/Equicord/Equibored/main/icons/quoter/quoter.png"); const avatar = new Image(); const fade = new Image(); - const avatarPromise = new Promise(resolve => { - avatar.onload = () => resolve(); - avatar.src = URL.createObjectURL(avatarBlob); - }); - - const fadePromise = new Promise(resolve => { - fade.onload = () => resolve(); - fade.src = URL.createObjectURL(fadeBlob); - }); - - await Promise.all([avatarPromise, fadePromise]); + await Promise.all([ + new Promise(resolve => { avatar.onload = () => resolve(); avatar.src = URL.createObjectURL(avatarBlob); }), + new Promise(resolve => { fade.onload = () => resolve(); fade.src = URL.createObjectURL(fadeBlob); }) + ]); ctx.drawImage(avatar, 0, 0, cardHeight, cardHeight); @@ -164,42 +143,69 @@ async function createQuoteImage(avatarUrl: string, quoteOld: string, grayScale: ctx.fillStyle = "#fff"; ctx.font = "italic 20px Georgia"; const quoteWidth = cardWidth / 2 - 50; - const quoteX = ((cardWidth - cardHeight)); + const quoteX = cardWidth - cardHeight; const quoteY = cardHeight / 2 - 10; wrapText(ctx, `"${quote}"`, quoteX, quoteY, quoteWidth, 20, preparingSentence, lines); const wrappedTextHeight = lines.length * 25; ctx.font = "bold 16px Georgia"; - const authorNameX = (cardHeight * 1.5) - (ctx.measureText(`- ${name}`).width / 2) - 30; + const authorNameX = cardHeight * 1.5 - ctx.measureText(`- ${name}`).width / 2 - 30; const authorNameY = quoteY + wrappedTextHeight + 30; - ctx.fillText(`- ${name}`, authorNameX, authorNameY); + preparingSentence.length = 0; lines.length = 0; + return await canvasToBlob(canvas); } } -function registerStyleChange(style) { - setStyle = style; - GeneratePreview(); +function generateFileNamePreview(message: string) { + const words = message.split(" "); + return words.length >= 6 ? words.slice(0, 6).join(" ") : words.join(" "); } -function QuoteModal(props: ModalProps) { +function QuoteModal({ message, ...props }: ModalProps & { message: Message; }) { const [gray, setGray] = useState(true); - useEffect(() => { - grayscale = gray; - GeneratePreview(); - }, [gray]); + const [style, setStyle] = useState(ImageStyle.inspirational); + const [quoteImage, setQuoteImage] = useState(null); + const { userIdentifier } = settings.store; + const safeContent = message.content ? message.content : ""; - const safeContent = recentmessage && recentmessage.content ? recentmessage.content : ""; + const generateImage = async () => { + const image = await createQuoteImage({ + avatarUrl: sizeUpgrade(message.author.getAvatarURL()), + quoteOld: safeContent, + grayScale: gray, + imageStyle: style, + author: message.author, + userIdentifier + }); + setQuoteImage(image); + document.getElementById("quoterPreview")?.setAttribute("src", URL.createObjectURL(image)); + }; - const [custom, setCustom] = useState(safeContent); - useEffect(() => { - customMessage = custom; - GeneratePreview(); - }, [custom]); + useEffect(() => { generateImage(); }, [gray, style, safeContent]); + + const Export = () => { + if (!quoteImage) return; + const link = document.createElement("a"); + const preview = generateFileNamePreview(safeContent); + link.href = URL.createObjectURL(quoteImage); + link.download = `${preview} - ${message.author.username}.png`; + link.click(); + link.remove(); + }; + + const SendInChat = () => { + if (!quoteImage) return; + const preview = generateFileNamePreview(safeContent); + const file = new File([quoteImage], `${preview} - ${message.author.username}.png`, { type: "image/png" }); + // @ts-expect-error typing issue + UploadHandler.promptToUpload([file], getCurrentChannel(), 0); + props.onClose?.(); + }; return ( @@ -210,59 +216,27 @@ function QuoteModal(props: ModalProps) { - -



+ +

+

Grayscale - +