mirror of
https://github.com/outline/outline.git
synced 2025-03-14 10:07:11 +00:00
chore: Remove suppressImplicitAnyIndexErrors
TS rule (#7760)
This commit is contained in:
@ -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<Partial<CollectionPermission> | "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({
|
||||
|
@ -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" ||
|
||||
|
@ -39,12 +39,15 @@ const LocaleTime: React.FC<Props> = ({
|
||||
relative,
|
||||
tooltipDelay,
|
||||
}: Props) => {
|
||||
const userLocale: string = useUserLocale() || "";
|
||||
const dateFormatLong = {
|
||||
const userLocale = useUserLocale();
|
||||
const dateFormatLong: Record<string, string> = {
|
||||
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>();
|
||||
|
@ -19,7 +19,8 @@ export interface PaginatedItem {
|
||||
}
|
||||
|
||||
type Props<T> = WithTranslation &
|
||||
RootStore & {
|
||||
RootStore &
|
||||
React.HTMLAttributes<HTMLDivElement> & {
|
||||
fetch?: (
|
||||
options: Record<string, any> | undefined
|
||||
) => Promise<T[] | undefined> | undefined;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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: <ExpandedIcon />,
|
||||
// @ts-expect-error We have a fallback for incorrect mapping
|
||||
label: LANGUAGES[node.attrs.language ?? "none"],
|
||||
children: languageMenuItems,
|
||||
},
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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<any> {
|
||||
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);
|
||||
|
@ -17,7 +17,7 @@ type RelationOptions<T = Model> = {
|
||||
|
||||
type RelationProperties<T = Model> = {
|
||||
/** 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. */
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
});
|
||||
}, []);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -38,7 +38,7 @@ const FileOperationListItem = ({ fileOperation }: Props) => {
|
||||
[FileOperationState.Error]: t("Failed"),
|
||||
};
|
||||
|
||||
const iconMapping = {
|
||||
const iconMapping: Record<FileOperationState, React.JSX.Element> = {
|
||||
[FileOperationState.Creating]: <Spinner />,
|
||||
[FileOperationState.Uploading]: <Spinner />,
|
||||
[FileOperationState.Expired]: <ArchiveIcon color={theme.textTertiary} />,
|
||||
@ -46,8 +46,9 @@ const FileOperationListItem = ({ fileOperation }: Props) => {
|
||||
[FileOperationState.Error]: <WarningIcon color={theme.danger} />,
|
||||
};
|
||||
|
||||
const formatMapping = {
|
||||
const formatMapping: Record<FileOperationFormat, string> = {
|
||||
[FileOperationFormat.JSON]: "JSON",
|
||||
[FileOperationFormat.Notion]: "Notion",
|
||||
[FileOperationFormat.MarkdownZip]: "Markdown",
|
||||
[FileOperationFormat.HTMLZip]: "HTML",
|
||||
[FileOperationFormat.PDF]: "PDF",
|
||||
|
@ -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<GroupMembership> {
|
||||
actions = [RPCAction.Create, RPCAction.Delete];
|
||||
@ -24,7 +28,7 @@ export default class GroupMembershipsStore extends Store<GroupMembership> {
|
||||
documentId?: string;
|
||||
collectionId?: string;
|
||||
groupId?: string;
|
||||
}): Promise<GroupMembership[]> => {
|
||||
}): Promise<PaginatedResponse<GroupMembership>> => {
|
||||
this.isFetching = true;
|
||||
|
||||
try {
|
||||
@ -41,7 +45,7 @@ export default class GroupMembershipsStore extends Store<GroupMembership> {
|
||||
: await client.post(`/groupMemberships.list`, params);
|
||||
invariant(res?.data, "Data not available");
|
||||
|
||||
let response: GroupMembership[] = [];
|
||||
let response: PaginatedResponse<GroupMembership> = [];
|
||||
runInAction(`GroupMembershipsStore#fetchPage`, () => {
|
||||
res.data.groups?.forEach(this.rootStore.groups.add);
|
||||
res.data.documents?.forEach(this.rootStore.documents.add);
|
||||
|
@ -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<Membership> {
|
||||
actions = [RPCAction.Create, RPCAction.Delete];
|
||||
@ -17,14 +21,14 @@ export default class MembershipsStore extends Store<Membership> {
|
||||
@action
|
||||
fetchPage = async (
|
||||
params: (PaginationParams & { id?: string }) | undefined
|
||||
): Promise<Membership[]> => {
|
||||
): Promise<PaginatedResponse<Membership>> => {
|
||||
this.isFetching = true;
|
||||
|
||||
try {
|
||||
const res = await client.post(`/collections.memberships`, params);
|
||||
invariant(res?.data, "Data not available");
|
||||
|
||||
let response: Membership[] = [];
|
||||
let response: PaginatedResponse<Membership> = [];
|
||||
runInAction(`MembershipsStore#fetchPage`, () => {
|
||||
res.data.users.forEach(this.rootStore.users.add);
|
||||
response = res.data.memberships.map(this.add);
|
||||
|
@ -102,14 +102,11 @@ export default class RootStore {
|
||||
*
|
||||
* @param modelName
|
||||
*/
|
||||
public getStoreForModelName<K extends keyof RootStore>(
|
||||
modelName: string
|
||||
): RootStore[K] {
|
||||
public getStoreForModelName<K extends keyof RootStore>(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<T = typeof Store>(StoreClass: T, name?: string) {
|
||||
private registerStore<T = typeof Store>(
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -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<string, any>;
|
||||
|
||||
export const PAGINATION_SYMBOL = Symbol.for("pagination");
|
||||
|
||||
export type PaginatedResponse<T> = T[] & {
|
||||
[PAGINATION_SYMBOL]?: {
|
||||
total: number;
|
||||
limit: number;
|
||||
offset: number;
|
||||
nextPath: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type FetchPageParams = PaginationParams & Record<string, any>;
|
||||
|
||||
export default abstract class Store<T extends Model> {
|
||||
@observable
|
||||
data: Map<string, T> = new Map();
|
||||
@ -129,6 +137,7 @@ export default abstract class Store<T extends Model> {
|
||||
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<T extends Model> {
|
||||
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<T extends Model> {
|
||||
}
|
||||
|
||||
@action
|
||||
fetchPage = async (params?: FetchPageParams | undefined): Promise<T[]> => {
|
||||
fetchPage = async (
|
||||
params?: FetchPageParams | undefined
|
||||
): Promise<PaginatedResponse<T>> => {
|
||||
if (!this.actions.includes(RPCAction.List)) {
|
||||
throw new Error(`Cannot list ${this.modelName}`);
|
||||
}
|
||||
@ -327,7 +339,7 @@ export default abstract class Store<T extends Model> {
|
||||
const res = await client.post(`/${this.apiEndpoint}.list`, params);
|
||||
invariant(res?.data, "Data not available");
|
||||
|
||||
let response: T[] = [];
|
||||
let response: PaginatedResponse<T> = [];
|
||||
|
||||
runInAction(`list#${this.modelName}`, () => {
|
||||
this.addPolicies(res.policies);
|
||||
@ -343,15 +355,16 @@ export default abstract class Store<T extends Model> {
|
||||
};
|
||||
|
||||
@action
|
||||
fetchAll = async (params?: Record<string, any>): Promise<T[]> => {
|
||||
fetchAll = async (
|
||||
params?: Record<string, any>
|
||||
): Promise<PaginatedResponse<T>> => {
|
||||
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 = [];
|
||||
|
@ -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();
|
||||
|
@ -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];
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -19,5 +19,5 @@ export function getVisibilityListener(): string {
|
||||
}
|
||||
|
||||
export function getPageVisible(): boolean {
|
||||
return !document[hidden];
|
||||
return !document[hidden as keyof Document];
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ function SlackListItem({ integration, collection }: Props) {
|
||||
toast.success(t("Settings saved"));
|
||||
};
|
||||
|
||||
const mapping = {
|
||||
const mapping: Record<string, string> = {
|
||||
"documents.publish": t("document published"),
|
||||
"documents.update": t("document updated"),
|
||||
};
|
||||
|
@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -74,7 +74,7 @@ export class SlackUtils {
|
||||
redirectUri = SlackUtils.callbackUrl()
|
||||
): string {
|
||||
const baseUrl = SlackUtils.authBaseUrl;
|
||||
const params = {
|
||||
const params: Record<string, string> = {
|
||||
client_id: env.SLACK_CLIENT_ID,
|
||||
scope: scopes ? scopes.join(" ") : "",
|
||||
redirect_uri: redirectUri,
|
||||
|
@ -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]+$/);
|
||||
|
@ -692,7 +692,7 @@ export default class DeliverWebhookTask extends BaseTask<Props> {
|
||||
"user-agent": `Outline-Webhooks${
|
||||
env.VERSION ? `/${env.VERSION.slice(0, 7)}` : ""
|
||||
}`,
|
||||
};
|
||||
} as Record<string, string>;
|
||||
|
||||
const signature = subscription.signature(JSON.stringify(requestBody));
|
||||
if (signature) {
|
||||
|
@ -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<string, typeof BaseEmail> = {};
|
||||
|
||||
requireDirectory<{ default: BaseEmail<any> }>(__dirname).forEach(
|
||||
requireDirectory<{ default: typeof BaseEmail }>(__dirname).forEach(
|
||||
([module, id]) => {
|
||||
if (id === "index") {
|
||||
return;
|
||||
|
@ -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) => {
|
||||
|
@ -237,7 +237,7 @@ class Logger {
|
||||
}
|
||||
|
||||
if (isObject(input)) {
|
||||
const output = { ...input };
|
||||
const output: Record<string, any> = { ...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;
|
||||
|
@ -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 () => {
|
||||
|
@ -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
|
||||
|
@ -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(
|
||||
|
@ -36,11 +36,14 @@ export function CounterCache<
|
||||
setImmediate(() => {
|
||||
const recalculateCache =
|
||||
(offset: number) => async (model: InstanceType<T>) => {
|
||||
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);
|
||||
|
@ -355,7 +355,10 @@ export default class SearchHelper {
|
||||
options: SearchOptions
|
||||
) {
|
||||
const teamId = model instanceof Team ? model.id : model.teamId;
|
||||
const where: WhereOptions<Document> = {
|
||||
const where: WhereOptions<Document> & {
|
||||
[Op.or]: WhereOptions<Document>[];
|
||||
[Op.and]: WhereOptions<Document>[];
|
||||
} = {
|
||||
teamId,
|
||||
[Op.or]: [],
|
||||
[Op.and]: [
|
||||
|
@ -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<string, boolean | string[]> = {};
|
||||
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 = <T extends object>(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 = <T extends object>(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];
|
||||
|
@ -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<void>;
|
||||
}
|
||||
|
@ -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<string, typeof BaseProcessor> = {};
|
||||
|
||||
requireDirectory<{ default: BaseProcessor }>(__dirname).forEach(
|
||||
requireDirectory<{ default: typeof BaseProcessor }>(__dirname).forEach(
|
||||
([module, id]) => {
|
||||
if (id === "index") {
|
||||
return;
|
||||
|
@ -15,6 +15,7 @@ export default class EmailTask extends BaseTask<Props> {
|
||||
);
|
||||
}
|
||||
|
||||
// @ts-expect-error We won't instantiate an abstract class
|
||||
const email = new EmailClass(props, metadata);
|
||||
return email.send();
|
||||
}
|
||||
|
@ -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<string, typeof BaseTask> = {};
|
||||
|
||||
requireDirectory<{ default: BaseTask<any> }>(__dirname).forEach(
|
||||
requireDirectory<{ default: typeof BaseTask }>(__dirname).forEach(
|
||||
([module, id]) => {
|
||||
if (id === "index") {
|
||||
return;
|
||||
|
@ -813,7 +813,9 @@ router.post(
|
||||
const { transaction } = ctx.state;
|
||||
const collectionIds = await user.collectionIds({ transaction });
|
||||
|
||||
const where: WhereOptions<Collection> = {
|
||||
const where: WhereOptions<Collection> & {
|
||||
[Op.and]: WhereOptions<Collection>[];
|
||||
} = {
|
||||
teamId: user.teamId,
|
||||
[Op.and]: [
|
||||
{
|
||||
|
@ -97,7 +97,9 @@ router.post(
|
||||
|
||||
// always filter by the current team
|
||||
const { user } = ctx.state.auth;
|
||||
const where: WhereOptions<Document> = {
|
||||
const where: WhereOptions<Document> & {
|
||||
[Op.and]: WhereOptions<Document>[];
|
||||
} = {
|
||||
teamId: user.teamId,
|
||||
[Op.and]: [
|
||||
{
|
||||
|
@ -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(),
|
||||
}),
|
||||
});
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -12,4 +12,4 @@ export default {
|
||||
web,
|
||||
worker,
|
||||
cron,
|
||||
};
|
||||
} as const;
|
||||
|
@ -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 {
|
||||
|
@ -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];
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -59,13 +59,13 @@ type Token = {
|
||||
};
|
||||
|
||||
type Segment = {
|
||||
beforeTokens: Array<Token>;
|
||||
afterTokens: Array<Token>;
|
||||
beforeTokens: Token[];
|
||||
afterTokens: Token[];
|
||||
beforeIndex: number;
|
||||
afterIndex: number;
|
||||
|
||||
beforeMap: object;
|
||||
afterMap: object;
|
||||
beforeMap: Record<string, number[]>;
|
||||
afterMap: Record<string, number[]>;
|
||||
};
|
||||
|
||||
type MatchT = {
|
||||
@ -232,7 +232,7 @@ function Match(
|
||||
*
|
||||
* @return {Array} The list of tokens.
|
||||
*/
|
||||
function htmlToTokens(html: string): Array<Token> {
|
||||
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<Token>): 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<Token>,
|
||||
afterTokens: Array<Token>,
|
||||
beforeTokens: Token[],
|
||||
afterTokens: Token[],
|
||||
beforeIndex: number,
|
||||
afterIndex: number
|
||||
): Segment {
|
||||
@ -759,8 +759,8 @@ function findMatchingBlocks(segment: Segment): Array<MatchT> {
|
||||
* - {number} endInAfter The end of the range in the list of after tokens.
|
||||
*/
|
||||
function calculateOperations(
|
||||
beforeTokens: Array<Token>,
|
||||
afterTokens: Array<Token>
|
||||
beforeTokens: Token[],
|
||||
afterTokens: Token[]
|
||||
): Array<Operation> {
|
||||
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<Token>) => void,
|
||||
tagFn: (tokens: Array<Token>) => 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,
|
||||
|
@ -6,7 +6,7 @@ import { Collection, Document, Star } from "@server/models";
|
||||
export async function collectionIndexing(
|
||||
teamId: string,
|
||||
{ transaction }: FindOptions<Collection>
|
||||
): 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<string, string | null> = {};
|
||||
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<string, string | null> = {};
|
||||
sortable.forEach((star) => {
|
||||
indexedStars[star.id] = star.index;
|
||||
});
|
||||
|
@ -4,6 +4,7 @@ import Logger from "@server/logging/Logger";
|
||||
|
||||
export type Chunk = {
|
||||
file: string;
|
||||
imports: string[];
|
||||
src: string;
|
||||
isEntry?: boolean;
|
||||
};
|
||||
|
@ -10,7 +10,11 @@ const tableShouldBeSkippedCache = new WeakMap<HTMLTableElement, boolean>();
|
||||
|
||||
function getAlignment(node: HTMLElement) {
|
||||
return node
|
||||
? (node.getAttribute("align") || node.style.textAlign || "").toLowerCase()
|
||||
? ((
|
||||
node.getAttribute("align") ||
|
||||
node.style.textAlign ||
|
||||
""
|
||||
).toLowerCase() as "left" | "right" | "center")
|
||||
: "";
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,7 @@ export function CannotUseWithout(
|
||||
validator: {
|
||||
validate<T>(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) {
|
||||
|
@ -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<string, Primitive>) => Command;
|
||||
export type CommandFactory = (
|
||||
attrs?: Record<string, Primitive>
|
||||
) => Command | void;
|
||||
|
||||
export type WidgetProps = { rtl: boolean; readOnly: boolean | undefined };
|
||||
|
||||
|
@ -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<string, CommandFactory> = {};
|
||||
|
||||
// @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<string, Primitive>
|
||||
) => {
|
||||
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<string, Primitive>) =>
|
||||
_value.forEach((callback) => apply(callback, attrs));
|
||||
} else if (typeof _value === "function") {
|
||||
commands[_name] = (attrs: Record<string, Primitive>) =>
|
||||
apply(_value, attrs);
|
||||
commands[_name] = ((attrs: Record<string, Primitive>) =>
|
||||
apply(_value, attrs)) as CommandFactory;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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<string, number>
|
||||
);
|
||||
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 = <T>(freqs: [T, number][]) =>
|
||||
freqs.sort((a, b) => (a[1] >= b[1] ? -1 : 1));
|
||||
|
@ -21,6 +21,7 @@ export const nameToEmoji: Record<string, string> = 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;
|
||||
}, {});
|
||||
|
@ -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;
|
||||
|
@ -220,7 +220,7 @@ export default class Heading extends Node {
|
||||
get plugins() {
|
||||
const getAnchors = (doc: ProsemirrorNode) => {
|
||||
const decorations: Decoration[] = [];
|
||||
const previouslySeen = {};
|
||||
const previouslySeen: Record<string, number> = {};
|
||||
|
||||
doc.descendants((node, pos) => {
|
||||
if (node.type.name !== this.name) {
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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, boolean> =
|
||||
{
|
||||
[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",
|
||||
|
@ -279,7 +279,7 @@ export class ProsemirrorHelper {
|
||||
*/
|
||||
static getHeadings(doc: Node, schema: Schema) {
|
||||
const headings: Heading[] = [];
|
||||
const previouslySeen = {};
|
||||
const previouslySeen: Record<string, number> = {};
|
||||
|
||||
doc.forEach((node) => {
|
||||
if (node.type.name === "heading") {
|
||||
|
@ -75,7 +75,7 @@ class Storage {
|
||||
* when localStorage is not available.
|
||||
*/
|
||||
class MemoryStorage {
|
||||
private data = {};
|
||||
private data: Record<string, string> = {};
|
||||
|
||||
getItem(key: string) {
|
||||
return this.data[key] || null;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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<EmojiCategory, string[]> =
|
||||
Categories.reduce((obj, { id, emojis }) => {
|
||||
const category = EmojiCategory[capitalize(id)];
|
||||
const key = capitalize(id) as EmojiCategory;
|
||||
const category = EmojiCategory[key];
|
||||
if (!category) {
|
||||
return obj;
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ const stripEmojis = (value: string) => value.replace(regex, "");
|
||||
|
||||
const cleanValue = (value: string) => stripEmojis(deburr(value));
|
||||
|
||||
function getSortByField<T>(
|
||||
function getSortByField<T extends Record<string, any>>(
|
||||
item: T,
|
||||
keyOrCallback: string | ((item: T) => string)
|
||||
) {
|
||||
@ -25,7 +25,7 @@ function getSortByField<T>(
|
||||
return cleanValue(field);
|
||||
}
|
||||
|
||||
function naturalSortBy<T>(
|
||||
function naturalSortBy<T extends Record<string, any>>(
|
||||
items: T[],
|
||||
key: string | ((item: T) => string),
|
||||
sortOptions?: NaturalSortOptions
|
||||
|
@ -23,7 +23,6 @@
|
||||
"noEmit": true,
|
||||
"skipLibCheck": true,
|
||||
"ignoreDeprecations": "5.0",
|
||||
"suppressImplicitAnyIndexErrors": true,
|
||||
"target": "es2020",
|
||||
"paths": {
|
||||
"@server/*": ["./server/*"],
|
||||
|
Reference in New Issue
Block a user