Skip to content

Commit 5f1bfd1

Browse files
Merge branch 'outline:main' into main
2 parents b92fc14 + 9680e57 commit 5f1bfd1

File tree

109 files changed

+1429
-459
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

109 files changed

+1429
-459
lines changed

app/components/ConfirmMoveDialog.tsx

+7-5
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,13 @@ function ConfirmMoveDialog({ collection, item, ...rest }: Props) {
2121
const { documents, dialogs, collections } = useStores();
2222
const { t } = useTranslation();
2323
const prevCollection = collections.get(item.collectionId!);
24-
const accessMapping = {
25-
[CollectionPermission.ReadWrite]: t("view and edit access"),
26-
[CollectionPermission.Read]: t("view only access"),
27-
null: t("no access"),
28-
};
24+
const accessMapping: Record<Partial<CollectionPermission> | "null", string> =
25+
{
26+
[CollectionPermission.Admin]: t("manage access"),
27+
[CollectionPermission.ReadWrite]: t("view and edit access"),
28+
[CollectionPermission.Read]: t("view only access"),
29+
null: t("no access"),
30+
};
2931

3032
const handleSubmit = async () => {
3133
await documents.move({

app/components/ConnectionStatus.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ function ConnectionStatus() {
3535
};
3636

3737
const message = ui.multiplayerErrorCode
38-
? codeToMessage[ui.multiplayerErrorCode]
38+
? codeToMessage[ui.multiplayerErrorCode as keyof typeof codeToMessage]
3939
: undefined;
4040

4141
return ui.multiplayerStatus === "connecting" ||

app/components/LocaleTime.tsx

+6-3
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,15 @@ const LocaleTime: React.FC<Props> = ({
3939
relative,
4040
tooltipDelay,
4141
}: Props) => {
42-
const userLocale: string = useUserLocale() || "";
43-
const dateFormatLong = {
42+
const userLocale = useUserLocale();
43+
const dateFormatLong: Record<string, string> = {
4444
en_US: "MMMM do, yyyy h:mm a",
4545
fr_FR: "'Le 'd MMMM yyyy 'à' H:mm",
4646
};
47-
const formatLocaleLong = dateFormatLong[userLocale] ?? "MMMM do, yyyy h:mm a";
47+
const formatLocaleLong =
48+
(userLocale ? dateFormatLong[userLocale] : undefined) ??
49+
"MMMM do, yyyy h:mm a";
50+
// @ts-expect-error fallback to formatLocaleLong
4851
const formatLocale = format?.[userLocale] ?? formatLocaleLong;
4952
const [_, setMinutesMounted] = React.useState(0); // eslint-disable-line @typescript-eslint/no-unused-vars
5053
const callback = React.useRef<() => void>();

app/components/PaginatedList.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ export interface PaginatedItem {
1919
}
2020

2121
type Props<T> = WithTranslation &
22-
RootStore & {
22+
RootStore &
23+
React.HTMLAttributes<HTMLDivElement> & {
2324
fetch?: (
2425
options: Record<string, any> | undefined
2526
) => Promise<T[] | undefined> | undefined;

app/editor/components/EmojiMenu.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const EmojiMenu = (props: Props) => {
2929
.map((item) => {
3030
// We snake_case the shortcode for backwards compatability with gemoji to
3131
// avoid multiple formats being written into documents.
32+
// @ts-expect-error emojiMartToGemoji key
3233
const shortcode = snakeCase(emojiMartToGemoji[item.id] || item.id);
3334
const emoji = item.value;
3435

app/editor/extensions/MentionMenu.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export default class MentionMenuExtension extends Suggestion {
88
get defaultOptions() {
99
return {
1010
// ported from https://github.com/tc39/proposal-regexp-unicode-property-escapes#unicode-aware-version-of-w
11-
openRegex: /(?:^|\s|\()@([\p{L}\p{M}\d]+)?$/u,
11+
openRegex: /(?:^|\s|\()@([\p{L}\p{M}\d\s{1}]+)?$/u,
1212
closeRegex: /(?:^|\s|\()@(([\p{L}\p{M}\d]*\s{2})|(\s+[\p{L}\p{M}\d]+))$/u,
1313
};
1414
}

app/editor/menus/code.tsx

+5-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@ export default function codeMenuItems(
1414
): MenuItem[] {
1515
const node = state.selection.$from.node();
1616

17-
const allLanguages = Object.entries(LANGUAGES);
17+
const allLanguages = Object.entries(LANGUAGES) as [
18+
keyof typeof LANGUAGES,
19+
string
20+
][];
1821
const frequentLanguages = getFrequentCodeLanguages();
1922

2023
const frequentLangMenuItems = frequentLanguages.map((value) => {
@@ -49,6 +52,7 @@ export default function codeMenuItems(
4952
visible: !readOnly,
5053
name: "code_block",
5154
icon: <ExpandedIcon />,
55+
// @ts-expect-error We have a fallback for incorrect mapping
5256
label: LANGUAGES[node.attrs.language ?? "none"],
5357
children: languageMenuItems,
5458
},

app/hooks/useUserLocale.ts

+2-9
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,9 @@ import useCurrentUser from "./useCurrentUser";
33
/**
44
* Returns the user's locale, or undefined if the user is not logged in.
55
*
6-
* @param languageCode Whether to only return the language code
76
* @returns The user's locale, or undefined if the user is not logged in
87
*/
9-
export default function useUserLocale(languageCode?: boolean) {
8+
export default function useUserLocale() {
109
const user = useCurrentUser({ rejectOnEmpty: false });
11-
12-
if (!user?.language) {
13-
return undefined;
14-
}
15-
16-
const { language } = user;
17-
return languageCode ? language.split("_")[0] : language;
10+
return user?.language;
1811
}

app/models/FileOperation.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import { computed, observable } from "mobx";
2-
import { FileOperationFormat, FileOperationType } from "@shared/types";
2+
import {
3+
FileOperationFormat,
4+
FileOperationState,
5+
FileOperationType,
6+
} from "@shared/types";
37
import { bytesToHumanReadable } from "@shared/utils/files";
48
import User from "./User";
59
import Model from "./base/Model";
@@ -10,7 +14,7 @@ class FileOperation extends Model {
1014
id: string;
1115

1216
@observable
13-
state: string;
17+
state: FileOperationState;
1418

1519
name: string;
1620

app/models/User.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
UserRole,
1212
} from "@shared/types";
1313
import type { NotificationSettings } from "@shared/types";
14+
import { locales } from "@shared/utils/date";
1415
import { client } from "~/utils/ApiClient";
1516
import Document from "./Document";
1617
import Group from "./Group";
@@ -39,7 +40,7 @@ class User extends ParanoidModel {
3940

4041
@Field
4142
@observable
42-
language: string;
43+
language: keyof typeof locales;
4344

4445
@Field
4546
@observable

app/models/base/Model.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export default abstract class Model {
4040
* @returns A promise that resolves when loading is complete.
4141
*/
4242
async loadRelations(
43+
this: Model,
4344
options: { withoutPolicies?: boolean } = {}
4445
): Promise<any> {
4546
const relations = getRelationsForModelClass(
@@ -62,7 +63,7 @@ export default abstract class Model {
6263
if ("fetch" in store) {
6364
const id = this[properties.idKey];
6465
if (id) {
65-
promises.push(store.fetch(id));
66+
promises.push(store.fetch(id as string));
6667
}
6768
}
6869
}
@@ -145,6 +146,7 @@ export default abstract class Model {
145146
if (key === "initialized") {
146147
continue;
147148
}
149+
// @ts-expect-error TODO
148150
this[key] = data[key];
149151
} catch (error) {
150152
Logger.warn(`Error setting ${key} on model`, error);

app/models/decorators/Relation.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ type RelationOptions<T = Model> = {
1717

1818
type RelationProperties<T = Model> = {
1919
/** The name of the property on the model that stores the ID of the relation. */
20-
idKey: string;
20+
idKey: keyof T;
2121
/** A function that returns the class of the relation. */
2222
relationClassResolver: () => typeof Model;
2323
/** Options for the relation. */

app/scenes/Collection/components/MembershipPreview.tsx

+6-2
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,12 @@ const MembershipPreview = ({ collection, limit = 8 }: Props) => {
4343
memberships.fetchPage(options),
4444
groupMemberships.fetchPage(options),
4545
]);
46-
setUsersCount(users[PAGINATION_SYMBOL].total);
47-
setGroupsCount(groups[PAGINATION_SYMBOL].total);
46+
if (users[PAGINATION_SYMBOL]) {
47+
setUsersCount(users[PAGINATION_SYMBOL].total);
48+
}
49+
if (groups[PAGINATION_SYMBOL]) {
50+
setGroupsCount(groups[PAGINATION_SYMBOL].total);
51+
}
4852
} finally {
4953
setIsLoading(false);
5054
}

app/scenes/Invite.tsx

+3-2
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,11 @@ function Invite({ onSubmit }: Props) {
7171
[onSubmit, invites, role, t, users]
7272
);
7373

74-
const handleChange = React.useCallback((ev, index) => {
74+
const handleChange = React.useCallback((ev, index: number) => {
7575
setInvites((prevInvites) => {
7676
const newInvites = [...prevInvites];
77-
newInvites[index][ev.target.name] = ev.target.value;
77+
newInvites[index][ev.target.name as keyof InviteRequest] =
78+
ev.target.value;
7879
return newInvites;
7980
});
8081
}, []);

app/scenes/Settings/Members.tsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,9 @@ function Members() {
6262
filter,
6363
role,
6464
});
65-
setTotalPages(Math.ceil(response[PAGINATION_SYMBOL].total / limit));
65+
if (response[PAGINATION_SYMBOL]) {
66+
setTotalPages(Math.ceil(response[PAGINATION_SYMBOL].total / limit));
67+
}
6668
setUserIds(response.map((u: User) => u.id));
6769
} finally {
6870
setIsLoading(false);

app/scenes/Settings/Shares.tsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,9 @@ function Shares() {
4747
sort,
4848
direction,
4949
});
50-
setTotalPages(Math.ceil(response[PAGINATION_SYMBOL].total / limit));
50+
if (response[PAGINATION_SYMBOL]) {
51+
setTotalPages(Math.ceil(response[PAGINATION_SYMBOL].total / limit));
52+
}
5153
setShareIds(response.map((u: Share) => u.id));
5254
} finally {
5355
setIsLoading(false);

app/scenes/Settings/components/FileOperationListItem.tsx

+3-2
Original file line numberDiff line numberDiff line change
@@ -38,16 +38,17 @@ const FileOperationListItem = ({ fileOperation }: Props) => {
3838
[FileOperationState.Error]: t("Failed"),
3939
};
4040

41-
const iconMapping = {
41+
const iconMapping: Record<FileOperationState, React.JSX.Element> = {
4242
[FileOperationState.Creating]: <Spinner />,
4343
[FileOperationState.Uploading]: <Spinner />,
4444
[FileOperationState.Expired]: <ArchiveIcon color={theme.textTertiary} />,
4545
[FileOperationState.Complete]: <DoneIcon color={theme.accent} />,
4646
[FileOperationState.Error]: <WarningIcon color={theme.danger} />,
4747
};
4848

49-
const formatMapping = {
49+
const formatMapping: Record<FileOperationFormat, string> = {
5050
[FileOperationFormat.JSON]: "JSON",
51+
[FileOperationFormat.Notion]: "Notion",
5152
[FileOperationFormat.MarkdownZip]: "Markdown",
5253
[FileOperationFormat.HTMLZip]: "HTML",
5354
[FileOperationFormat.PDF]: "PDF",

app/stores/GroupMembershipsStore.ts

+7-3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@ import GroupMembership from "~/models/GroupMembership";
55
import { PaginationParams } from "~/types";
66
import { client } from "~/utils/ApiClient";
77
import RootStore from "./RootStore";
8-
import Store, { PAGINATION_SYMBOL, RPCAction } from "./base/Store";
8+
import Store, {
9+
PAGINATION_SYMBOL,
10+
PaginatedResponse,
11+
RPCAction,
12+
} from "./base/Store";
913

1014
export default class GroupMembershipsStore extends Store<GroupMembership> {
1115
actions = [RPCAction.Create, RPCAction.Delete];
@@ -24,7 +28,7 @@ export default class GroupMembershipsStore extends Store<GroupMembership> {
2428
documentId?: string;
2529
collectionId?: string;
2630
groupId?: string;
27-
}): Promise<GroupMembership[]> => {
31+
}): Promise<PaginatedResponse<GroupMembership>> => {
2832
this.isFetching = true;
2933

3034
try {
@@ -41,7 +45,7 @@ export default class GroupMembershipsStore extends Store<GroupMembership> {
4145
: await client.post(`/groupMemberships.list`, params);
4246
invariant(res?.data, "Data not available");
4347

44-
let response: GroupMembership[] = [];
48+
let response: PaginatedResponse<GroupMembership> = [];
4549
runInAction(`GroupMembershipsStore#fetchPage`, () => {
4650
res.data.groups?.forEach(this.rootStore.groups.add);
4751
res.data.documents?.forEach(this.rootStore.documents.add);

app/stores/MembershipsStore.ts

+7-3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@ import Membership from "~/models/Membership";
55
import { PaginationParams } from "~/types";
66
import { client } from "~/utils/ApiClient";
77
import RootStore from "./RootStore";
8-
import Store, { PAGINATION_SYMBOL, RPCAction } from "./base/Store";
8+
import Store, {
9+
PAGINATION_SYMBOL,
10+
PaginatedResponse,
11+
RPCAction,
12+
} from "./base/Store";
913

1014
export default class MembershipsStore extends Store<Membership> {
1115
actions = [RPCAction.Create, RPCAction.Delete];
@@ -17,14 +21,14 @@ export default class MembershipsStore extends Store<Membership> {
1721
@action
1822
fetchPage = async (
1923
params: (PaginationParams & { id?: string }) | undefined
20-
): Promise<Membership[]> => {
24+
): Promise<PaginatedResponse<Membership>> => {
2125
this.isFetching = true;
2226

2327
try {
2428
const res = await client.post(`/collections.memberships`, params);
2529
invariant(res?.data, "Data not available");
2630

27-
let response: Membership[] = [];
31+
let response: PaginatedResponse<Membership> = [];
2832
runInAction(`MembershipsStore#fetchPage`, () => {
2933
res.data.users.forEach(this.rootStore.users.add);
3034
response = res.data.memberships.map(this.add);

app/stores/RootStore.ts

+10-9
Original file line numberDiff line numberDiff line change
@@ -102,14 +102,11 @@ export default class RootStore {
102102
*
103103
* @param modelName
104104
*/
105-
public getStoreForModelName<K extends keyof RootStore>(
106-
modelName: string
107-
): RootStore[K] {
105+
public getStoreForModelName<K extends keyof RootStore>(modelName: string) {
108106
const storeName = this.getStoreNameForModelName(modelName);
109107
const store = this[storeName];
110108
invariant(store, `No store found for model name "${modelName}"`);
111-
112-
return store;
109+
return store as RootStore[K];
113110
}
114111

115112
/**
@@ -118,8 +115,9 @@ export default class RootStore {
118115
public clear() {
119116
Object.getOwnPropertyNames(this)
120117
.filter((key) => ["auth", "ui"].includes(key) === false)
121-
.forEach((key) => {
122-
this[key]?.clear?.();
118+
.forEach((key: keyof RootStore) => {
119+
// @ts-expect-error clear exists on all stores
120+
"clear" in this[key] && this[key].clear();
123121
});
124122
}
125123

@@ -128,14 +126,17 @@ export default class RootStore {
128126
*
129127
* @param StoreClass
130128
*/
131-
private registerStore<T = typeof Store>(StoreClass: T, name?: string) {
129+
private registerStore<T = typeof Store>(
130+
StoreClass: T,
131+
name?: keyof RootStore
132+
) {
132133
// @ts-expect-error TS thinks we are instantiating an abstract class.
133134
const store = new StoreClass(this);
134135
const storeName = name ?? this.getStoreNameForModelName(store.modelName);
135136
this[storeName] = store;
136137
}
137138

138139
private getStoreNameForModelName(modelName: string) {
139-
return pluralize(lowerFirst(modelName));
140+
return pluralize(lowerFirst(modelName)) as keyof RootStore;
140141
}
141142
}

0 commit comments

Comments
 (0)