diff options
-rw-r--r-- | src/plugins/gifPaste.ts | 4 | ||||
-rw-r--r-- | src/plugins/invisibleChat/components/EncryptionModal.tsx | 5 | ||||
-rw-r--r-- | src/plugins/quickMention.tsx | 5 | ||||
-rw-r--r-- | src/plugins/sendTimestamps/index.tsx | 219 | ||||
-rw-r--r-- | src/plugins/sendTimestamps/styles.css | 48 | ||||
-rw-r--r-- | src/utils/constants.ts | 4 | ||||
-rw-r--r-- | src/utils/discord.ts | 12 | ||||
-rw-r--r-- | src/webpack/common/utils.ts | 1 |
8 files changed, 288 insertions, 10 deletions
diff --git a/src/plugins/gifPaste.ts b/src/plugins/gifPaste.ts index 9b6e8a9..fa64f30 100644 --- a/src/plugins/gifPaste.ts +++ b/src/plugins/gifPaste.ts @@ -18,12 +18,12 @@ import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; -import { filters, findLazy, mapMangledModuleLazy } from "@webpack"; +import { filters, mapMangledModuleLazy } from "@webpack"; +import { ComponentDispatch } from "@webpack/common"; const ExpressionPickerState = mapMangledModuleLazy('name:"expression-picker-last-active-view"', { close: filters.byCode("activeView:null", "setState") }); -const ComponentDispatch = findLazy(m => m.emitter?._events?.INSERT_TEXT); export default definePlugin({ name: "GifPaste", diff --git a/src/plugins/invisibleChat/components/EncryptionModal.tsx b/src/plugins/invisibleChat/components/EncryptionModal.tsx index f650f28..c4f1f35 100644 --- a/src/plugins/invisibleChat/components/EncryptionModal.tsx +++ b/src/plugins/invisibleChat/components/EncryptionModal.tsx @@ -24,13 +24,10 @@ import { ModalRoot, openModal, } from "@utils/modal"; -import { findLazy } from "@webpack"; -import { Button, Forms, React, Switch, TextInput } from "@webpack/common"; +import { Button, ComponentDispatch, Forms, React, Switch, TextInput } from "@webpack/common"; import { encrypt } from "../index"; -const ComponentDispatch = findLazy(m => m.emitter?._events?.INSERT_TEXT); - function EncModal(props: ModalProps) { const [secret, setSecret] = React.useState(""); const [cover, setCover] = React.useState(""); diff --git a/src/plugins/quickMention.tsx b/src/plugins/quickMention.tsx index 6e00dd0..fe3da56 100644 --- a/src/plugins/quickMention.tsx +++ b/src/plugins/quickMention.tsx @@ -19,10 +19,7 @@ import { addButton, removeButton } from "@api/MessagePopover"; import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; -import { findLazy } from "@webpack"; -import { ChannelStore } from "@webpack/common"; - -const ComponentDispatch = findLazy(m => m.emitter?._events?.INSERT_TEXT); +import { ChannelStore, ComponentDispatch } from "@webpack/common"; export default definePlugin({ name: "QuickMention", diff --git a/src/plugins/sendTimestamps/index.tsx b/src/plugins/sendTimestamps/index.tsx new file mode 100644 index 0000000..9eaa8be --- /dev/null +++ b/src/plugins/sendTimestamps/index.tsx @@ -0,0 +1,219 @@ +/* + * 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 "./styles.css"; + +import { addPreSendListener, removePreSendListener } from "@api/MessageEvents"; +import { classNameFactory } from "@api/Styles"; +import { Devs } from "@utils/constants"; +import { getTheme, Theme } from "@utils/discord"; +import { Margins } from "@utils/margins"; +import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModal } from "@utils/modal"; +import definePlugin from "@utils/types"; +import { Button, ButtonLooks, ButtonWrapperClasses, ComponentDispatch, Forms, Parser, Select, Tooltip, useMemo, useState } from "@webpack/common"; + +function parseTime(time: string) { + const cleanTime = time.slice(1, -1).replace(/(\d)(AM|PM)$/i, "$1 $2"); + + let ms = new Date(`${new Date().toDateString()} ${cleanTime}`).getTime() / 1000; + if (isNaN(ms)) return time; + + // add 24h if time is in the past + if (Date.now() / 1000 > ms) ms += 86400; + + return `<t:${Math.round(ms)}:t>`; +} + +const Formats = ["", "t", "T", "d", "D", "f", "F", "R"] as const; +type Format = typeof Formats[number]; + +const cl = classNameFactory("vc-st-"); + +function PickerModal({ rootProps, close }: { rootProps: ModalProps, close(): void; }) { + const [value, setValue] = useState<string>(); + const [format, setFormat] = useState<Format>(""); + const time = Math.round((new Date(value!).getTime() || Date.now()) / 1000); + + const formatTimestamp = (time: number, format: Format) => `<t:${time}${format && `:${format}`}>`; + + const [formatted, rendered] = useMemo(() => { + const formatted = formatTimestamp(time, format); + return [formatted, Parser.parse(formatted)]; + }, [time, format]); + + return ( + <ModalRoot {...rootProps}> + <ModalHeader className={cl("modal-header")}> + <Forms.FormTitle tag="h2"> + Timestamp Picker + </Forms.FormTitle> + + <ModalCloseButton onClick={close} /> + </ModalHeader> + + <ModalContent className={cl("modal-content")}> + <input + type="datetime-local" + value={value} + onChange={e => setValue(e.currentTarget.value)} + style={{ + colorScheme: getTheme() === Theme.Light ? "light" : "dark", + }} + /> + + <Forms.FormTitle>Timestamp Format</Forms.FormTitle> + <Select + options={ + Formats.map(m => ({ + label: m, + value: m + })) + } + isSelected={v => v === format} + select={v => setFormat(v)} + serialize={v => v} + renderOptionLabel={o => ( + <div className={cl("format-label")}> + {Parser.parse(formatTimestamp(time, o.value))} + </div> + )} + renderOptionValue={() => rendered} + /> + + <Forms.FormTitle className={Margins.bottom8}>Preview</Forms.FormTitle> + <Forms.FormText className={cl("preview-text")}> + {rendered} ({formatted}) + </Forms.FormText> + </ModalContent> + + <ModalFooter> + <Button + onClick={() => { + ComponentDispatch.dispatchToLastSubscribed("INSERT_TEXT", { rawText: formatted }); + close(); + }} + >Insert</Button> + </ModalFooter> + </ModalRoot> + ); +} + +export default definePlugin({ + name: "SendTimestamps", + description: "Send timestamps easily via chat box button & text shortcuts. Read the extended description!", + authors: [Devs.Ven, Devs.Tyler], + dependencies: ["MessageEventsAPI"], + + patches: [ + { + find: ".activeCommandOption", + replacement: { + match: /(.)\.push.{1,30}disabled:(\i),.{1,20}\},"gift"\)\)/, + replace: "$&;try{$2||$1.push($self.chatBarIcon())}catch{}", + } + }, + ], + + start() { + this.listener = addPreSendListener((_, msg) => { + msg.content = msg.content.replace(/`\d{1,2}:\d{2} ?(?:AM|PM)?`/gi, parseTime); + }); + }, + + stop() { + removePreSendListener(this.listener); + }, + + chatBarIcon() { + return ( + <Tooltip text="Insert Timestamp"> + {({ onMouseEnter, onMouseLeave }) => ( + <div style={{ display: "flex" }}> + <Button + aria-haspopup="dialog" + aria-label="" + size="" + look={ButtonLooks.BLANK} + onMouseEnter={onMouseEnter} + onMouseLeave={onMouseLeave} + innerClassName={ButtonWrapperClasses.button} + onClick={() => { + const key = openModal(props => ( + <PickerModal + rootProps={props} + close={() => closeModal(key)} + /> + )); + }} + className={cl("button")} + > + <div className={ButtonWrapperClasses.buttonWrapper}> + <svg + aria-hidden="true" + role="img" + width="24" + height="24" + viewBox="0 0 24 24" + > + <g fill="none" fill-rule="evenodd"> + <path fill="currentColor" d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.11 0-1.99.9-1.99 2L3 19a2 2 0 0 0 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v11zM7 10h5v5H7v-5z" /> + <rect width="24" height="24" /> + </g> + </svg> + </div> + </Button> + </div> + ) + } + </Tooltip > + ); + }, + + settingsAboutComponent() { + const samples = [ + "12:00", + "3:51", + "17:59", + "24:00", + "12:00 AM", + "0:13PM" + ].map(s => `\`${s}\``); + + return ( + <> + <Forms.FormText> + To quickly send send time only timestamps, include timestamps formatted as `HH:MM` (including the backticks!) in your message + </Forms.FormText> + <Forms.FormText> + See below for examples. + If you need anything more specific, use the Date button in the chat bar! + </Forms.FormText> + <Forms.FormText> + Examples: + <ul> + {samples.map(s => ( + <li key={s}> + <code>{s}</code> {"->"} {Parser.parse(parseTime(s))} + </li> + ))} + </ul> + </Forms.FormText> + </> + ); + }, +}); diff --git a/src/plugins/sendTimestamps/styles.css b/src/plugins/sendTimestamps/styles.css new file mode 100644 index 0000000..daefd42 --- /dev/null +++ b/src/plugins/sendTimestamps/styles.css @@ -0,0 +1,48 @@ +.vc-st-modal-content input { + background-color: var(--input-background); + color: var(--text-normal); + width: 95%; + padding: 8px 8px 8px 12px; + margin: 1em 0; + outline: none; + border: 1px solid var(--input-background); + border-radius: 4px; + font-weight: 500; + font-style: inherit; + font-size: 100%; +} + +.vc-st-format-label, +.vc-st-format-label span { + background-color: transparent; +} + +.vc-st-modal-content [class|="select"] { + margin-bottom: 1em; +} + +.vc-st-modal-content [class|="select"] span { + background-color: var(--input-background); +} + +.vc-st-modal-header { + justify-content: space-between; + align-content: center; +} + +.vc-st-modal-header h1 { + margin: 0; +} + +.vc-st-modal-header button { + padding: 0; +} + +.vc-st-preview-text { + margin-bottom: 1em; +} + +.vc-st-button { + margin-right: 4px; + transform: scale(1.1); +} diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 6a939c9..e2105ae 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -258,6 +258,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({ name: "pylix", id: 492949202121261067n }, + Tyler: { + name: "\\\\GGTyler\\\\", + id: 143117463788191746n + }, RyanCaoDev: { name: "RyanCaoDev", id: 952235800110694471n, diff --git a/src/utils/discord.ts b/src/utils/discord.ts index f1a1f8a..ec732b4 100644 --- a/src/utils/discord.ts +++ b/src/utils/discord.ts @@ -16,9 +16,12 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ +import { findLazy } from "@webpack"; import { ChannelStore, GuildStore, PrivateChannelsStore, SelectedChannelStore } from "@webpack/common"; import { Guild } from "discord-types/general"; +const PreloadedUserSettings = findLazy(m => m.ProtoClass?.typeName.endsWith("PreloadedUserSettings")); + export function getCurrentChannel() { return ChannelStore.getChannel(SelectedChannelStore.getChannelId()); } @@ -30,3 +33,12 @@ export function getCurrentGuild(): Guild | undefined { export function openPrivateChannel(userId: string) { PrivateChannelsStore.openPrivateChannel(userId); } + +export const enum Theme { + Dark = 1, + Light = 2 +} + +export function getTheme(): Theme { + return PreloadedUserSettings.getCurrentValue()?.appearance?.theme; +} diff --git a/src/webpack/common/utils.ts b/src/webpack/common/utils.ts index 2c2af54..3893a3a 100644 --- a/src/webpack/common/utils.ts +++ b/src/webpack/common/utils.ts @@ -23,6 +23,7 @@ import { _resolveReady, filters, findByCodeLazy, findByPropsLazy, findLazy, mapM import type * as t from "./types/utils"; export let FluxDispatcher: t.FluxDispatcher; +export const ComponentDispatch = findLazy(m => m.emitter?._events?.INSERT_TEXT); export const RestAPI: t.RestAPI = findByPropsLazy("getAPIBaseURL", "get"); export const moment: typeof import("moment") = findByPropsLazy("parseTwoDigitYear"); |