From 9680e5784904e144444573cf994ec81d351a4a11 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Fri, 11 Oct 2024 15:46:46 -0400 Subject: [PATCH] chore: Remove `suppressImplicitAnyIndexErrors` TS rule (#7760) --- app/components/ConfirmMoveDialog.tsx | 12 ++++--- app/components/ConnectionStatus.tsx | 2 +- app/components/LocaleTime.tsx | 9 +++-- app/components/PaginatedList.tsx | 3 +- app/editor/components/EmojiMenu.tsx | 1 + app/editor/menus/code.tsx | 6 +++- app/hooks/useUserLocale.ts | 11 ++---- app/models/FileOperation.ts | 8 +++-- app/models/User.ts | 3 +- app/models/base/Model.ts | 4 ++- app/models/decorators/Relation.ts | 2 +- .../components/MembershipPreview.tsx | 8 +++-- app/scenes/Invite.tsx | 5 +-- app/scenes/Settings/Members.tsx | 4 ++- app/scenes/Settings/Shares.tsx | 4 ++- .../components/FileOperationListItem.tsx | 5 +-- app/stores/GroupMembershipsStore.ts | 10 ++++-- app/stores/MembershipsStore.ts | 10 ++++-- app/stores/RootStore.ts | 19 +++++----- app/stores/base/Store.ts | 35 +++++++++++++------ app/utils/date.ts | 5 +-- app/utils/download.ts | 2 ++ app/utils/language.ts | 4 +-- app/utils/pageVisibility.ts | 2 +- .../slack/client/components/SlackListItem.tsx | 2 +- plugins/slack/server/api/hooks.ts | 7 ++-- plugins/slack/shared/SlackUtils.ts | 2 +- .../server/tasks/DeliverWebhookTask.test.ts | 5 ++- .../server/tasks/DeliverWebhookTask.ts | 2 +- server/emails/templates/index.ts | 4 +-- server/index.ts | 4 +-- server/logging/Logger.ts | 4 +-- server/models/Collection.test.ts | 2 +- server/models/User.ts | 5 +-- server/models/base/Model.ts | 1 + server/models/decorators/CounterCache.ts | 7 ++-- server/models/helpers/SearchHelper.ts | 5 ++- server/policies/cancan.ts | 12 ++++--- server/queues/processors/BaseProcessor.ts | 2 +- server/queues/processors/index.ts | 4 +-- server/queues/tasks/EmailTask.ts | 1 + server/queues/tasks/index.ts | 4 +-- server/routes/api/collections/collections.ts | 4 ++- server/routes/api/documents/documents.ts | 4 ++- server/routes/api/users/schema.ts | 4 ++- server/routes/index.ts | 2 +- ...313000000-migrate-notification-settings.ts | 1 + server/services/index.ts | 2 +- server/services/worker.ts | 5 ++- server/utils/decorators/Public.ts | 6 ++-- server/utils/diff.ts | 27 +++++++------- server/utils/indexing.ts | 10 +++--- server/utils/readManifestFile.ts | 1 + server/utils/turndown/tables.ts | 6 +++- server/utils/validators.ts | 2 +- shared/editor/lib/Extension.ts | 4 ++- shared/editor/lib/ExtensionManager.ts | 13 ++++--- shared/editor/lib/code.ts | 10 +++--- shared/editor/lib/emoji.ts | 1 + .../getFromPath.ts | 2 +- shared/editor/nodes/Heading.ts | 2 +- shared/i18n/index.ts | 9 ++++- shared/i18n/locales/en_US/translation.json | 1 + shared/types.ts | 30 ++++++++-------- shared/utils/ProsemirrorHelper.ts | 2 +- shared/utils/Storage.ts | 2 +- shared/utils/date.ts | 2 +- shared/utils/emoji.ts | 7 ++-- shared/utils/naturalSort.ts | 4 +-- tsconfig.json | 1 - 70 files changed, 255 insertions(+), 156 deletions(-) diff --git a/app/components/ConfirmMoveDialog.tsx b/app/components/ConfirmMoveDialog.tsx index 3af9ee0cc4..e7b3371700 100644 --- a/app/components/ConfirmMoveDialog.tsx +++ b/app/components/ConfirmMoveDialog.tsx @@ -21,11 +21,13 @@ function ConfirmMoveDialog({ collection, item, ...rest }: Props) { const { documents, dialogs, collections } = useStores(); const { t } = useTranslation(); const prevCollection = collections.get(item.collectionId!); - const accessMapping = { - [CollectionPermission.ReadWrite]: t("view and edit access"), - [CollectionPermission.Read]: t("view only access"), - null: t("no access"), - }; + const accessMapping: Record | "null", string> = + { + [CollectionPermission.Admin]: t("manage access"), + [CollectionPermission.ReadWrite]: t("view and edit access"), + [CollectionPermission.Read]: t("view only access"), + null: t("no access"), + }; const handleSubmit = async () => { await documents.move({ diff --git a/app/components/ConnectionStatus.tsx b/app/components/ConnectionStatus.tsx index 41234608d2..6d79959633 100644 --- a/app/components/ConnectionStatus.tsx +++ b/app/components/ConnectionStatus.tsx @@ -35,7 +35,7 @@ function ConnectionStatus() { }; const message = ui.multiplayerErrorCode - ? codeToMessage[ui.multiplayerErrorCode] + ? codeToMessage[ui.multiplayerErrorCode as keyof typeof codeToMessage] : undefined; return ui.multiplayerStatus === "connecting" || diff --git a/app/components/LocaleTime.tsx b/app/components/LocaleTime.tsx index 53efa13318..abc1808ca6 100644 --- a/app/components/LocaleTime.tsx +++ b/app/components/LocaleTime.tsx @@ -39,12 +39,15 @@ const LocaleTime: React.FC = ({ relative, tooltipDelay, }: Props) => { - const userLocale: string = useUserLocale() || ""; - const dateFormatLong = { + const userLocale = useUserLocale(); + const dateFormatLong: Record = { en_US: "MMMM do, yyyy h:mm a", fr_FR: "'Le 'd MMMM yyyy 'à' H:mm", }; - const formatLocaleLong = dateFormatLong[userLocale] ?? "MMMM do, yyyy h:mm a"; + const formatLocaleLong = + (userLocale ? dateFormatLong[userLocale] : undefined) ?? + "MMMM do, yyyy h:mm a"; + // @ts-expect-error fallback to formatLocaleLong const formatLocale = format?.[userLocale] ?? formatLocaleLong; const [_, setMinutesMounted] = React.useState(0); // eslint-disable-line @typescript-eslint/no-unused-vars const callback = React.useRef<() => void>(); diff --git a/app/components/PaginatedList.tsx b/app/components/PaginatedList.tsx index dd8ecf474c..1784b2177f 100644 --- a/app/components/PaginatedList.tsx +++ b/app/components/PaginatedList.tsx @@ -19,7 +19,8 @@ export interface PaginatedItem { } type Props = WithTranslation & - RootStore & { + RootStore & + React.HTMLAttributes & { fetch?: ( options: Record | undefined ) => Promise | undefined; diff --git a/app/editor/components/EmojiMenu.tsx b/app/editor/components/EmojiMenu.tsx index ce8946aa87..c2e21bacbf 100644 --- a/app/editor/components/EmojiMenu.tsx +++ b/app/editor/components/EmojiMenu.tsx @@ -29,6 +29,7 @@ const EmojiMenu = (props: Props) => { .map((item) => { // We snake_case the shortcode for backwards compatability with gemoji to // avoid multiple formats being written into documents. + // @ts-expect-error emojiMartToGemoji key const shortcode = snakeCase(emojiMartToGemoji[item.id] || item.id); const emoji = item.value; diff --git a/app/editor/menus/code.tsx b/app/editor/menus/code.tsx index 509deb432f..07fa0b7da3 100644 --- a/app/editor/menus/code.tsx +++ b/app/editor/menus/code.tsx @@ -14,7 +14,10 @@ export default function codeMenuItems( ): MenuItem[] { const node = state.selection.$from.node(); - const allLanguages = Object.entries(LANGUAGES); + const allLanguages = Object.entries(LANGUAGES) as [ + keyof typeof LANGUAGES, + string + ][]; const frequentLanguages = getFrequentCodeLanguages(); const frequentLangMenuItems = frequentLanguages.map((value) => { @@ -49,6 +52,7 @@ export default function codeMenuItems( visible: !readOnly, name: "code_block", icon: , + // @ts-expect-error We have a fallback for incorrect mapping label: LANGUAGES[node.attrs.language ?? "none"], children: languageMenuItems, }, diff --git a/app/hooks/useUserLocale.ts b/app/hooks/useUserLocale.ts index 7a981589be..50bfe19fd9 100644 --- a/app/hooks/useUserLocale.ts +++ b/app/hooks/useUserLocale.ts @@ -3,16 +3,9 @@ import useCurrentUser from "./useCurrentUser"; /** * Returns the user's locale, or undefined if the user is not logged in. * - * @param languageCode Whether to only return the language code * @returns The user's locale, or undefined if the user is not logged in */ -export default function useUserLocale(languageCode?: boolean) { +export default function useUserLocale() { const user = useCurrentUser({ rejectOnEmpty: false }); - - if (!user?.language) { - return undefined; - } - - const { language } = user; - return languageCode ? language.split("_")[0] : language; + return user?.language; } diff --git a/app/models/FileOperation.ts b/app/models/FileOperation.ts index ba0bcdc716..2663efc23f 100644 --- a/app/models/FileOperation.ts +++ b/app/models/FileOperation.ts @@ -1,5 +1,9 @@ import { computed, observable } from "mobx"; -import { FileOperationFormat, FileOperationType } from "@shared/types"; +import { + FileOperationFormat, + FileOperationState, + FileOperationType, +} from "@shared/types"; import { bytesToHumanReadable } from "@shared/utils/files"; import User from "./User"; import Model from "./base/Model"; @@ -10,7 +14,7 @@ class FileOperation extends Model { id: string; @observable - state: string; + state: FileOperationState; name: string; diff --git a/app/models/User.ts b/app/models/User.ts index 175486ea5d..461b10651c 100644 --- a/app/models/User.ts +++ b/app/models/User.ts @@ -11,6 +11,7 @@ import { UserRole, } from "@shared/types"; import type { NotificationSettings } from "@shared/types"; +import { locales } from "@shared/utils/date"; import { client } from "~/utils/ApiClient"; import Document from "./Document"; import Group from "./Group"; @@ -39,7 +40,7 @@ class User extends ParanoidModel { @Field @observable - language: string; + language: keyof typeof locales; @Field @observable diff --git a/app/models/base/Model.ts b/app/models/base/Model.ts index b876d0afb5..747eb4e710 100644 --- a/app/models/base/Model.ts +++ b/app/models/base/Model.ts @@ -40,6 +40,7 @@ export default abstract class Model { * @returns A promise that resolves when loading is complete. */ async loadRelations( + this: Model, options: { withoutPolicies?: boolean } = {} ): Promise { const relations = getRelationsForModelClass( @@ -62,7 +63,7 @@ export default abstract class Model { if ("fetch" in store) { const id = this[properties.idKey]; if (id) { - promises.push(store.fetch(id)); + promises.push(store.fetch(id as string)); } } } @@ -145,6 +146,7 @@ export default abstract class Model { if (key === "initialized") { continue; } + // @ts-expect-error TODO this[key] = data[key]; } catch (error) { Logger.warn(`Error setting ${key} on model`, error); diff --git a/app/models/decorators/Relation.ts b/app/models/decorators/Relation.ts index b83d6467f3..5a3f22b5cc 100644 --- a/app/models/decorators/Relation.ts +++ b/app/models/decorators/Relation.ts @@ -17,7 +17,7 @@ type RelationOptions = { type RelationProperties = { /** The name of the property on the model that stores the ID of the relation. */ - idKey: string; + idKey: keyof T; /** A function that returns the class of the relation. */ relationClassResolver: () => typeof Model; /** Options for the relation. */ diff --git a/app/scenes/Collection/components/MembershipPreview.tsx b/app/scenes/Collection/components/MembershipPreview.tsx index 942abf8aea..1c980a57ba 100644 --- a/app/scenes/Collection/components/MembershipPreview.tsx +++ b/app/scenes/Collection/components/MembershipPreview.tsx @@ -43,8 +43,12 @@ const MembershipPreview = ({ collection, limit = 8 }: Props) => { memberships.fetchPage(options), groupMemberships.fetchPage(options), ]); - setUsersCount(users[PAGINATION_SYMBOL].total); - setGroupsCount(groups[PAGINATION_SYMBOL].total); + if (users[PAGINATION_SYMBOL]) { + setUsersCount(users[PAGINATION_SYMBOL].total); + } + if (groups[PAGINATION_SYMBOL]) { + setGroupsCount(groups[PAGINATION_SYMBOL].total); + } } finally { setIsLoading(false); } diff --git a/app/scenes/Invite.tsx b/app/scenes/Invite.tsx index fb74cd1902..0d4e36be60 100644 --- a/app/scenes/Invite.tsx +++ b/app/scenes/Invite.tsx @@ -71,10 +71,11 @@ function Invite({ onSubmit }: Props) { [onSubmit, invites, role, t, users] ); - const handleChange = React.useCallback((ev, index) => { + const handleChange = React.useCallback((ev, index: number) => { setInvites((prevInvites) => { const newInvites = [...prevInvites]; - newInvites[index][ev.target.name] = ev.target.value; + newInvites[index][ev.target.name as keyof InviteRequest] = + ev.target.value; return newInvites; }); }, []); diff --git a/app/scenes/Settings/Members.tsx b/app/scenes/Settings/Members.tsx index 53b22ecafb..48a496dc03 100644 --- a/app/scenes/Settings/Members.tsx +++ b/app/scenes/Settings/Members.tsx @@ -62,7 +62,9 @@ function Members() { filter, role, }); - setTotalPages(Math.ceil(response[PAGINATION_SYMBOL].total / limit)); + if (response[PAGINATION_SYMBOL]) { + setTotalPages(Math.ceil(response[PAGINATION_SYMBOL].total / limit)); + } setUserIds(response.map((u: User) => u.id)); } finally { setIsLoading(false); diff --git a/app/scenes/Settings/Shares.tsx b/app/scenes/Settings/Shares.tsx index 7d8fd29707..0b9c1e1459 100644 --- a/app/scenes/Settings/Shares.tsx +++ b/app/scenes/Settings/Shares.tsx @@ -47,7 +47,9 @@ function Shares() { sort, direction, }); - setTotalPages(Math.ceil(response[PAGINATION_SYMBOL].total / limit)); + if (response[PAGINATION_SYMBOL]) { + setTotalPages(Math.ceil(response[PAGINATION_SYMBOL].total / limit)); + } setShareIds(response.map((u: Share) => u.id)); } finally { setIsLoading(false); diff --git a/app/scenes/Settings/components/FileOperationListItem.tsx b/app/scenes/Settings/components/FileOperationListItem.tsx index bd1600a9da..8e5e932276 100644 --- a/app/scenes/Settings/components/FileOperationListItem.tsx +++ b/app/scenes/Settings/components/FileOperationListItem.tsx @@ -38,7 +38,7 @@ const FileOperationListItem = ({ fileOperation }: Props) => { [FileOperationState.Error]: t("Failed"), }; - const iconMapping = { + const iconMapping: Record = { [FileOperationState.Creating]: , [FileOperationState.Uploading]: , [FileOperationState.Expired]: , @@ -46,8 +46,9 @@ const FileOperationListItem = ({ fileOperation }: Props) => { [FileOperationState.Error]: , }; - const formatMapping = { + const formatMapping: Record = { [FileOperationFormat.JSON]: "JSON", + [FileOperationFormat.Notion]: "Notion", [FileOperationFormat.MarkdownZip]: "Markdown", [FileOperationFormat.HTMLZip]: "HTML", [FileOperationFormat.PDF]: "PDF", diff --git a/app/stores/GroupMembershipsStore.ts b/app/stores/GroupMembershipsStore.ts index 7624c6943f..ec0cd90a70 100644 --- a/app/stores/GroupMembershipsStore.ts +++ b/app/stores/GroupMembershipsStore.ts @@ -5,7 +5,11 @@ import GroupMembership from "~/models/GroupMembership"; import { PaginationParams } from "~/types"; import { client } from "~/utils/ApiClient"; import RootStore from "./RootStore"; -import Store, { PAGINATION_SYMBOL, RPCAction } from "./base/Store"; +import Store, { + PAGINATION_SYMBOL, + PaginatedResponse, + RPCAction, +} from "./base/Store"; export default class GroupMembershipsStore extends Store { actions = [RPCAction.Create, RPCAction.Delete]; @@ -24,7 +28,7 @@ export default class GroupMembershipsStore extends Store { documentId?: string; collectionId?: string; groupId?: string; - }): Promise => { + }): Promise> => { this.isFetching = true; try { @@ -41,7 +45,7 @@ export default class GroupMembershipsStore extends Store { : await client.post(`/groupMemberships.list`, params); invariant(res?.data, "Data not available"); - let response: GroupMembership[] = []; + let response: PaginatedResponse = []; runInAction(`GroupMembershipsStore#fetchPage`, () => { res.data.groups?.forEach(this.rootStore.groups.add); res.data.documents?.forEach(this.rootStore.documents.add); diff --git a/app/stores/MembershipsStore.ts b/app/stores/MembershipsStore.ts index 6210d74427..61cb4b8de2 100644 --- a/app/stores/MembershipsStore.ts +++ b/app/stores/MembershipsStore.ts @@ -5,7 +5,11 @@ import Membership from "~/models/Membership"; import { PaginationParams } from "~/types"; import { client } from "~/utils/ApiClient"; import RootStore from "./RootStore"; -import Store, { PAGINATION_SYMBOL, RPCAction } from "./base/Store"; +import Store, { + PAGINATION_SYMBOL, + PaginatedResponse, + RPCAction, +} from "./base/Store"; export default class MembershipsStore extends Store { actions = [RPCAction.Create, RPCAction.Delete]; @@ -17,14 +21,14 @@ export default class MembershipsStore extends Store { @action fetchPage = async ( params: (PaginationParams & { id?: string }) | undefined - ): Promise => { + ): Promise> => { this.isFetching = true; try { const res = await client.post(`/collections.memberships`, params); invariant(res?.data, "Data not available"); - let response: Membership[] = []; + let response: PaginatedResponse = []; runInAction(`MembershipsStore#fetchPage`, () => { res.data.users.forEach(this.rootStore.users.add); response = res.data.memberships.map(this.add); diff --git a/app/stores/RootStore.ts b/app/stores/RootStore.ts index 297abde26a..3182f5618f 100644 --- a/app/stores/RootStore.ts +++ b/app/stores/RootStore.ts @@ -102,14 +102,11 @@ export default class RootStore { * * @param modelName */ - public getStoreForModelName( - modelName: string - ): RootStore[K] { + public getStoreForModelName(modelName: string) { const storeName = this.getStoreNameForModelName(modelName); const store = this[storeName]; invariant(store, `No store found for model name "${modelName}"`); - - return store; + return store as RootStore[K]; } /** @@ -118,8 +115,9 @@ export default class RootStore { public clear() { Object.getOwnPropertyNames(this) .filter((key) => ["auth", "ui"].includes(key) === false) - .forEach((key) => { - this[key]?.clear?.(); + .forEach((key: keyof RootStore) => { + // @ts-expect-error clear exists on all stores + "clear" in this[key] && this[key].clear(); }); } @@ -128,7 +126,10 @@ export default class RootStore { * * @param StoreClass */ - private registerStore(StoreClass: T, name?: string) { + private registerStore( + StoreClass: T, + name?: keyof RootStore + ) { // @ts-expect-error TS thinks we are instantiating an abstract class. const store = new StoreClass(this); const storeName = name ?? this.getStoreNameForModelName(store.modelName); @@ -136,6 +137,6 @@ export default class RootStore { } private getStoreNameForModelName(modelName: string) { - return pluralize(lowerFirst(modelName)); + return pluralize(lowerFirst(modelName)) as keyof RootStore; } } diff --git a/app/stores/base/Store.ts b/app/stores/base/Store.ts index c89874803e..ede7cb5e23 100644 --- a/app/stores/base/Store.ts +++ b/app/stores/base/Store.ts @@ -17,7 +17,6 @@ import { LifecycleManager } from "~/models/decorators/Lifecycle"; import { getInverseRelationsForModelClass } from "~/models/decorators/Relation"; import type { PaginationParams, PartialExcept, Properties } from "~/types"; import { client } from "~/utils/ApiClient"; -import Logger from "~/utils/Logger"; import { AuthorizationError, NotFoundError } from "~/utils/errors"; export enum RPCAction { @@ -29,10 +28,19 @@ export enum RPCAction { Count = "count", } -export type FetchPageParams = PaginationParams & Record; - export const PAGINATION_SYMBOL = Symbol.for("pagination"); +export type PaginatedResponse = T[] & { + [PAGINATION_SYMBOL]?: { + total: number; + limit: number; + offset: number; + nextPath: string; + }; +}; + +export type FetchPageParams = PaginationParams & Record; + export default abstract class Store { @observable data: Map = new Map(); @@ -129,6 +137,7 @@ export default abstract class Store { if (deleteBehavior === "cascade") { store.remove(item.id); } else if (deleteBehavior === "null") { + // @ts-expect-error TODO item[relation.idKey] = null; } }); @@ -166,6 +175,7 @@ export default abstract class Store { if (archiveBehavior === "cascade") { store.addToArchive(item); } else if (archiveBehavior === "null") { + // @ts-expect-error TODO item[relation.idKey] = null; } }); @@ -316,7 +326,9 @@ export default abstract class Store { } @action - fetchPage = async (params?: FetchPageParams | undefined): Promise => { + fetchPage = async ( + params?: FetchPageParams | undefined + ): Promise> => { if (!this.actions.includes(RPCAction.List)) { throw new Error(`Cannot list ${this.modelName}`); } @@ -327,7 +339,7 @@ export default abstract class Store { const res = await client.post(`/${this.apiEndpoint}.list`, params); invariant(res?.data, "Data not available"); - let response: T[] = []; + let response: PaginatedResponse = []; runInAction(`list#${this.modelName}`, () => { this.addPolicies(res.policies); @@ -343,15 +355,16 @@ export default abstract class Store { }; @action - fetchAll = async (params?: Record): Promise => { + fetchAll = async ( + params?: Record + ): Promise> => { const limit = params?.limit ?? Pagination.defaultLimit; const response = await this.fetchPage({ ...params, limit }); - if (!response[PAGINATION_SYMBOL]) { - Logger.warn("Pagination information not available in response", { - params, - }); - } + invariant( + response[PAGINATION_SYMBOL], + "Pagination information not available in response" + ); const pages = Math.ceil(response[PAGINATION_SYMBOL].total / limit); const fetchPages = []; diff --git a/app/utils/date.ts b/app/utils/date.ts index 8918eb2d9c..e38113f92f 100644 --- a/app/utils/date.ts +++ b/app/utils/date.ts @@ -17,13 +17,14 @@ import { getCurrentTimeAsString, unicodeCLDRtoBCP47, dateLocale, + locales, } from "@shared/utils/date"; import User from "~/models/User"; export function dateToHeading( dateTime: string, t: TFunction, - userLocale: string | null | undefined + userLocale: keyof typeof locales | undefined ) { const date = Date.parse(dateTime); const now = new Date(); @@ -84,7 +85,7 @@ export function dateToHeading( export function dateToExpiry( dateTime: string, t: TFunction, - userLocale: string | null | undefined + userLocale: keyof typeof locales | null | undefined ) { const date = Date.parse(dateTime); const now = new Date(); diff --git a/app/utils/download.ts b/app/utils/download.ts index 624d2f8f83..59d8aa3ae1 100644 --- a/app/utils/download.ts +++ b/app/utils/download.ts @@ -33,7 +33,9 @@ export default function download( // reverse arguments, allowing download.bind(true, "text/xml", "export.xml") to act as a callback // @ts-expect-error this is weird code x = [x, m]; + // @ts-expect-error this is weird code m = x[0]; + // @ts-expect-error this is weird code x = x[1]; } diff --git a/app/utils/language.ts b/app/utils/language.ts index ee731c4a2b..1f75a275f3 100644 --- a/app/utils/language.ts +++ b/app/utils/language.ts @@ -1,5 +1,5 @@ import { i18n } from "i18next"; -import { unicodeCLDRtoBCP47 } from "@shared/utils/date"; +import { locales, unicodeCLDRtoBCP47 } from "@shared/utils/date"; import Desktop from "./Desktop"; /** @@ -25,7 +25,7 @@ export function formatNumber(number: number, locale: string) { export function detectLanguage() { const [ln, r] = navigator.language.split("-"); const region = (r || ln).toUpperCase(); - return `${ln}_${region}`; + return `${ln}_${region}` as keyof typeof locales; } /** diff --git a/app/utils/pageVisibility.ts b/app/utils/pageVisibility.ts index d03e0305bd..040daf8eb1 100644 --- a/app/utils/pageVisibility.ts +++ b/app/utils/pageVisibility.ts @@ -19,5 +19,5 @@ export function getVisibilityListener(): string { } export function getPageVisible(): boolean { - return !document[hidden]; + return !document[hidden as keyof Document]; } diff --git a/plugins/slack/client/components/SlackListItem.tsx b/plugins/slack/client/components/SlackListItem.tsx index 5b3ab1120d..7efa2e56d4 100644 --- a/plugins/slack/client/components/SlackListItem.tsx +++ b/plugins/slack/client/components/SlackListItem.tsx @@ -40,7 +40,7 @@ function SlackListItem({ integration, collection }: Props) { toast.success(t("Settings saved")); }; - const mapping = { + const mapping: Record = { "documents.publish": t("document published"), "documents.update": t("document updated"), }; diff --git a/plugins/slack/server/api/hooks.ts b/plugins/slack/server/api/hooks.ts index 6c93ea3013..ee9aac1f75 100644 --- a/plugins/slack/server/api/hooks.ts +++ b/plugins/slack/server/api/hooks.ts @@ -79,7 +79,10 @@ router.post( return; } // get content for unfurled links - const unfurls = {}; + const unfurls: Record< + string, + { title: string; text: string; color?: string | undefined } + > = {}; for (const link of event.links) { const documentId = parseDocumentSlug(link.url); @@ -109,7 +112,7 @@ router.post( unfurls[link.url] = { title: doc.title, text: doc.getSummary(), - color: doc.collection?.color, + color: doc.collection?.color ?? undefined, }; } } diff --git a/plugins/slack/shared/SlackUtils.ts b/plugins/slack/shared/SlackUtils.ts index 8a14424978..956a8454a6 100644 --- a/plugins/slack/shared/SlackUtils.ts +++ b/plugins/slack/shared/SlackUtils.ts @@ -74,7 +74,7 @@ export class SlackUtils { redirectUri = SlackUtils.callbackUrl() ): string { const baseUrl = SlackUtils.authBaseUrl; - const params = { + const params: Record = { client_id: env.SLACK_CLIENT_ID, scope: scopes ? scopes.join(" ") : "", redirect_uri: redirectUri, diff --git a/plugins/webhooks/server/tasks/DeliverWebhookTask.test.ts b/plugins/webhooks/server/tasks/DeliverWebhookTask.test.ts index cf4aab8de9..0aa80c4bb3 100644 --- a/plugins/webhooks/server/tasks/DeliverWebhookTask.test.ts +++ b/plugins/webhooks/server/tasks/DeliverWebhookTask.test.ts @@ -85,7 +85,10 @@ describe("DeliverWebhookTask", () => { event, }); - const headers = fetchMock.mock.calls[0]![1]!.headers!; + const headers = fetchMock.mock.calls[0]![1]!.headers! as Record< + string, + string + >; expect(fetchMock).toHaveBeenCalledTimes(1); expect(headers["Outline-Signature"]).toMatch(/^t=[0-9]+,s=[a-z0-9]+$/); diff --git a/plugins/webhooks/server/tasks/DeliverWebhookTask.ts b/plugins/webhooks/server/tasks/DeliverWebhookTask.ts index 801e3a6f7b..158750a410 100644 --- a/plugins/webhooks/server/tasks/DeliverWebhookTask.ts +++ b/plugins/webhooks/server/tasks/DeliverWebhookTask.ts @@ -692,7 +692,7 @@ export default class DeliverWebhookTask extends BaseTask { "user-agent": `Outline-Webhooks${ env.VERSION ? `/${env.VERSION.slice(0, 7)}` : "" }`, - }; + } as Record; const signature = subscription.signature(JSON.stringify(requestBody)); if (signature) { diff --git a/server/emails/templates/index.ts b/server/emails/templates/index.ts index 8380e01012..261e9c7a66 100644 --- a/server/emails/templates/index.ts +++ b/server/emails/templates/index.ts @@ -2,9 +2,9 @@ import { Hook, PluginManager } from "@server/utils/PluginManager"; import { requireDirectory } from "@server/utils/fs"; import BaseEmail from "./BaseEmail"; -const emails = {}; +const emails: Record = {}; -requireDirectory<{ default: BaseEmail }>(__dirname).forEach( +requireDirectory<{ default: typeof BaseEmail }>(__dirname).forEach( ([module, id]) => { if (id === "index") { return; diff --git a/server/index.ts b/server/index.ts index 2dfc6187c5..3b8ba88967 100644 --- a/server/index.ts +++ b/server/index.ts @@ -127,8 +127,8 @@ async function start(_id: number, disconnect: () => void) { } Logger.info("lifecycle", `Starting ${name} service`); - const init = services[name]; - await init(app, server, env.SERVICES); + const init = services[name as keyof typeof services]; + init(app, server as https.Server, env.SERVICES); } server.on("error", (err) => { diff --git a/server/logging/Logger.ts b/server/logging/Logger.ts index 4751e98dd7..3924409b0b 100644 --- a/server/logging/Logger.ts +++ b/server/logging/Logger.ts @@ -237,7 +237,7 @@ class Logger { } if (isObject(input)) { - const output = { ...input }; + const output: Record = { ...input }; for (const key of Object.keys(output)) { if (isObject(output[key])) { @@ -252,7 +252,7 @@ class Logger { output[key] = this.sanitize(output[key], level + 1); } } - return output; + return output as T; } return input; diff --git a/server/models/Collection.test.ts b/server/models/Collection.test.ts index 2acf22370e..46a076b1b2 100644 --- a/server/models/Collection.test.ts +++ b/server/models/Collection.test.ts @@ -38,7 +38,7 @@ describe("getDocumentParents", () => { }); const result = collection.getDocumentParents(document.id); expect(result?.length).toBe(1); - expect(result[0]).toBe(parent.id); + expect(result ? result[0] : undefined).toBe(parent.id); }); test("should return array of parent document ids", async () => { diff --git a/server/models/User.ts b/server/models/User.ts index 8d67352bfc..6322bb2076 100644 --- a/server/models/User.ts +++ b/server/models/User.ts @@ -45,6 +45,7 @@ import { } from "@shared/types"; import { UserRoleHelper } from "@shared/utils/UserRoleHelper"; import { stringToColor } from "@shared/utils/color"; +import { locales } from "@shared/utils/date"; import env from "@server/env"; import DeleteAttachmentTask from "@server/queues/tasks/DeleteAttachmentTask"; import parseAttachmentIds from "@server/utils/parseAttachmentIds"; @@ -179,8 +180,8 @@ class User extends ParanoidModel< @Default(env.DEFAULT_LANGUAGE) @IsIn([languages]) - @Column - language: string; + @Column(DataType.STRING) + language: keyof typeof locales | null; @AllowNull @IsUrlOrRelativePath diff --git a/server/models/base/Model.ts b/server/models/base/Model.ts index b3d96be98c..845bbd1b42 100644 --- a/server/models/base/Model.ts +++ b/server/models/base/Model.ts @@ -69,6 +69,7 @@ class Model< ) { const difference = Object.keys(previous) .concat(Object.keys(current)) + // @ts-expect-error TODO .filter((key) => !isEqual(previous[key], current[key])); previousAttributes[change] = pick( diff --git a/server/models/decorators/CounterCache.ts b/server/models/decorators/CounterCache.ts index 88fbcb4eb9..44de893c0b 100644 --- a/server/models/decorators/CounterCache.ts +++ b/server/models/decorators/CounterCache.ts @@ -36,11 +36,14 @@ export function CounterCache< setImmediate(() => { const recalculateCache = (offset: number) => async (model: InstanceType) => { - const cacheKey = `${cacheKeyPrefix}:${model[options.foreignKey]}`; + const cacheKey = `${cacheKeyPrefix}:${ + model[options.foreignKey as keyof typeof model] + }`; const count = await modelClass.count({ where: { - [options.foreignKey]: model[options.foreignKey], + [options.foreignKey]: + model[options.foreignKey as keyof typeof model], }, }); await CacheHelper.setData(cacheKey, count + offset); diff --git a/server/models/helpers/SearchHelper.ts b/server/models/helpers/SearchHelper.ts index 365add62d4..c0c55d1a40 100644 --- a/server/models/helpers/SearchHelper.ts +++ b/server/models/helpers/SearchHelper.ts @@ -355,7 +355,10 @@ export default class SearchHelper { options: SearchOptions ) { const teamId = model instanceof Team ? model.id : model.teamId; - const where: WhereOptions = { + const where: WhereOptions & { + [Op.or]: WhereOptions[]; + [Op.and]: WhereOptions[]; + } = { teamId, [Op.or]: [], [Op.and]: [ diff --git a/server/policies/cancan.ts b/server/policies/cancan.ts index cd2e5b3b9a..060fbc08dc 100644 --- a/server/policies/cancan.ts +++ b/server/policies/cancan.ts @@ -112,7 +112,7 @@ export class CanCan { * and sent in API responses to allow clients to adjust which UI is displayed. */ public serialize = (performer: Model, target: Model | null): Policy => { - const output = {}; + const output: Record = {}; abilities.forEach((ability) => { if ( performer instanceof ability.model && @@ -184,11 +184,14 @@ export class CanCan { (ability.action === "manage" || action === ability.action) ); - private get = (obj: object, key: string) => + private get = (obj: T, key: keyof T) => "get" in obj && typeof obj.get === "function" ? obj.get(key) : obj[key]; - private isPartiallyEqual = (target: object, obj: object) => - Object.keys(obj).every((key) => this.get(target, key) === obj[key]); + private isPartiallyEqual = (target: T, obj: T) => + Object.keys(obj).every( + // @ts-expect-error TODO + (key: keyof T) => this.get(target, key) === obj[key] + ); private getConditionFn = (condition: object) => (performer: Model, target: Model) => @@ -204,6 +207,7 @@ export class CanCan { if (typeof value === "string") { return [value]; } + // @ts-expect-error - TS doesn't know that value is iterable if (typeof value[Symbol.iterator] === "function") { // @ts-expect-error - TS doesn't know that value is iterable return [...value]; diff --git a/server/queues/processors/BaseProcessor.ts b/server/queues/processors/BaseProcessor.ts index 6cf0c13f9f..72c379ce0d 100644 --- a/server/queues/processors/BaseProcessor.ts +++ b/server/queues/processors/BaseProcessor.ts @@ -1,7 +1,7 @@ import { Event } from "@server/types"; export default abstract class BaseProcessor { - static applicableEvents: Event["name"][] | ["*"] = []; + static applicableEvents: (Event["name"] | "*")[] = []; public abstract perform(event: Event): Promise; } diff --git a/server/queues/processors/index.ts b/server/queues/processors/index.ts index 3834adb035..327e7226b1 100644 --- a/server/queues/processors/index.ts +++ b/server/queues/processors/index.ts @@ -2,9 +2,9 @@ import { Hook, PluginManager } from "@server/utils/PluginManager"; import { requireDirectory } from "@server/utils/fs"; import BaseProcessor from "./BaseProcessor"; -const processors = {}; +const processors: Record = {}; -requireDirectory<{ default: BaseProcessor }>(__dirname).forEach( +requireDirectory<{ default: typeof BaseProcessor }>(__dirname).forEach( ([module, id]) => { if (id === "index") { return; diff --git a/server/queues/tasks/EmailTask.ts b/server/queues/tasks/EmailTask.ts index fa0a564bd3..618226bb81 100644 --- a/server/queues/tasks/EmailTask.ts +++ b/server/queues/tasks/EmailTask.ts @@ -15,6 +15,7 @@ export default class EmailTask extends BaseTask { ); } + // @ts-expect-error We won't instantiate an abstract class const email = new EmailClass(props, metadata); return email.send(); } diff --git a/server/queues/tasks/index.ts b/server/queues/tasks/index.ts index 4c75d3a176..b59e30343b 100644 --- a/server/queues/tasks/index.ts +++ b/server/queues/tasks/index.ts @@ -2,9 +2,9 @@ import { Hook, PluginManager } from "@server/utils/PluginManager"; import { requireDirectory } from "@server/utils/fs"; import BaseTask from "./BaseTask"; -const tasks = {}; +const tasks: Record = {}; -requireDirectory<{ default: BaseTask }>(__dirname).forEach( +requireDirectory<{ default: typeof BaseTask }>(__dirname).forEach( ([module, id]) => { if (id === "index") { return; diff --git a/server/routes/api/collections/collections.ts b/server/routes/api/collections/collections.ts index f75ed915c8..2cbf08dd7b 100644 --- a/server/routes/api/collections/collections.ts +++ b/server/routes/api/collections/collections.ts @@ -813,7 +813,9 @@ router.post( const { transaction } = ctx.state; const collectionIds = await user.collectionIds({ transaction }); - const where: WhereOptions = { + const where: WhereOptions & { + [Op.and]: WhereOptions[]; + } = { teamId: user.teamId, [Op.and]: [ { diff --git a/server/routes/api/documents/documents.ts b/server/routes/api/documents/documents.ts index ecf472504b..4a47a44e7b 100644 --- a/server/routes/api/documents/documents.ts +++ b/server/routes/api/documents/documents.ts @@ -97,7 +97,9 @@ router.post( // always filter by the current team const { user } = ctx.state.auth; - const where: WhereOptions = { + const where: WhereOptions & { + [Op.and]: WhereOptions[]; + } = { teamId: user.teamId, [Op.and]: [ { diff --git a/server/routes/api/users/schema.ts b/server/routes/api/users/schema.ts index caedad1ad8..fd019af5a7 100644 --- a/server/routes/api/users/schema.ts +++ b/server/routes/api/users/schema.ts @@ -1,6 +1,8 @@ import { z } from "zod"; import { NotificationEventType, UserPreference, UserRole } from "@shared/types"; +import { locales } from "@shared/utils/date"; import User from "@server/models/User"; +import { zodEnumFromObjectKeys } from "@server/utils/zod"; import { BaseSchema } from "../schema"; const BaseIdSchema = z.object({ @@ -80,7 +82,7 @@ export const UsersUpdateSchema = BaseSchema.extend({ id: z.string().uuid().optional(), name: z.string().optional(), avatarUrl: z.string().nullish(), - language: z.string().optional(), + language: zodEnumFromObjectKeys(locales).optional(), preferences: z.record(z.nativeEnum(UserPreference), z.boolean()).optional(), }), }); diff --git a/server/routes/index.ts b/server/routes/index.ts index 871e2b457b..b0af61a972 100644 --- a/server/routes/index.ts +++ b/server/routes/index.ts @@ -97,7 +97,7 @@ router.use(compress()); router.get("/locales/:lng.json", async (ctx) => { const { lng } = ctx.params; - if (!languages.includes(lng)) { + if (!languages.includes(lng as (typeof languages)[number])) { ctx.status = 404; return; } diff --git a/server/scripts/20230313000000-migrate-notification-settings.ts b/server/scripts/20230313000000-migrate-notification-settings.ts index c50e46c57c..c0cbf9d9d0 100644 --- a/server/scripts/20230313000000-migrate-notification-settings.ts +++ b/server/scripts/20230313000000-migrate-notification-settings.ts @@ -33,6 +33,7 @@ export default async function main(exit = false) { user.notificationSettings = {}; for (const eventType of eventTypes) { + // @ts-expect-error old migration user.notificationSettings[eventType] = true; user.changed("notificationSettings", true); } diff --git a/server/services/index.ts b/server/services/index.ts index f4e1894c65..990e944ac4 100644 --- a/server/services/index.ts +++ b/server/services/index.ts @@ -12,4 +12,4 @@ export default { web, worker, cron, -}; +} as const; diff --git a/server/services/worker.ts b/server/services/worker.ts index 5e0e7ad5f8..6f309c7a81 100644 --- a/server/services/worker.ts +++ b/server/services/worker.ts @@ -3,6 +3,7 @@ import Logger from "@server/logging/Logger"; import { setResource, addTags } from "@server/logging/tracer"; import { traceFunction } from "@server/logging/tracing"; import HealthMonitor from "@server/queues/HealthMonitor"; +import { Event } from "@server/types"; import { initI18n } from "@server/utils/i18n"; import { globalEventQueue, @@ -25,7 +26,7 @@ export default function init() { spanName: "process", isRoot: true, })(async function (job) { - const event = job.data; + const event = job.data as Event; let err; setResource(`Event.${event.name}`); @@ -99,6 +100,7 @@ export default function init() { ); } + // @ts-expect-error We will not instantiate an abstract class const processor = new ProcessorClass(); if (processor.perform) { @@ -146,6 +148,7 @@ export default function init() { Logger.info("worker", `${name} running`, props); + // @ts-expect-error We will not instantiate an abstract class const task = new TaskClass(); try { diff --git a/server/utils/decorators/Public.ts b/server/utils/decorators/Public.ts index 7f9106986d..10db2b3844 100644 --- a/server/utils/decorators/Public.ts +++ b/server/utils/decorators/Public.ts @@ -23,9 +23,9 @@ export class PublicEnvironmentRegister { static registerEnv(env: Environment) { process.nextTick(() => { const vars: string[] = Reflect.getMetadata(key, env) ?? []; - vars.forEach((key: string) => { - if (isUndefined(this.publicEnv[key])) { - this.publicEnv[key] = env[key]; + vars.forEach((k: keyof Environment) => { + if (isUndefined(this.publicEnv[k])) { + this.publicEnv[k] = env[k]; } }); }); diff --git a/server/utils/diff.ts b/server/utils/diff.ts index e19c940d0f..5e5104d950 100644 --- a/server/utils/diff.ts +++ b/server/utils/diff.ts @@ -59,13 +59,13 @@ type Token = { }; type Segment = { - beforeTokens: Array; - afterTokens: Array; + beforeTokens: Token[]; + afterTokens: Token[]; beforeIndex: number; afterIndex: number; - beforeMap: object; - afterMap: object; + beforeMap: Record; + afterMap: Record; }; type MatchT = { @@ -232,7 +232,7 @@ function Match( * * @return {Array} The list of tokens. */ -function htmlToTokens(html: string): Array { +function htmlToTokens(html: string): Token[] { let mode = "char"; let currentWord = ""; let currentAtomicTag = ""; @@ -364,7 +364,7 @@ function getKeyForToken(token: string): string { * * @return {Object} A mapping that can be used to search for tokens. */ -function createMap(tokens: Array): object { +function createMap(tokens: Token[]) { return tokens.reduce(function (map, token, index) { if (map[token.key]) { map[token.key].push(index); @@ -651,8 +651,8 @@ function getFullMatch( * @return {Segment} The segment object. */ function createSegment( - beforeTokens: Array, - afterTokens: Array, + beforeTokens: Token[], + afterTokens: Token[], beforeIndex: number, afterIndex: number ): Segment { @@ -759,8 +759,8 @@ function findMatchingBlocks(segment: Segment): Array { * - {number} endInAfter The end of the range in the list of after tokens. */ function calculateOperations( - beforeTokens: Array, - afterTokens: Array + beforeTokens: Token[], + afterTokens: Token[] ): Array { if (!beforeTokens) { throw new Error("Missing beforeTokens"); @@ -902,8 +902,8 @@ function TokenWrapper(tokens: any) { * and whether those tokens are wrappable or not. The result should be a string. */ TokenWrapper.prototype.combine = function ( - mapFn: (wrappable: boolean, tokens: Array) => void, - tagFn: (tokens: Array) => void + mapFn: (wrappable: boolean, tokens: Token[]) => void, + tagFn: (tokens: Token[]) => void ) { const notes = this.notes; const tokens = this.tokens.slice(); @@ -1069,7 +1069,8 @@ function renderOperations( return operations.reduce(function (rendering, op, index) { return ( rendering + - OPS[op.action]( + // @ts-expect-error TODO + OPS[op.action]?.( op, beforeTokens, afterTokens, diff --git a/server/utils/indexing.ts b/server/utils/indexing.ts index 56dbc6b465..8d2b509646 100644 --- a/server/utils/indexing.ts +++ b/server/utils/indexing.ts @@ -6,7 +6,7 @@ import { Collection, Document, Star } from "@server/models"; export async function collectionIndexing( teamId: string, { transaction }: FindOptions -): Promise<{ [id: string]: string }> { +) { const collections = await Collection.findAll({ where: { teamId, @@ -34,16 +34,14 @@ export async function collectionIndexing( await Promise.all(promises); - const indexedCollections = {}; + const indexedCollections: Record = {}; sortable.forEach((collection) => { indexedCollections[collection.id] = collection.index; }); return indexedCollections; } -export async function starIndexing( - userId: string -): Promise<{ [id: string]: string }> { +export async function starIndexing(userId: string) { const stars = await Star.findAll({ where: { userId }, }); @@ -77,7 +75,7 @@ export async function starIndexing( await Promise.all(promises); - const indexedStars = {}; + const indexedStars: Record = {}; sortable.forEach((star) => { indexedStars[star.id] = star.index; }); diff --git a/server/utils/readManifestFile.ts b/server/utils/readManifestFile.ts index c6691d7c33..bfb411e22a 100644 --- a/server/utils/readManifestFile.ts +++ b/server/utils/readManifestFile.ts @@ -4,6 +4,7 @@ import Logger from "@server/logging/Logger"; export type Chunk = { file: string; + imports: string[]; src: string; isEntry?: boolean; }; diff --git a/server/utils/turndown/tables.ts b/server/utils/turndown/tables.ts index 7a38149d04..536cc564ec 100644 --- a/server/utils/turndown/tables.ts +++ b/server/utils/turndown/tables.ts @@ -10,7 +10,11 @@ const tableShouldBeSkippedCache = new WeakMap(); function getAlignment(node: HTMLElement) { return node - ? (node.getAttribute("align") || node.style.textAlign || "").toLowerCase() + ? (( + node.getAttribute("align") || + node.style.textAlign || + "" + ).toLowerCase() as "left" | "right" | "center") : ""; } diff --git a/server/utils/validators.ts b/server/utils/validators.ts index 5a97f5aadf..7b1712c4e8 100644 --- a/server/utils/validators.ts +++ b/server/utils/validators.ts @@ -19,7 +19,7 @@ export function CannotUseWithout( validator: { validate(value: T, args: ValidationArguments) { const obj = args.object as unknown as T; - const required = args.constraints[0] as string; + const required = args.constraints[0] as keyof T; return obj[required] !== undefined; }, defaultMessage(args: ValidationArguments) { diff --git a/shared/editor/lib/Extension.ts b/shared/editor/lib/Extension.ts index d70b02e402..af866571a8 100644 --- a/shared/editor/lib/Extension.ts +++ b/shared/editor/lib/Extension.ts @@ -5,7 +5,9 @@ import { Command, Plugin } from "prosemirror-state"; import { Primitive } from "utility-types"; import type { Editor } from "../../../app/editor"; -export type CommandFactory = (attrs?: Record) => Command; +export type CommandFactory = ( + attrs?: Record +) => Command | void; export type WidgetProps = { rtl: boolean; readOnly: boolean | undefined }; diff --git a/shared/editor/lib/ExtensionManager.ts b/shared/editor/lib/ExtensionManager.ts index 4ce929a103..2fe94f4249 100644 --- a/shared/editor/lib/ExtensionManager.ts +++ b/shared/editor/lib/ExtensionManager.ts @@ -186,6 +186,7 @@ export default class ExtensionManager { .map((extension) => ["node", "mark"].includes(extension.type) ? extension.keys({ + // @ts-expect-error TODO type: schema[`${extension.type}s`][extension.name], schema, }) @@ -206,6 +207,7 @@ export default class ExtensionManager { .filter((extension) => extension.inputRules) .map((extension) => extension.inputRules({ + // @ts-expect-error TODO type: schema[`${extension.type}s`][extension.name], schema, }) @@ -222,13 +224,14 @@ export default class ExtensionManager { .filter((extension) => extension.commands) .reduce((allCommands, extension) => { const { name, type } = extension; - const commands = {}; + const commands: Record = {}; // @ts-expect-error FIXME const value = extension.commands({ schema, ...(["node", "mark"].includes(type) ? { + // @ts-expect-error TODO type: schema[`${type}s`][name], } : {}), @@ -239,12 +242,12 @@ export default class ExtensionManager { attrs: Record ) => { if (!view.editable && !extension.allowInReadOnly) { - return false; + return; } if (extension.focusAfterExecution) { view.focus(); } - return callback(attrs)(view.state, view.dispatch, view); + return callback(attrs)?.(view.state, view.dispatch, view); }; const handle = (_name: string, _value: CommandFactory) => { @@ -252,8 +255,8 @@ export default class ExtensionManager { commands[_name] = (attrs: Record) => _value.forEach((callback) => apply(callback, attrs)); } else if (typeof _value === "function") { - commands[_name] = (attrs: Record) => - apply(_value, attrs); + commands[_name] = ((attrs: Record) => + apply(_value, attrs)) as CommandFactory; } }; diff --git a/shared/editor/lib/code.ts b/shared/editor/lib/code.ts index 51ca7c5681..92aff343e9 100644 --- a/shared/editor/lib/code.ts +++ b/shared/editor/lib/code.ts @@ -1,4 +1,5 @@ import Storage from "../../utils/Storage"; +import { LANGUAGES } from "../extensions/Prism"; const RecentStorageKey = "rme-code-language"; const StorageKey = "frequent-code-languages"; @@ -44,9 +45,10 @@ export const getRecentCodeLanguage = () => Storage.get(RecentStorageKey); export const getFrequentCodeLanguages = () => { const recentLang = Storage.get(RecentStorageKey); - const frequentLangEntries = Object.entries( - (Storage.get(StorageKey) ?? {}) as Record - ); + const frequentLangEntries = Object.entries(Storage.get(StorageKey) ?? {}) as [ + keyof typeof LANGUAGES, + number + ][]; const frequentLangs = sortFrequencies(frequentLangEntries) .slice(0, FrequentlyUsedCount.Get) @@ -61,5 +63,5 @@ export const getFrequentCodeLanguages = () => { return frequentLangs; }; -const sortFrequencies = (freqs: [string, number][]) => +const sortFrequencies = (freqs: [T, number][]) => freqs.sort((a, b) => (a[1] >= b[1] ? -1 : 1)); diff --git a/shared/editor/lib/emoji.ts b/shared/editor/lib/emoji.ts index acdfca2916..0ff868e49d 100644 --- a/shared/editor/lib/emoji.ts +++ b/shared/editor/lib/emoji.ts @@ -21,6 +21,7 @@ export const nameToEmoji: Record = Object.values( (data as EmojiMartData).emojis ).reduce((acc, emoji) => { const convertedId = snakeCase(emoji.id); + // @ts-expect-error emojiMartToGemoji is a valid map acc[emojiMartToGemoji[convertedId] ?? convertedId] = emoji.skins[0].native; return acc; }, {}); diff --git a/shared/editor/lib/prosemirror-recreate-transform/getFromPath.ts b/shared/editor/lib/prosemirror-recreate-transform/getFromPath.ts index 63d564e9fb..7364442ff4 100644 --- a/shared/editor/lib/prosemirror-recreate-transform/getFromPath.ts +++ b/shared/editor/lib/prosemirror-recreate-transform/getFromPath.ts @@ -13,7 +13,7 @@ export function getFromPath(obj: JSONValue, path: string): JSONValue { if (typeof obj !== "object") { throw new Error(); } - const property = pathParts.shift() as string; + const property = pathParts.shift() as keyof JSONValue; obj = obj[property]; } return obj; diff --git a/shared/editor/nodes/Heading.ts b/shared/editor/nodes/Heading.ts index 04d6ed57de..f9c19513c6 100644 --- a/shared/editor/nodes/Heading.ts +++ b/shared/editor/nodes/Heading.ts @@ -220,7 +220,7 @@ export default class Heading extends Node { get plugins() { const getAnchors = (doc: ProsemirrorNode) => { const decorations: Decoration[] = []; - const previouslySeen = {}; + const previouslySeen: Record = {}; doc.descendants((node, pos) => { if (node.type.name !== this.name) { diff --git a/shared/i18n/index.ts b/shared/i18n/index.ts index 7a8cf28120..e315ef413c 100644 --- a/shared/i18n/index.ts +++ b/shared/i18n/index.ts @@ -1,6 +1,13 @@ +import { locales } from "../utils/date"; + +type LanguageOption = { + label: string; + value: keyof typeof locales; +}; + // Note: Updating the available languages? Make sure to also update the // locales array in shared/utils/date.ts to enable translation for timestamps. -export const languageOptions = [ +export const languageOptions: LanguageOption[] = [ { label: "English (US)", value: "en_US", diff --git a/shared/i18n/locales/en_US/translation.json b/shared/i18n/locales/en_US/translation.json index 6b3f21c409..42e10c9c68 100644 --- a/shared/i18n/locales/en_US/translation.json +++ b/shared/i18n/locales/en_US/translation.json @@ -168,6 +168,7 @@ "Are you sure you want to permanently delete this entire comment thread?": "Are you sure you want to permanently delete this entire comment thread?", "Are you sure you want to permanently delete this comment?": "Are you sure you want to permanently delete this comment?", "Confirm": "Confirm", + "manage access": "manage access", "view and edit access": "view and edit access", "view only access": "view only access", "no access": "no access", diff --git a/shared/types.ts b/shared/types.ts index 659ded7184..5a6488cb07 100644 --- a/shared/types.ts +++ b/shared/types.ts @@ -292,20 +292,22 @@ export type NotificationSettings = { | boolean; }; -export const NotificationEventDefaults = { - [NotificationEventType.PublishDocument]: false, - [NotificationEventType.UpdateDocument]: true, - [NotificationEventType.CreateCollection]: false, - [NotificationEventType.CreateComment]: true, - [NotificationEventType.MentionedInDocument]: true, - [NotificationEventType.MentionedInComment]: true, - [NotificationEventType.InviteAccepted]: true, - [NotificationEventType.Onboarding]: true, - [NotificationEventType.Features]: true, - [NotificationEventType.ExportCompleted]: true, - [NotificationEventType.AddUserToDocument]: true, - [NotificationEventType.AddUserToCollection]: true, -}; +export const NotificationEventDefaults: Record = + { + [NotificationEventType.PublishDocument]: false, + [NotificationEventType.UpdateDocument]: true, + [NotificationEventType.CreateCollection]: false, + [NotificationEventType.CreateComment]: true, + [NotificationEventType.CreateRevision]: false, + [NotificationEventType.MentionedInDocument]: true, + [NotificationEventType.MentionedInComment]: true, + [NotificationEventType.InviteAccepted]: true, + [NotificationEventType.Onboarding]: true, + [NotificationEventType.Features]: true, + [NotificationEventType.ExportCompleted]: true, + [NotificationEventType.AddUserToDocument]: true, + [NotificationEventType.AddUserToCollection]: true, + }; export enum UnfurlResourceType { OEmbed = "oembed", diff --git a/shared/utils/ProsemirrorHelper.ts b/shared/utils/ProsemirrorHelper.ts index b8f0003f5e..595757d775 100644 --- a/shared/utils/ProsemirrorHelper.ts +++ b/shared/utils/ProsemirrorHelper.ts @@ -279,7 +279,7 @@ export class ProsemirrorHelper { */ static getHeadings(doc: Node, schema: Schema) { const headings: Heading[] = []; - const previouslySeen = {}; + const previouslySeen: Record = {}; doc.forEach((node) => { if (node.type.name === "heading") { diff --git a/shared/utils/Storage.ts b/shared/utils/Storage.ts index 1e2de4a091..451ac7e4d1 100644 --- a/shared/utils/Storage.ts +++ b/shared/utils/Storage.ts @@ -75,7 +75,7 @@ class Storage { * when localStorage is not available. */ class MemoryStorage { - private data = {}; + private data: Record = {}; getItem(key: string) { return this.data[key] || null; diff --git a/shared/utils/date.ts b/shared/utils/date.ts index 82b5fff429..397b2c7a46 100644 --- a/shared/utils/date.ts +++ b/shared/utils/date.ts @@ -189,7 +189,7 @@ const locales = { * @param language The user language * @returns The date-fns locale. */ -export function dateLocale(language: string | null | undefined) { +export function dateLocale(language: keyof typeof locales | undefined | null) { return language ? locales[language] : undefined; } diff --git a/shared/utils/emoji.ts b/shared/utils/emoji.ts index 0194012eda..7da06490a2 100644 --- a/shared/utils/emoji.ts +++ b/shared/utils/emoji.ts @@ -51,7 +51,9 @@ type GetVariantsProps = { const getVariants = ({ id, name, skins }: GetVariantsProps): EmojiVariants => skins.reduce((obj, skin) => { - const skinToneCode = skin.unified.split("-")[1]; + const skinToneCode = skin.unified.split( + "-" + )[1] as keyof typeof SKINTONE_CODE_TO_ENUM; const skinToneType = SKINTONE_CODE_TO_ENUM[skinToneCode] ?? EmojiSkinTone.Default; obj[skinToneType] = { id, name, value: skin.native } satisfies Emoji; @@ -72,7 +74,8 @@ const EMOJI_ID_TO_VARIANTS = Object.entries(Emojis).reduce( const CATEGORY_TO_EMOJI_IDS: Record = Categories.reduce((obj, { id, emojis }) => { - const category = EmojiCategory[capitalize(id)]; + const key = capitalize(id) as EmojiCategory; + const category = EmojiCategory[key]; if (!category) { return obj; } diff --git a/shared/utils/naturalSort.ts b/shared/utils/naturalSort.ts index 110e901f9c..d3cd4e255d 100644 --- a/shared/utils/naturalSort.ts +++ b/shared/utils/naturalSort.ts @@ -14,7 +14,7 @@ const stripEmojis = (value: string) => value.replace(regex, ""); const cleanValue = (value: string) => stripEmojis(deburr(value)); -function getSortByField( +function getSortByField>( item: T, keyOrCallback: string | ((item: T) => string) ) { @@ -25,7 +25,7 @@ function getSortByField( return cleanValue(field); } -function naturalSortBy( +function naturalSortBy>( items: T[], key: string | ((item: T) => string), sortOptions?: NaturalSortOptions diff --git a/tsconfig.json b/tsconfig.json index 2736d3fc1f..e697558ccd 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -23,7 +23,6 @@ "noEmit": true, "skipLibCheck": true, "ignoreDeprecations": "5.0", - "suppressImplicitAnyIndexErrors": true, "target": "es2020", "paths": { "@server/*": ["./server/*"],