diff --git a/package.json b/package.json index 41b9a2544da82105715eaab8814cd84404991196..2e298ff3fcfa9c664948d9fce65a63aee4f166b5 100644 --- a/package.json +++ b/package.json @@ -94,7 +94,7 @@ "react-router-dom": "^5.2.0", "react-scroll": "^1.8.2", "redux": "^4.1.0", - "revolt.js": "4.3.3-alpha.7", + "revolt.js": "4.3.3-alpha.8", "rimraf": "^3.0.2", "sass": "^1.35.1", "shade-blend-color": "^1.0.0", diff --git a/src/components/common/AutoComplete.tsx b/src/components/common/AutoComplete.tsx index 0e0f399da111e2276555137281e8d6bfa00000cb..565f4a4ac2fd34f8ba0694aaaf61c088f457c47e 100644 --- a/src/components/common/AutoComplete.tsx +++ b/src/components/common/AutoComplete.tsx @@ -384,7 +384,7 @@ export default function AutoComplete({ detached, state, setState, onClick }: Pic }) } onClick={onClick}> - <Emoji emoji={(emojiDictionary as any)[match]} size={20} /> + <Emoji emoji={(emojiDictionary as Record<string, string>)[match]} size={20} /> :{match}: </button> ))} diff --git a/src/components/common/LocaleSelector.tsx b/src/components/common/LocaleSelector.tsx index fbebe8f114d4242090090a0b2dc0891f0fb1d3c2..7f91b1c7a3063eec5f63dd373c797dee3819af81 100644 --- a/src/components/common/LocaleSelector.tsx +++ b/src/components/common/LocaleSelector.tsx @@ -1,7 +1,7 @@ import ComboBox from "../ui/ComboBox"; import { dispatch } from "../../redux"; import { connectState } from "../../redux/connector"; -import { LanguageEntry, Languages } from "../../context/Locale"; +import { Language, LanguageEntry, Languages } from "../../context/Locale"; type Props = { locale: string; @@ -14,12 +14,12 @@ export function LocaleSelector(props: Props) { onChange={e => dispatch({ type: "SET_LOCALE", - locale: e.currentTarget.value as any + locale: e.currentTarget.value as Language }) } > {Object.keys(Languages).map(x => { - const l = (Languages as any)[x] as LanguageEntry; + const l = Languages[x as keyof typeof Languages]; return ( <option value={x}> {l.emoji} {l.display} diff --git a/src/components/common/messaging/Message.tsx b/src/components/common/messaging/Message.tsx index 46edea72c2c8db2f72856e7c296102cffc605a71..f00aad634dec4f7e7b7151039dd636dca0c4b4c2 100644 --- a/src/components/common/messaging/Message.tsx +++ b/src/components/common/messaging/Message.tsx @@ -35,7 +35,12 @@ function Message({ attachContext, message, contrast, content: replacement, head: const content = message.content as string; const head = preferHead || (message.replies && message.replies.length > 0); - const userContext = attachContext ? attachContextMenu('Menu', { user: message.author, contextualChannel: message.channel }) : undefined as any; // ! FIXME: tell fatal to make this type generic + // ! FIXME: tell fatal to make this type generic + // bree: Fatal please... + const userContext = attachContext + ? attachContextMenu('Menu', { user: message.author, contextualChannel: message.channel }) as any + : undefined; + const openProfile = () => openScreen({ id: 'profile', user_id: message.author }); return ( diff --git a/src/components/common/messaging/attachments/AttachmentActions.tsx b/src/components/common/messaging/attachments/AttachmentActions.tsx index 2009229b6ab18f4d9cf74c5eeff618467c2b6f43..8b297f4becb000814764698ea314b911d53e7186 100644 --- a/src/components/common/messaging/attachments/AttachmentActions.tsx +++ b/src/components/common/messaging/attachments/AttachmentActions.tsx @@ -19,7 +19,8 @@ export default function AttachmentActions({ attachment }: Props) { const open_url = `${url}/${filename}`; const download_url = url.replace('attachments', 'attachments/download') - const filesize = determineFileSize(size as any); + // for some reason revolt.js says the size is a string even though it's a number + const filesize = determineFileSize(size); switch (metadata.type) { case 'Image': diff --git a/src/components/markdown/Renderer.tsx b/src/components/markdown/Renderer.tsx index 115cad68d749154929f55e41c854611ab7fcae52..61575a8650f59c71ce6897bd9e8a9ac2d7a34f83 100644 --- a/src/components/markdown/Renderer.tsx +++ b/src/components/markdown/Renderer.tsx @@ -22,13 +22,21 @@ import MarkdownSup from "markdown-it-sup"; // @ts-ignore import MarkdownSub from "markdown-it-sub"; +// TODO: global.d.ts file for defining globals +declare global { + interface Window { + copycode: (element: HTMLDivElement) => void + } +} + + // Handler for code block copy. if (typeof window !== "undefined") { - (window as any).copycode = function(element: HTMLDivElement) { + window.copycode = function(element: HTMLDivElement) { try { let code = element.parentElement?.parentElement?.children[1]; if (code) { - navigator.clipboard.writeText((code as any).innerText.trim()); + navigator.clipboard.writeText(code.textContent?.trim() ?? ''); } } catch (e) {} }; @@ -65,10 +73,17 @@ const defaultRender = return self.renderToken(tokens, idx, options); }; +// TODO: global.d.ts file for defining globals +declare global { + interface Window { + internalHandleURL: (element: HTMLAnchorElement) => void + } +} + // Handler for internal links, pushes events to React using magic. if (typeof window !== "undefined") { - (window as any).internalHandleURL = function(element: HTMLAnchorElement) { - const url = new URL(element.href, location as any); + window.internalHandleURL = function(element: HTMLAnchorElement) { + const url = new URL(element.href, location.href); const pathname = url.pathname; if (pathname.startsWith("/@")) { @@ -87,7 +102,7 @@ md.renderer.rules.link_open = function(tokens, idx, options, env, self) { // For internal links, we should use our own handler to use react-router history. // @ts-ignore const href = tokens[idx].attrs[hIndex][1]; - const url = new URL(href, location as any); + const url = new URL(href, location.href); if (url.hostname === location.hostname) { internal = true; @@ -161,7 +176,7 @@ export default function Renderer({ content, disallowBigEmoji }: MarkdownProps) { data-large-emojis={useLargeEmojis} onClick={ev => { if (ev.target) { - let element: Element = ev.target as any; + let element = ev.currentTarget; if (element.classList.contains("spoiler")) { element.classList.add("shown"); } diff --git a/src/components/navigation/right/MemberSidebar.tsx b/src/components/navigation/right/MemberSidebar.tsx index 303b86c00d3644b202b3f7f8ba222e70dfd0890f..40be8041dd0fcae9d11a6313c9a0a680bdadff88 100644 --- a/src/components/navigation/right/MemberSidebar.tsx +++ b/src/components/navigation/right/MemberSidebar.tsx @@ -56,12 +56,8 @@ export function GroupMemberSidebar({ channel, ctx }: Props & { channel: Channels members.sort((a, b) => { // ! FIXME: should probably rewrite all this code - let l = ((a.online && - a.status?.presence !== Users.Presence.Invisible) ?? - false) as any | 0; - let r = ((b.online && - b.status?.presence !== Users.Presence.Invisible) ?? - false) as any | 0; + let l = +((a.online && a.status?.presence !== Users.Presence.Invisible) ?? false) | 0; + let r = +((b.online && b.status?.presence !== Users.Presence.Invisible) ?? false) | 0; let n = r - l; if (n !== 0) { @@ -161,12 +157,8 @@ export function ServerMemberSidebar({ channel, ctx }: Props & { channel: Channel // copy paste from above users.sort((a, b) => { // ! FIXME: should probably rewrite all this code - let l = ((a.online && - a.status?.presence !== Users.Presence.Invisible) ?? - false) as any | 0; - let r = ((b.online && - b.status?.presence !== Users.Presence.Invisible) ?? - false) as any | 0; + let l = +((a.online && a.status?.presence !== Users.Presence.Invisible) ?? false) | 0; + let r = +((b.online && b.status?.presence !== Users.Presence.Invisible) ?? false) | 0; let n = r - l; if (n !== 0) { diff --git a/src/context/Locale.tsx b/src/context/Locale.tsx index 873d57a67fff81de7aa71512c5f825868813ecb3..6fdff9e216a3173b2e8e6eedac9b9744d9d70d6e 100644 --- a/src/context/Locale.tsx +++ b/src/context/Locale.tsx @@ -114,9 +114,11 @@ interface Props { } function Locale({ children, locale }: Props) { - const [defns, setDefinition] = useState(definition); + // TODO: create and use LanguageDefinition type here + const [defns, setDefinition] = useState<Record<string, unknown>>(definition); const lang = Languages[locale]; + // TODO: clean this up and use the built in Intl API function transformLanguage(source: { [key: string]: any }) { const obj = defaultsDeep(source, definition); diff --git a/src/context/Settings.tsx b/src/context/Settings.tsx index 7f834feaeeb723d63bccf2373655ffdc74d4c011..3653b93e772ada8c62710b82ada757949047e955 100644 --- a/src/context/Settings.tsx +++ b/src/context/Settings.tsx @@ -15,8 +15,8 @@ import { Children } from "../types/Preact"; import { createContext } from "preact"; import { useMemo } from "preact/hooks"; -export const SettingsContext = createContext<Settings>({} as any); -export const SoundContext = createContext<(sound: Sounds) => void>({} as any); +export const SettingsContext = createContext<Settings>({}); +export const SoundContext = createContext<((sound: Sounds) => void)>(null!); interface Props { children?: Children, diff --git a/src/context/Theme.tsx b/src/context/Theme.tsx index 51f425545daf0e49e4c8ca743bbc59ba80ce4386..8a9fc54e11c32954e398f3ab4826726eb64376ef 100644 --- a/src/context/Theme.tsx +++ b/src/context/Theme.tsx @@ -30,7 +30,15 @@ export type Variables = | "status-away" | "status-busy" | "status-streaming" - | "status-invisible"; + | "status-invisible" + +// While this isn't used, it'd be good to keep this up to date as a reference or for future use +export type HiddenVariables = + | "font" + | "ligatures" + | "app-height" + | "sidebar-active" + | "monospace-font" export type Fonts = 'Open Sans' | 'Inter' | 'Atkinson Hyperlegible' | 'Roboto' | 'Noto Sans' | 'Lato' | 'Bree Serif' | 'Montserrat' | 'Poppins' | 'Raleway' | 'Ubuntu' | 'Comic Neue'; export type MonoscapeFonts = 'Fira Code' | 'Roboto Mono' | 'Source Code Pro' | 'Space Mono' | 'Ubuntu Mono'; @@ -191,7 +199,7 @@ export const DEFAULT_FONT = 'Open Sans'; export const DEFAULT_MONO_FONT = 'Fira Code'; // Generated from https://gitlab.insrt.uk/revolt/community/themes -export const PRESETS: { [key: string]: Theme } = { +export const PRESETS: Record<string, Theme> = { light: { light: true, accent: "#FD6671", @@ -217,7 +225,7 @@ export const PRESETS: { [key: string]: Theme } = { "status-away": "#F39F00", "status-busy": "#F84848", "status-streaming": "#977EFF", - "status-invisible": "#A5A5A5", + "status-invisible": "#A5A5A5" }, dark: { light: false, @@ -244,7 +252,7 @@ export const PRESETS: { [key: string]: Theme } = { "status-away": "#F39F00", "status-busy": "#F84848", "status-streaming": "#977EFF", - "status-invisible": "#A5A5A5", + "status-invisible": "#A5A5A5" }, }; @@ -259,7 +267,8 @@ const GlobalTheme = createGlobalStyle<{ theme: Theme }>` } `; -export const ThemeContext = createContext<Theme>({} as any); +// Load the default default them and apply extras later +export const ThemeContext = createContext<Theme>(PRESETS['dark']); interface Props { children: Children; @@ -269,7 +278,7 @@ interface Props { function Theme({ children, options }: Props) { const theme: Theme = { ...PRESETS["dark"], - ...(PRESETS as any)[options?.preset as any], + ...PRESETS[options?.preset ?? ''], ...options?.custom }; diff --git a/src/context/Voice.tsx b/src/context/Voice.tsx index ac5c40777bf1b5a92b01b643128a03d93b8da519..6190c363d31c14909d7032f418fe27ba23b8e05b 100644 --- a/src/context/Voice.tsx +++ b/src/context/Voice.tsx @@ -33,8 +33,9 @@ export interface VoiceState { participants?: Readonly<Map<string, VoiceUser>>; } -export const VoiceContext = createContext<VoiceState>(undefined as any); -export const VoiceOperationsContext = createContext<VoiceOperations>(undefined as any); +// They should be present from first render. - insert's words +export const VoiceContext = createContext<VoiceState>(null!); +export const VoiceOperationsContext = createContext<VoiceOperations>(null!); type Props = { children: Children; diff --git a/src/context/intermediate/modals/Onboarding.tsx b/src/context/intermediate/modals/Onboarding.tsx index 8f3894d8c2132af5e3eb96c81cc9ab12212046cf..3f328191a9f8bbb804cecdef303f6569c9b428b5 100644 --- a/src/context/intermediate/modals/Onboarding.tsx +++ b/src/context/intermediate/modals/Onboarding.tsx @@ -1,6 +1,6 @@ import { Text } from "preact-i18n"; import { useState } from "preact/hooks"; -import { useForm } from "react-hook-form"; +import { SubmitHandler, useForm } from "react-hook-form"; import styles from "./Onboarding.module.scss"; import { takeError } from "../../revoltjs/util"; import Button from "../../../components/ui/Button"; @@ -14,12 +14,16 @@ interface Props { callback: (username: string, loginAfterSuccess?: true) => Promise<void>; } +interface FormInputs { + username: string +} + export function OnboardingModal({ onClose, callback }: Props) { - const { handleSubmit, register } = useForm(); + const { handleSubmit, register } = useForm<FormInputs>(); const [loading, setLoading] = useState(false); const [error, setError] = useState<string | undefined>(undefined); - async function onSubmit({ username }: { username: string }) { + const onSubmit: SubmitHandler<FormInputs> = ({ username }) => { setLoading(true); callback(username, true) .then(onClose) @@ -45,7 +49,7 @@ export function OnboardingModal({ onClose, callback }: Props) { <p> <Text id="app.special.modals.onboarding.pick" /> </p> - <form onSubmit={handleSubmit(onSubmit) as any}> + <form onSubmit={handleSubmit(onSubmit) as JSX.GenericEventHandler<HTMLFormElement>}> <div> <FormField type="username" diff --git a/src/context/intermediate/popovers/ModifyAccount.tsx b/src/context/intermediate/popovers/ModifyAccount.tsx index 2deb2b8cef6d7a3578310d2856ac416218309941..25477c0768b99a61149b2c12d8103afca363fb9f 100644 --- a/src/context/intermediate/popovers/ModifyAccount.tsx +++ b/src/context/intermediate/popovers/ModifyAccount.tsx @@ -1,5 +1,5 @@ import { Text } from "preact-i18n"; -import { useForm } from "react-hook-form"; +import { SubmitHandler, useForm } from "react-hook-form"; import Modal from "../../../components/ui/Modal"; import { takeError } from "../../revoltjs/util"; import { useContext, useState } from "preact/hooks"; @@ -12,22 +12,28 @@ interface Props { field: "username" | "email" | "password"; } +interface FormInputs { + password: string, + new_email: string, + new_username: string, + new_password: string, + + // TODO: figure out if this is correct or not + // it wasn't in the types before this was typed but the element itself was there + current_password?: string +} + export function ModifyAccountModal({ onClose, field }: Props) { const client = useContext(AppContext); - const { handleSubmit, register, errors } = useForm(); + const { handleSubmit, register, errors } = useForm<FormInputs>(); const [error, setError] = useState<string | undefined>(undefined); - async function onSubmit({ + const onSubmit: SubmitHandler<FormInputs> = async ({ password, new_username, new_email, new_password - }: { - password: string; - new_username: string; - new_email: string; - new_password: string; - }) { + }) => { try { if (field === "email") { await client.req("POST", "/auth/change/email", { @@ -75,7 +81,8 @@ export function ModifyAccountModal({ onClose, field }: Props) { } ]} > - <form onSubmit={handleSubmit(onSubmit) as any}> + {/* Preact / React typing incompatabilities */} + <form onSubmit={handleSubmit(onSubmit) as JSX.GenericEventHandler<HTMLFormElement>}> {field === "email" && ( <FormField type="email" diff --git a/src/context/revoltjs/FileUploads.tsx b/src/context/revoltjs/FileUploads.tsx index 74fa2753c45f95bd8a3cb082b474f40831c50ae2..87781e2f2ef9cb78b2c59dc995010a693446db34 100644 --- a/src/context/revoltjs/FileUploads.tsx +++ b/src/context/revoltjs/FileUploads.tsx @@ -44,8 +44,8 @@ export function grabFiles(maxFileSize: number, cb: (files: File[]) => void, tooL input.type = "file"; input.multiple = multiple ?? false; - input.onchange = async e => { - const files = (e.target as any)?.files; + input.onchange = async (e) => { + const files = (e.currentTarget as HTMLInputElement)?.files; if (!files) return; for (let file of files) { if (file.size > maxFileSize) { diff --git a/src/context/revoltjs/Notifications.tsx b/src/context/revoltjs/Notifications.tsx index dc90617beb3d85594158cb5468bfdd88ad74fcee..4ae6c74a61da7fd3fcf025d3c8aa0ff020847673 100644 --- a/src/context/revoltjs/Notifications.tsx +++ b/src/context/revoltjs/Notifications.tsx @@ -216,7 +216,7 @@ function Notifier({ options, notifs }: Props) { document.removeEventListener("visibilitychange", visChange); }, [guild_id, channel_id]); - return <></>; + return null; } const NotifierComponent = connectState( diff --git a/src/context/revoltjs/RevoltClient.tsx b/src/context/revoltjs/RevoltClient.tsx index ea33dfb15d0c5333906ec200918f01cb73bd0be6..91dc602f0bdc2724b4b55fdf271b6a4fe4edc0d4 100644 --- a/src/context/revoltjs/RevoltClient.tsx +++ b/src/context/revoltjs/RevoltClient.tsx @@ -34,9 +34,12 @@ export interface ClientOperations { openDM: (user_id: string) => Promise<string>; } -export const AppContext = createContext<Client>(undefined as any); -export const StatusContext = createContext<ClientStatus>(undefined as any); -export const OperationsContext = createContext<ClientOperations>(undefined as any); +// By the time they are used, they should all be initialized. +// Currently the app does not render until a client is built and the other two are always initialized on first render. +// - insert's words +export const AppContext = createContext<Client>(null!); +export const StatusContext = createContext<ClientStatus>(null!); +export const OperationsContext = createContext<ClientOperations>(null!); type Props = { auth: AuthState; @@ -93,16 +96,14 @@ function Context({ auth, children }: Props) { const login = () => dispatch({ type: "LOGIN", - session: client.session as any + session: client.session! // This [null assertion] is ok, we should have a session by now. - insert's words }); if (onboarding) { openScreen({ id: "onboarding", - callback: async (username: string) => { - await (onboarding as any)(username, true); - login(); - } + callback: (username: string) => + onboarding(username, true).then(login) }); } else { login(); diff --git a/src/context/revoltjs/StateMonitor.tsx b/src/context/revoltjs/StateMonitor.tsx index bffdfdbb09225a0376b2894220ae2cfec971d353..cc0d7616196af52da5a2233eb48ddb8400c96164 100644 --- a/src/context/revoltjs/StateMonitor.tsx +++ b/src/context/revoltjs/StateMonitor.tsx @@ -63,7 +63,7 @@ function StateMonitor(props: Props) { return () => clearInterval(interval); }, [ props.typing ]); - return <></>; + return null; } export default connectState( diff --git a/src/context/revoltjs/SyncManager.tsx b/src/context/revoltjs/SyncManager.tsx index 285713f3ceb5dacafae4e20f8fcdfa4c9d187390..f09ffb0d1ca7477e4c715085267f541b85f680e5 100644 --- a/src/context/revoltjs/SyncManager.tsx +++ b/src/context/revoltjs/SyncManager.tsx @@ -23,11 +23,11 @@ type Props = { var lastValues: { [key in SyncKeys]?: any } = { }; -export function mapSync(packet: Sync.UserSettings, revision?: { [key: string]: number }) { +export function mapSync(packet: Sync.UserSettings, revision?: Record<string, number>) { let update: { [key in SyncKeys]?: [ number, SyncData[key] ] } = {}; for (let key of Object.keys(packet)) { let [ timestamp, obj ] = packet[key]; - if (timestamp < (revision ?? {} as any)[key] ?? 0) { + if (timestamp < (revision ?? {})[key] ?? 0) { continue; } @@ -110,7 +110,7 @@ function SyncManager(props: Props) { return () => client.removeListener('packet', onPacket); }, [ disabled, props.sync ]); - return <></>; + return null; } export default connectState( diff --git a/src/context/revoltjs/events.ts b/src/context/revoltjs/events.ts index 089415124144407a18d1025f17c5d3229e37bed5..319894689955981271fff384005383b3855eee60 100644 --- a/src/context/revoltjs/events.ts +++ b/src/context/revoltjs/events.ts @@ -31,7 +31,7 @@ export function registerEvents({ } } - const listeners = { + let listeners: Record<string, (...args: any[]) => void> = { connecting: () => operations.ready() && setStatus(ClientStatus.CONNECTING), @@ -86,21 +86,18 @@ export function registerEvents({ ready: () => setStatus(ClientStatus.ONLINE) }; - let listenerFunc: { [key: string]: Function }; if (import.meta.env.DEV) { - listenerFunc = {}; - for (const listener of Object.keys(listeners)) { - listenerFunc[listener] = (...args: any[]) => { - console.debug(`Calling ${listener} with`, args); - (listeners as any)[listener](...args); - }; - } - } else { - listenerFunc = listeners; + listeners = new Proxy(listeners, { + get: (target, listener, receiver) => (...args: unknown[]) => { + console.debug(`Calling ${listener.toString()} with`, args); + Reflect.get(target, listener)(...args) + } + }) } - for (const listener of Object.keys(listenerFunc)) { - client.addListener(listener, (listenerFunc as any)[listener]); + // TODO: clean this a bit and properly handle types + for (const listener in listeners) { + client.addListener(listener, listeners[listener]); } function logMutation(target: string, key: string) { @@ -134,8 +131,8 @@ export function registerEvents({ window.addEventListener("offline", offline); return () => { - for (const listener of Object.keys(listenerFunc)) { - client.removeListener(listener, (listenerFunc as any)[listener]); + for (const listener in listeners) { + client.removeListener(listener, listeners[listener as keyof typeof listeners]); } if (import.meta.env.DEV) { diff --git a/src/context/revoltjs/hooks.ts b/src/context/revoltjs/hooks.ts index f57038bbd12bfc7bd6d12736ea79bef67c93a9fc..fac889862a8075df4e1e85707062b9e09d06a172 100644 --- a/src/context/revoltjs/hooks.ts +++ b/src/context/revoltjs/hooks.ts @@ -2,6 +2,7 @@ import { useCallback, useContext, useEffect, useState } from "preact/hooks"; import { Channels, Servers, Users } from "revolt.js/dist/api/objects"; import { Client, PermissionCalculator } from 'revolt.js'; import { AppContext } from "./RevoltClient"; +import Collection from "revolt.js/dist/maps/Collection"; export interface HookContext { client: Client, @@ -25,7 +26,16 @@ export function useForceUpdate(context?: HookContext): HookContext { return { client, forceUpdate: () => updateState(Math.random()) }; } -function useObject(type: string, id?: string | string[], context?: HookContext) { +// TODO: utils.d.ts maybe? +type PickProperties<T, U> = Pick<T, { + [K in keyof T]: T[K] extends U ? K : never +}[keyof T]> + +// The keys in Client that are an object +// for some reason undefined keeps appearing despite there being no reason to so it's filtered out +type ClientCollectionKey = Exclude<keyof PickProperties<Client, Collection<any>>, undefined>; + +function useObject(type: ClientCollectionKey, id?: string | string[], context?: HookContext) { const ctx = useForceUpdate(context); function update(target: any) { @@ -35,7 +45,7 @@ function useObject(type: string, id?: string | string[], context?: HookContext) } } - const map = (ctx.client as any)[type]; + const map = ctx.client[type]; useEffect(() => { map.addListener("update", update); return () => map.removeListener("update", update); diff --git a/src/lib/ContextMenus.tsx b/src/lib/ContextMenus.tsx index 5c5769f9ce43efea5c025a5bf62d78906555983d..89f1a6bb87f906c6cde55a35b4ab5b7590a13251 100644 --- a/src/lib/ContextMenus.tsx +++ b/src/lib/ContextMenus.tsx @@ -307,11 +307,15 @@ function ContextMenus(props: Props) { case "delete_server": case "delete_message": case "create_channel": - // @ts-expect-error - case "create_invite": openScreen({ id: "special_prompt", type: data.action, target: data.target }); break; + case "create_invite": + // The any here is because typescript flattens the case types into a single type and type structure and specifity is lost or whatever + openScreen({ id: "special_prompt", type: data.action, target: data.target as any }); + break; case "ban_member": - case "kick_member": openScreen({ id: "special_prompt", type: data.action, target: data.target, user: data.user }); break; + case "kick_member": + openScreen({ id: "special_prompt", type: data.action, target: data.target, user: data.user }); + break; case "open_notification_options": { openContextMenu("NotificationOptions", { channel: data.channel }); @@ -427,7 +431,7 @@ function ContextMenus(props: Props) { } if (user) { - let actions: string[]; + let actions: Action['action'][]; switch (user.relationship) { case Users.Relationship.User: actions = []; break; case Users.Relationship.Friend: @@ -461,11 +465,9 @@ function ContextMenus(props: Props) { generateAction({ action: 'message_user', user: user._id }); } - for (const action of actions) { - generateAction({ - action: action as any, - user - }); + for(let i = 0; i < actions.length; i++) { + // The any here is because typescript can't determine that user the actions are linked together correctly + generateAction({ action: actions[i] as any, user }) } } diff --git a/src/pages/login/FormField.tsx b/src/pages/login/FormField.tsx index eb7f498899a4e8598ec0c842fe26fa7ebfa2d7a7..94a36098d7bda22f459b56d4d2bf3da804f1067b 100644 --- a/src/pages/login/FormField.tsx +++ b/src/pages/login/FormField.tsx @@ -26,6 +26,8 @@ export default function FormField({ )} <Localizer> <InputBox + // Styled uses React typing while we use Preact + // this leads to inconsistances where things need to be typed oddly placeholder={(<Text id={`login.enter.${type}`} />) as any} name={ type === "current_password" ? "password" : name ?? type diff --git a/src/pages/login/forms/Form.tsx b/src/pages/login/forms/Form.tsx index d1e0e8c4bf29cad75255770210feece26f2f49e1..66791ec3529280d13ae99278b1e1b0331b1f00c7 100644 --- a/src/pages/login/forms/Form.tsx +++ b/src/pages/login/forms/Form.tsx @@ -35,6 +35,12 @@ function getInviteCode() { return code ?? ''; } +interface FormInputs { + email: string + password: string + invite: string +} + export function Form({ page, callback }: Props) { const client = useContext(AppContext); @@ -43,7 +49,7 @@ export function Form({ page, callback }: Props) { const [error, setGlobalError] = useState<string | undefined>(undefined); const [captcha, setCaptcha] = useState<CaptchaProps | undefined>(undefined); - const { handleSubmit, register, errors, setError } = useForm({ + const { handleSubmit, register, errors, setError } = useForm<FormInputs>({ defaultValues: { email: '', password: '', @@ -51,11 +57,7 @@ export function Form({ page, callback }: Props) { } }); - async function onSubmit(data: { - email: string; - password: string; - invite: string; - }) { + async function onSubmit(data: FormInputs) { setGlobalError(undefined); setLoading(true); @@ -143,7 +145,8 @@ export function Form({ page, callback }: Props) { return ( <div className={styles.form}> <img src={wideSVG} /> - <form onSubmit={handleSubmit(onSubmit) as any}> + {/* Preact / React typing incompatabilities */} + <form onSubmit={handleSubmit(onSubmit) as JSX.GenericEventHandler<HTMLFormElement>}> {page !== "reset" && ( <FormField type="email" diff --git a/src/pages/login/forms/FormReset.tsx b/src/pages/login/forms/FormReset.tsx index a8a7886b903e35cd55fefe7c859dee4400e6dd72..ac73d0186563e03c1e62b9dd4208877a9c8cca17 100644 --- a/src/pages/login/forms/FormReset.tsx +++ b/src/pages/login/forms/FormReset.tsx @@ -25,9 +25,9 @@ export function FormReset() { <Form page="reset" callback={async data => { - await client.req("POST", "/auth/reset" as any, { + await client.req("POST", "/auth/reset", { token, - ...(data as any) + ...data }); history.push("/login"); }} diff --git a/src/pages/settings/panes/Account.tsx b/src/pages/settings/panes/Account.tsx index 7a849e0e45e01b704ba38064b384fbe7fc0c6bbb..88a7839c70b7e53a71451de484fb3a5624ed129d 100644 --- a/src/pages/settings/panes/Account.tsx +++ b/src/pages/settings/panes/Account.tsx @@ -52,11 +52,11 @@ export function Account() { <div className={styles.username}>@{user.username}</div> </div> <div className={styles.details}> - {[ + {([ ["username", user.username, <At size={24} />], ["email", email, <Envelope size={24} />], ["password", "*****", <Key size={24} />] - ].map(([field, value, icon]) => ( + ] as const).map(([field, value, icon]) => ( <div> {icon} <div className={styles.detail}> @@ -70,7 +70,7 @@ export function Account() { onClick={() => openScreen({ id: "modify_account", - field: field as any + field: field }) } contrast diff --git a/src/pages/settings/panes/Appearance.tsx b/src/pages/settings/panes/Appearance.tsx index 58426b3199ac555deedb19ba74ed74aa782a5723..714e4086ec8a7bb04743dd6d2245236d5ee2886a 100644 --- a/src/pages/settings/panes/Appearance.tsx +++ b/src/pages/settings/panes/Appearance.tsx @@ -226,7 +226,7 @@ export function Component(props: Props) { </Button> </div> <div className={styles.overrides}> - {[ + {([ "accent", "background", "foreground", @@ -240,7 +240,6 @@ export function Component(props: Props) { "block", "message-box", "mention", - "sidebar-active", "scrollbar-thumb", "scrollbar-track", "status-online", @@ -252,15 +251,15 @@ export function Component(props: Props) { "warning", "error", "hover" - ].map(x => ( + ] as const).map(x => ( <div className={styles.entry} key={x}> <span>{x}</span> <div className={styles.override}> <div className={styles.picker} - style={{ backgroundColor: (theme as any)[x as any] }}> + style={{ backgroundColor: theme[x] }}> <input type="color" - value={(theme as any)[x as any]} + value={theme[x]} onChange={v => setOverride({ [x]: v.currentTarget.value @@ -270,7 +269,7 @@ export function Component(props: Props) { </div> <InputBox className={styles.text} - value={(theme as any)[x as any]} + value={theme[x]} onChange={y => setOverride({ [x]: y.currentTarget.value diff --git a/src/pages/settings/panes/Notifications.tsx b/src/pages/settings/panes/Notifications.tsx index ae3c165780a67aa852eef7d0cce66f52dbef9a6d..5098c85e9266c9291ea5b9c981e0ce9757c32aee 100644 --- a/src/pages/settings/panes/Notifications.tsx +++ b/src/pages/settings/panes/Notifications.tsx @@ -77,11 +77,11 @@ export function Component({ options }: Props) { // tell the server we just subscribed const json = sub.toJSON(); - if (json.keys) { + if (json.keys) {; client.req("POST", "/push/subscribe", { endpoint: sub.endpoint, - ...json.keys - } as any); + ...(json.keys as { p256dh: string, auth: string }) + }); setPushEnabled(true); } } else { diff --git a/src/pages/settings/panes/Profile.tsx b/src/pages/settings/panes/Profile.tsx index 941d311d1b3a880de03bddfb8a3895b00b18226e..1b6fa429e0a61b1a9c0679033aad07dd6a367e13 100644 --- a/src/pages/settings/panes/Profile.tsx +++ b/src/pages/settings/panes/Profile.tsx @@ -11,7 +11,7 @@ import { ClientStatus, StatusContext } from "../../../context/revoltjs/RevoltCli import AutoComplete, { useAutoComplete } from "../../../components/common/AutoComplete"; export function Profile() { - const { intl } = useContext(IntlContext) as any; + const { intl } = useContext(IntlContext); const status = useContext(StatusContext); const ctx = useForceUpdate(); @@ -121,7 +121,7 @@ export function Profile() { : "placeholder" }`, "", - intl.dictionary + (intl as any).dictionary as Record<string, unknown> )} onKeyUp={onKeyUp} onKeyDown={onKeyDown} diff --git a/src/pages/settings/panes/Sessions.tsx b/src/pages/settings/panes/Sessions.tsx index b9bccc4b9767a7a89ed960a081466d595392fb28..2ec357be7e352aea5d0ad1676eaa554f2b29e7d6 100644 --- a/src/pages/settings/panes/Sessions.tsx +++ b/src/pages/settings/panes/Sessions.tsx @@ -155,7 +155,7 @@ export function Sessions() { ]); await client.req( "DELETE", - `/auth/sessions/${session.id}` as any + `/auth/sessions/${session.id}` as '/auth/sessions' ); setSessions( sessions?.filter( diff --git a/yarn.lock b/yarn.lock index 9362c74f3ae5c17a53db45780b63bd428793f403..6fec18642ef22fc373748e1f54afa9d80e866547 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3590,10 +3590,10 @@ reusify@^1.0.4: resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== -revolt.js@4.3.3-alpha.7: - version "4.3.3-alpha.7" - resolved "https://registry.yarnpkg.com/revolt.js/-/revolt.js-4.3.3-alpha.7.tgz#de6ecef444e8368aac3753761e2e10f516f50712" - integrity sha512-oi76A+EIxrD+tVRTU8s2LISFBpvMf0kpinw5rdukoc1VWpl0bCC6Kko26yC7lhVkWGLTZxHMOKaUkgbOgy0flA== +revolt.js@4.3.3-alpha.8: + version "4.3.3-alpha.8" + resolved "https://registry.yarnpkg.com/revolt.js/-/revolt.js-4.3.3-alpha.8.tgz#2a191ffa9d4c304e328b5eb8d9dc1e13e1f99d9a" + integrity sha512-A6sjZ7cmeQuqS9otzANv+Rg4CfvpsTMoDARBwQuez4O7NPRopdWNHylUPo20UutAPzW9xoqVbF8673VlTu5Jag== dependencies: "@insertish/mutable" "1.1.0" axios "^0.19.2"