chore: Move to Typescript (#2783)

This PR moves the entire project to Typescript. Due to the ~1000 ignores this will lead to a messy codebase for a while, but the churn is worth it – all of those ignore comments are places that were never type-safe previously.

closes #1282
This commit is contained in:
Tom Moor
2021-11-29 06:40:55 -08:00
committed by GitHub
parent 25ccfb5d04
commit 15b1069bcc
1017 changed files with 17410 additions and 54942 deletions

View File

@@ -1,13 +1,12 @@
// @flow
import { CollectionIcon, EditIcon, PlusIcon } from "outline-icons";
import * as React from "react";
import stores from "stores";
import CollectionEdit from "scenes/CollectionEdit";
import CollectionNew from "scenes/CollectionNew";
import DynamicCollectionIcon from "components/CollectionIcon";
import { createAction } from "actions";
import { CollectionSection } from "actions/sections";
import history from "utils/history";
import stores from "~/stores";
import CollectionEdit from "~/scenes/CollectionEdit";
import CollectionNew from "~/scenes/CollectionNew";
import DynamicCollectionIcon from "~/components/CollectionIcon";
import { createAction } from "~/actions";
import { CollectionSection } from "~/actions/sections";
import history from "~/utils/history";
export const openCollection = createAction({
name: ({ t }) => t("Open collection"),
@@ -16,7 +15,6 @@ export const openCollection = createAction({
icon: <CollectionIcon />,
children: ({ stores }) => {
const collections = stores.collections.orderedData;
return collections.map((collection) => ({
// Note: using url which includes the slug rather than id here to bust
// cache if the collection is renamed
@@ -39,7 +37,6 @@ export const createCollection = createAction({
perform: ({ t, event }) => {
event?.preventDefault();
event?.stopPropagation();
stores.dialogs.openModal({
title: t("Create a collection"),
content: <CollectionNew onSubmit={stores.dialogs.closeAllModals} />,
@@ -55,6 +52,8 @@ export const editCollection = createAction({
!!activeCollectionId &&
stores.policies.abilities(activeCollectionId).update,
perform: ({ t, activeCollectionId }) => {
if (!activeCollectionId) return;
stores.dialogs.openModal({
title: t("Edit collection"),
content: (

View File

@@ -1,11 +1,10 @@
// @flow
import { ToolsIcon, TrashIcon } from "outline-icons";
import * as React from "react";
import stores from "stores";
import { createAction } from "actions";
import { DebugSection } from "actions/sections";
import env from "env";
import { deleteAllDatabases } from "utils/developer";
import stores from "~/stores";
import { createAction } from "~/actions";
import { DebugSection } from "~/actions/sections";
import env from "~/env";
import { deleteAllDatabases } from "~/utils/developer";
export const clearIndexedDB = createAction({
name: ({ t }) => t("Delete IndexedDB cache"),

View File

@@ -1,4 +1,3 @@
// @flow
import invariant from "invariant";
import {
DownloadIcon,
@@ -12,12 +11,12 @@ import {
ImportIcon,
} from "outline-icons";
import * as React from "react";
import DocumentTemplatize from "scenes/DocumentTemplatize";
import { createAction } from "actions";
import { DocumentSection } from "actions/sections";
import getDataTransferFiles from "utils/getDataTransferFiles";
import history from "utils/history";
import { newDocumentPath } from "utils/routeHelpers";
import DocumentTemplatize from "~/scenes/DocumentTemplatize";
import { createAction } from "~/actions";
import { DocumentSection } from "~/actions/sections";
import getDataTransferFiles from "~/utils/getDataTransferFiles";
import history from "~/utils/history";
import { newDocumentPath } from "~/utils/routeHelpers";
export const openDocument = createAction({
name: ({ t }) => t("Open document"),
@@ -36,9 +35,7 @@ export const openDocument = createAction({
id: path.url,
name: path.title,
icon: () =>
stores.documents.get(path.id)?.isStarred ? (
<StarredIcon />
) : undefined,
stores.documents.get(path.id)?.isStarred ? <StarredIcon /> : null,
section: DocumentSection,
perform: () => history.push(path.url),
}));
@@ -65,13 +62,12 @@ export const starDocument = createAction({
visible: ({ activeDocumentId, stores }) => {
if (!activeDocumentId) return false;
const document = stores.documents.get(activeDocumentId);
return (
!document?.isStarred && stores.policies.abilities(activeDocumentId).star
);
},
perform: ({ activeDocumentId, stores }) => {
if (!activeDocumentId) return false;
if (!activeDocumentId) return;
const document = stores.documents.get(activeDocumentId);
document?.star();
@@ -86,14 +82,13 @@ export const unstarDocument = createAction({
visible: ({ activeDocumentId, stores }) => {
if (!activeDocumentId) return false;
const document = stores.documents.get(activeDocumentId);
return (
!!document?.isStarred &&
stores.policies.abilities(activeDocumentId).unstar
);
},
perform: ({ activeDocumentId, stores }) => {
if (!activeDocumentId) return false;
if (!activeDocumentId) return;
const document = stores.documents.get(activeDocumentId);
document?.unstar();
@@ -109,7 +104,7 @@ export const downloadDocument = createAction({
visible: ({ activeDocumentId, stores }) =>
!!activeDocumentId && stores.policies.abilities(activeDocumentId).download,
perform: ({ activeDocumentId, stores }) => {
if (!activeDocumentId) return false;
if (!activeDocumentId) return;
const document = stores.documents.get(activeDocumentId);
document?.download();
@@ -125,16 +120,16 @@ export const duplicateDocument = createAction({
visible: ({ activeDocumentId, stores }) =>
!!activeDocumentId && stores.policies.abilities(activeDocumentId).update,
perform: async ({ activeDocumentId, t, stores }) => {
if (!activeDocumentId) return false;
if (!activeDocumentId) return;
const document = stores.documents.get(activeDocumentId);
invariant(document, "Document must exist");
const duped = await document.duplicate();
// when duplicating, go straight to the duplicated document content
history.push(duped.url);
stores.toasts.showToast(t("Document duplicated"), { type: "success" });
stores.toasts.showToast(t("Document duplicated"), {
type: "success",
});
},
});
@@ -150,7 +145,7 @@ export const printDocument = createAction({
});
export const importDocument = createAction({
name: ({ t, activeDocumentId }) => t("Import document"),
name: ({ t }) => t("Import document"),
section: DocumentSection,
icon: <ImportIcon />,
keywords: "upload",
@@ -158,18 +153,20 @@ export const importDocument = createAction({
if (activeDocumentId) {
return !!stores.policies.abilities(activeDocumentId).createChildDocument;
}
if (activeCollectionId) {
return !!stores.policies.abilities(activeCollectionId).update;
}
return false;
},
perform: ({ activeCollectionId, activeDocumentId, stores }) => {
const { documents, toasts } = stores;
const input = document.createElement("input");
input.type = "file";
input.accept = documents.importFileTypes.join(", ");
input.onchange = async (ev: SyntheticEvent<>) => {
input.onchange = async (ev: Event) => {
const files = getDataTransferFiles(ev);
try {
@@ -187,10 +184,10 @@ export const importDocument = createAction({
toasts.showToast(err.message, {
type: "error",
});
throw err;
}
};
input.click();
},
});
@@ -202,9 +199,7 @@ export const createTemplate = createAction({
keywords: "new create template",
visible: ({ activeCollectionId, activeDocumentId, stores }) => {
if (!activeDocumentId) return false;
const document = stores.documents.get(activeDocumentId);
return (
!!activeCollectionId &&
stores.policies.abilities(activeCollectionId).update &&
@@ -212,6 +207,8 @@ export const createTemplate = createAction({
);
},
perform: ({ activeDocumentId, stores, t, event }) => {
if (!activeDocumentId) return;
event?.preventDefault();
event?.stopPropagation();

View File

@@ -1,4 +1,3 @@
// @flow
import {
HomeIcon,
SearchIcon,
@@ -17,12 +16,12 @@ import {
changelogUrl,
mailToUrl,
githubIssuesUrl,
} from "shared/utils/routeHelpers";
import stores from "stores";
import KeyboardShortcuts from "scenes/KeyboardShortcuts";
import { createAction } from "actions";
import { NavigationSection } from "actions/sections";
import history from "utils/history";
} from "@shared/utils/routeHelpers";
import stores from "~/stores";
import KeyboardShortcuts from "~/scenes/KeyboardShortcuts";
import { createAction } from "~/actions";
import { NavigationSection } from "~/actions/sections";
import history from "~/utils/history";
import {
settingsPath,
homePath,
@@ -31,7 +30,7 @@ import {
templatesPath,
archivePath,
trashPath,
} from "utils/routeHelpers";
} from "~/utils/routeHelpers";
export const navigateToHome = createAction({
name: ({ t }) => t("Home"),

View File

@@ -1,9 +1,9 @@
// @flow
import { SunIcon, MoonIcon, BrowserIcon } from "outline-icons";
import * as React from "react";
import stores from "stores";
import { createAction } from "actions";
import { SettingsSection } from "actions/sections";
import stores from "~/stores";
import { Theme } from "~/stores/UiStore";
import { createAction } from "~/actions";
import { SettingsSection } from "~/actions/sections";
export const changeToDarkTheme = createAction({
name: ({ t }) => t("Dark"),
@@ -12,7 +12,7 @@ export const changeToDarkTheme = createAction({
keywords: "theme dark night",
section: SettingsSection,
selected: () => stores.ui.theme === "dark",
perform: () => stores.ui.setTheme("dark"),
perform: () => stores.ui.setTheme(Theme.Dark),
});
export const changeToLightTheme = createAction({
@@ -22,7 +22,7 @@ export const changeToLightTheme = createAction({
keywords: "theme light day",
section: SettingsSection,
selected: () => stores.ui.theme === "light",
perform: () => stores.ui.setTheme("light"),
perform: () => stores.ui.setTheme(Theme.Light),
});
export const changeToSystemTheme = createAction({
@@ -32,7 +32,7 @@ export const changeToSystemTheme = createAction({
keywords: "theme system default",
section: SettingsSection,
selected: () => stores.ui.theme === "system",
perform: () => stores.ui.setTheme("system"),
perform: () => stores.ui.setTheme(Theme.System),
});
export const changeTheme = createAction({

View File

@@ -1,10 +1,9 @@
// @flow
import { PlusIcon } from "outline-icons";
import * as React from "react";
import stores from "stores";
import Invite from "scenes/Invite";
import { createAction } from "actions";
import { UserSection } from "actions/sections";
import stores from "~/stores";
import Invite from "~/scenes/Invite";
import { createAction } from "~/actions";
import { UserSection } from "~/actions/sections";
export const inviteUser = createAction({
name: ({ t }) => `${t("Invite people")}`,

View File

@@ -1,17 +1,22 @@
// @flow
import { flattenDeep } from "lodash";
import * as React from "react";
import { $Diff } from "utility-types";
import { v4 as uuidv4 } from "uuid";
import type {
import {
Action,
ActionContext,
CommandBarAction,
MenuItemClickable,
MenuItemButton,
MenuItemWithChildren,
} from "types";
} from "~/types";
export function createAction(
definition: $Diff<Action, { id?: string }>
definition: $Diff<
Action,
{
id?: string;
}
>
): Action {
return {
id: uuidv4(),
@@ -22,7 +27,7 @@ export function createAction(
export function actionToMenuItem(
action: Action,
context: ActionContext
): MenuItemClickable | MenuItemWithChildren {
): MenuItemButton | MenuItemWithChildren {
function resolve<T>(value: any): T {
if (typeof value === "function") {
return value(context);
@@ -31,18 +36,20 @@ export function actionToMenuItem(
return value;
}
const resolvedIcon = resolve<React.Element<any>>(action.icon);
const resolvedIcon = resolve<React.ReactElement<any>>(action.icon);
const resolvedChildren = resolve<Action[]>(action.children);
const visible = action.visible ? action.visible(context) : true;
const title = resolve<string>(action.name);
const icon =
resolvedIcon && action.iconInContextMenu !== false
? React.cloneElement(resolvedIcon, { color: "currentColor" })
? React.cloneElement(resolvedIcon, {
color: "currentColor",
})
: undefined;
if (resolvedChildren) {
return {
type: "submenu",
title,
icon,
items: resolvedChildren
@@ -53,6 +60,7 @@ export function actionToMenuItem(
}
return {
type: "button",
title,
icon,
visible,
@@ -77,12 +85,11 @@ export function actionToKBar(
return [];
}
const resolvedIcon = resolve<React.Element<any>>(action.icon);
const resolvedIcon = resolve<React.ReactElement<any>>(action.icon);
const resolvedChildren = resolve<Action[]>(action.children);
const resolvedSection = resolve<string>(action.section);
const resolvedName = resolve<string>(action.name);
const resolvedPlaceholder = resolve<string>(action.placeholder);
const children = resolvedChildren
? flattenDeep(resolvedChildren.map((a) => actionToKBar(a, context))).filter(
(a) => !!a
@@ -99,19 +106,17 @@ export function actionToKBar(
.filter((c) => !!c.keywords)
.map((c) => c.keywords)
.join(" ")}`,
shortcut: action.shortcut,
shortcut: action.shortcut || [],
icon: resolvedIcon
? React.cloneElement(resolvedIcon, { color: "currentColor" })
? React.cloneElement(resolvedIcon, {
color: "currentColor",
})
: undefined,
perform: action.perform
? () => action.perform && action.perform(context)
: undefined,
children: children.length ? children.map((a) => a.id) : undefined,
},
].concat(
children.map((child) => ({
...child,
parent: action.id,
}))
);
// @ts-expect-error ts-migrate(2769) FIXME: No overload matches this call.
].concat(children.map((child) => ({ ...child, parent: action.id })));
}

View File

@@ -1,4 +1,3 @@
// @flow
import { rootCollectionActions } from "./definitions/collections";
import { rootDebugActions } from "./definitions/debug";
import { rootDocumentActions } from "./definitions/documents";

View File

@@ -1,5 +1,4 @@
// @flow
import { type ActionContext } from "types";
import { ActionContext } from "~/types";
export const CollectionSection = ({ t }: ActionContext) => t("Collection");