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:
@@ -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: (
|
||||
@@ -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"),
|
||||
@@ -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();
|
||||
|
||||
@@ -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"),
|
||||
@@ -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({
|
||||
@@ -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")}…`,
|
||||
@@ -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 })));
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import { rootCollectionActions } from "./definitions/collections";
|
||||
import { rootDebugActions } from "./definitions/debug";
|
||||
import { rootDocumentActions } from "./definitions/documents";
|
||||
@@ -1,5 +1,4 @@
|
||||
// @flow
|
||||
import { type ActionContext } from "types";
|
||||
import { ActionContext } from "~/types";
|
||||
|
||||
export const CollectionSection = ({ t }: ActionContext) => t("Collection");
|
||||
|
||||
Reference in New Issue
Block a user