From 15b1069bcc21d58752fd51f3c7f4ffb10013678b Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Mon, 29 Nov 2021 06:40:55 -0800 Subject: [PATCH] chore: Move to Typescript (#2783) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .babelrc | 2 +- .circleci/config.yml | 4 +- .dockerignore | 5 +- .eslintrc | 85 +- .flowconfig | 44 - .vscode/settings.json | 8 +- README.md | 7 +- __mocks__/bull.js | 1 - app/.eslintrc | 9 + app/.jestconfig.json | 10 +- .../{collections.js => collections.tsx} | 19 +- .../definitions/{debug.js => debug.tsx} | 11 +- .../{documents.js => documents.tsx} | 47 +- .../{navigation.js => navigation.tsx} | 15 +- .../definitions/{settings.js => settings.tsx} | 14 +- .../definitions/{users.js => users.tsx} | 9 +- app/actions/{index.js => index.ts} | 43 +- app/actions/{root.js => root.ts} | 1 - app/actions/{sections.js => sections.ts} | 3 +- app/components/{Actions.js => Actions.ts} | 3 +- app/components/{Analytics.js => Analytics.ts} | 18 +- app/components/{Arrow.js => Arrow.tsx} | 1 - .../{GoogleLogo.js => GoogleLogo.tsx} | 7 +- .../{MicrosoftLogo.js => MicrosoftLogo.tsx} | 7 +- .../AuthLogo/{SlackLogo.js => SlackLogo.tsx} | 7 +- .../AuthLogo/{index.js => index.tsx} | 12 +- .../{Authenticated.js => Authenticated.tsx} | 13 +- .../Avatar/{Avatar.js => Avatar.tsx} | 27 +- ...WithPresence.js => AvatarWithPresence.tsx} | 33 +- app/components/Avatar/{index.js => index.ts} | 2 +- app/components/{Badge.js => Badge.ts} | 3 +- app/components/{Branding.js => Branding.tsx} | 5 +- .../{Breadcrumb.js => Breadcrumb.tsx} | 37 +- app/components/{Bubble.js => Bubble.tsx} | 9 +- app/components/{Button.js => Button.tsx} | 110 +- .../{ButtonLarge.js => ButtonLarge.ts} | 1 - .../{ButtonLink.js => ButtonLink.tsx} | 5 +- ...CenteredContent.js => CenteredContent.tsx} | 13 +- app/components/{Checkbox.js => Checkbox.tsx} | 33 +- ...ProgressBar.js => CircularProgressBar.tsx} | 18 +- ...lickablePadding.js => ClickablePadding.ts} | 3 +- .../{Collaborators.js => Collaborators.tsx} | 33 +- ...scription.js => CollectionDescription.tsx} | 44 +- .../{CollectionIcon.js => CollectionIcon.tsx} | 13 +- .../{CommandBar.js => CommandBar.tsx} | 15 +- .../{CommandBarItem.js => CommandBarItem.tsx} | 38 +- ...andBarResults.js => CommandBarResults.tsx} | 12 +- ...nnectionStatus.js => ConnectionStatus.tsx} | 9 +- ...ContentEditable.js => ContentEditable.tsx} | 52 +- .../ContextMenu/{Header.js => Header.ts} | 1 - .../ContextMenu/{MenuItem.js => MenuItem.tsx} | 30 +- ...owMenuButton.js => OverflowMenuButton.tsx} | 10 +- .../{Separator.js => Separator.tsx} | 3 +- app/components/ContextMenu/Template.js | 197 - app/components/ContextMenu/Template.tsx | 225 + .../ContextMenu/{index.js => index.tsx} | 78 +- ...{CopyToClipboard.js => CopyToClipboard.ts} | 21 +- .../{DelayedMount.js => DelayedMount.ts} | 5 +- app/components/{Dialogs.js => Dialogs.tsx} | 8 +- app/components/{Divider.js => Divider.ts} | 1 - ...ntBreadcrumb.js => DocumentBreadcrumb.tsx} | 73 +- ...DocumentHistory.js => DocumentHistory.tsx} | 50 +- .../{DocumentList.js => DocumentList.tsx} | 24 +- ...cumentListItem.js => DocumentListItem.tsx} | 72 +- .../{DocumentMeta.js => DocumentMeta.tsx} | 43 +- ...WithViews.js => DocumentMetaWithViews.tsx} | 29 +- .../{DocumentTasks.js => DocumentTasks.tsx} | 30 +- .../{DocumentViews.js => DocumentViews.tsx} | 32 +- app/components/{Editor.js => Editor.tsx} | 131 +- app/components/{Empty.js => Empty.ts} | 1 - .../{ErrorBoundary.js => ErrorBoundary.tsx} | 48 +- .../{EventBoundary.js => EventBoundary.tsx} | 8 +- .../{EventListItem.js => EventListItem.tsx} | 39 +- app/components/{Facepile.js => Facepile.tsx} | 25 +- app/components/{Fade.js => Fade.ts} | 5 +- .../{FilterOptions.js => FilterOptions.tsx} | 56 +- app/components/{Flex.js => Flex.tsx} | 33 +- ...screenLoading.js => FullscreenLoading.tsx} | 7 +- .../{GithubLogo.js => GithubLogo.tsx} | 7 +- .../{GroupListItem.js => GroupListItem.tsx} | 46 +- app/components/{Guide.js => Guide.tsx} | 22 +- app/components/{Header.js => Header.tsx} | 19 +- app/components/{Heading.js => Heading.ts} | 3 +- app/components/{HelpText.js => HelpText.ts} | 3 +- .../{Highlight.js => Highlight.tsx} | 17 +- .../{HoverPreview.js => HoverPreview.tsx} | 50 +- ...ewDocument.js => HoverPreviewDocument.tsx} | 20 +- .../{IconPicker.js => IconPicker.tsx} | 44 +- app/components/{Input.js => Input.tsx} | 94 +- .../{InputLarge.js => InputLarge.ts} | 1 - .../{InputRich.js => InputRich.tsx} | 33 +- .../{InputSearch.js => InputSearch.tsx} | 17 +- ...InputSearchPage.js => InputSearchPage.tsx} | 34 +- .../{InputSelect.js => InputSelect.tsx} | 98 +- ...ermission.js => InputSelectPermission.tsx} | 29 +- app/components/InputSelectRole.js | 25 - app/components/InputSelectRole.tsx | 39 + app/components/{Key.js => Key.ts} | 1 - app/components/{Labeled.js => Labeled.tsx} | 11 +- .../{LanguagePrompt.js => LanguagePrompt.tsx} | 24 +- app/components/{Layout.js => Layout.tsx} | 109 +- app/components/List/{Item.js => Item.tsx} | 67 +- app/components/List/{List.js => List.ts} | 1 - .../List/{Placeholder.js => Placeholder.tsx} | 9 +- app/components/List/{index.js => index.ts} | 2 +- .../LoadingIndicator/LoadingIndicator.js | 25 - .../LoadingIndicator/LoadingIndicator.ts | 16 + ...ndicatorBar.js => LoadingIndicatorBar.tsx} | 1 - .../LoadingIndicator/{index.js => index.ts} | 3 +- .../{LocaleTime.js => LocaleTime.tsx} | 36 +- ...{MenuIconWrapper.js => MenuIconWrapper.ts} | 1 - app/components/{Modal.js => Modal.tsx} | 48 +- app/components/NavLink.js | 26 - app/components/NavLink.tsx | 28 + app/components/{Notice.js => Notice.ts} | 1 - .../{NoticeAlert.js => NoticeAlert.tsx} | 17 +- app/components/{NoticeTip.js => NoticeTip.ts} | 1 - app/components/NudeButton.js | 20 - app/components/NudeButton.tsx | 20 + .../{OutlineLogo.js => OutlineLogo.tsx} | 7 +- app/components/{PageTheme.js => PageTheme.ts} | 5 +- .../{PageTitle.js => PageTitle.tsx} | 13 +- ...umentList.js => PaginatedDocumentList.tsx} | 33 +- ...tedEventList.js => PaginatedEventList.tsx} | 24 +- ...tedList.test.js => PaginatedList.test.tsx} | 45 +- .../{PaginatedList.js => PaginatedList.tsx} | 76 +- .../{PathToDocument.js => PathToDocument.tsx} | 30 +- ...derDocument.js => PlaceholderDocument.tsx} | 13 +- ...PlaceholderText.js => PlaceholderText.tsx} | 29 +- app/components/{Popover.js => Popover.tsx} | 10 +- .../{ProfiledRoute.js => ProfiledRoute.ts} | 9 +- ...{RegisterKeyDown.js => RegisterKeyDown.ts} | 7 +- app/components/{Scene.js => Scene.tsx} | 25 +- .../{ScrollToTop.js => ScrollToTop.ts} | 11 +- .../{Scrollable.js => Scrollable.tsx} | 32 +- app/components/Sidebar/{Main.js => Main.tsx} | 49 +- .../Sidebar/{Settings.js => Settings.tsx} | 16 +- .../Sidebar/{Sidebar.js => Sidebar.tsx} | 85 +- .../{ArchiveLink.js => ArchiveLink.tsx} | 23 +- .../{CollectionLink.js => CollectionLink.tsx} | 92 +- .../{Collections.js => Collections.tsx} | 30 +- .../{Disclosure.js => Disclosure.ts} | 1 - .../{DocumentLink.js => DocumentLink.tsx} | 114 +- .../{DropCursor.js => DropCursor.tsx} | 16 +- .../{DropToImport.js => DropToImport.tsx} | 44 +- .../{EditableTitle.js => EditableTitle.tsx} | 18 +- .../components/{Header.js => Header.ts} | 3 +- .../components/{NavLink.js => NavLink.tsx} | 58 +- ...lections.js => PlaceholderCollections.tsx} | 3 +- .../{ResizeBorder.js => ResizeBorder.ts} | 1 - .../components/{Section.js => Section.ts} | 3 +- .../{SidebarAction.js => SidebarAction.tsx} | 18 +- .../{SidebarLink.js => SidebarLink.tsx} | 56 +- .../components/{Starred.js => Starred.tsx} | 31 +- .../{StarredLink.js => StarredLink.tsx} | 59 +- .../{TeamButton.js => TeamButton.tsx} | 25 +- .../components/{Toggle.js => Toggle.tsx} | 14 +- .../{TrashLink.js => TrashLink.tsx} | 25 +- .../components/{Version.js => Version.tsx} | 5 +- app/components/Sidebar/{index.js => index.ts} | 2 +- .../{SkipNavContent.js => SkipNavContent.tsx} | 1 - .../{SkipNavLink.js => SkipNavLink.tsx} | 3 +- .../{SlackIcon.js => SlackIcon.tsx} | 3 +- .../{SocketProvider.js => SocketProvider.tsx} | 131 +- app/components/{Star.js => Star.tsx} | 14 +- .../{Subheading.js => Subheading.tsx} | 11 +- app/components/{Switch.js => Switch.tsx} | 30 +- app/components/{Tab.js => Tab.tsx} | 12 +- app/components/{Table.js => Table.tsx} | 80 +- app/components/{Tabs.js => Tabs.tsx} | 10 +- app/components/{TeamLogo.js => TeamLogo.ts} | 3 +- app/components/{Theme.js => Theme.tsx} | 29 +- app/components/{Time.js => Time.tsx} | 18 +- app/components/{Toast.js => Toast.tsx} | 39 +- app/components/{Toasts.js => Toasts.tsx} | 9 +- app/components/Tooltip.js | 73 - app/components/Tooltip.tsx | 69 + .../{ZapierIcon.js => ZapierIcon.tsx} | 3 +- app/components/withStores.tsx | 34 + app/env.js | 3 - app/env.ts | 11 + app/hooks/{useBoolean.js => useBoolean.ts} | 6 +- ...dBarActions.js => useCommandBarActions.ts} | 8 +- .../{useCurrentTeam.js => useCurrentTeam.ts} | 1 - ...{useCurrentToken.js => useCurrentToken.ts} | 1 - .../{useCurrentUser.js => useCurrentUser.ts} | 1 - ...cedCallback.js => useDebouncedCallback.ts} | 8 +- app/hooks/{useIdle.js => useIdle.ts} | 4 +- ...ImportDocument.js => useImportDocument.ts} | 15 +- .../{useIsMounted.js => useIsMounted.ts} | 2 - app/hooks/{useKeyDown.js => useKeyDown.ts} | 25 +- .../{useMediaQuery.js => useMediaQuery.ts} | 6 +- .../{useMenuHeight.js => useMenuHeight.ts} | 11 +- app/hooks/{useMobile.js => useMobile.ts} | 3 +- ...PageVisibility.js => usePageVisibility.ts} | 6 +- app/hooks/{usePrevious.js => usePrevious.ts} | 5 +- app/hooks/{useQuery.js => useQuery.ts} | 1 - app/hooks/{useSessions.js => useSessions.ts} | 16 +- app/hooks/useStores.js | 8 - app/hooks/useStores.ts | 7 + app/hooks/{useToasts.js => useToasts.ts} | 7 +- app/hooks/{useUnmount.js => useUnmount.ts} | 4 +- .../{useUserLocale.js => useUserLocale.ts} | 1 - ...Position.js => useWindowScrollPosition.ts} | 32 +- .../{useWindowSize.js => useWindowSize.ts} | 11 +- app/{index.js => index.tsx} | 34 +- app/menus/{AccountMenu.js => AccountMenu.tsx} | 38 +- .../{BreadcrumbMenu.js => BreadcrumbMenu.tsx} | 16 +- ...rMenu.js => CollectionGroupMemberMenu.tsx} | 22 +- .../{CollectionMenu.js => CollectionMenu.tsx} | 75 +- ...tionSortMenu.js => CollectionSortMenu.tsx} | 27 +- .../{DocumentMenu.js => DocumentMenu.tsx} | 173 +- ...OperationMenu.js => FileOperationMenu.tsx} | 21 +- ...GroupMemberMenu.js => GroupMemberMenu.tsx} | 19 +- app/menus/{GroupMenu.js => GroupMenu.tsx} | 32 +- app/menus/{MemberMenu.js => MemberMenu.tsx} | 19 +- ...cumentMenu.js => NewChildDocumentMenu.tsx} | 29 +- ...NewDocumentMenu.js => NewDocumentMenu.tsx} | 35 +- ...NewTemplateMenu.js => NewTemplateMenu.tsx} | 33 +- .../{RevisionMenu.js => RevisionMenu.tsx} | 49 +- app/menus/{ShareMenu.js => ShareMenu.tsx} | 37 +- ...ontentsMenu.js => TableOfContentsMenu.tsx} | 27 +- .../{TemplatesMenu.js => TemplatesMenu.tsx} | 31 +- app/menus/{UserMenu.js => UserMenu.tsx} | 55 +- app/menus/separator.js | 7 - app/menus/separator.ts | 7 + app/models/{ApiKey.js => ApiKey.ts} | 3 +- app/models/{BaseModel.js => BaseModel.ts} | 23 +- app/models/Collection.js | 129 - ...{Collection.test.js => Collection.test.ts} | 8 +- app/models/Collection.ts | 158 + ...ership.js => CollectionGroupMembership.ts} | 4 +- app/models/{Document.js => Document.ts} | 170 +- app/models/{Event.js => Event.ts} | 27 +- .../{FileOperation.js => FileOperation.ts} | 10 +- app/models/{Group.js => Group.ts} | 4 +- ...{GroupMembership.js => GroupMembership.ts} | 3 +- app/models/{Integration.js => Integration.ts} | 26 +- app/models/{Membership.js => Membership.ts} | 4 +- ...ationSetting.js => NotificationSetting.ts} | 2 +- app/models/{Policy.js => Policy.ts} | 6 +- app/models/{Revision.js => Revision.ts} | 6 +- app/models/{Share.js => Share.ts} | 13 +- app/models/{Team.js => Team.ts} | 15 +- app/models/{User.js => User.ts} | 13 +- app/models/{View.js => View.ts} | 6 +- ...erExtension.js => MultiplayerExtension.ts} | 3 +- .../{authenticated.js => authenticated.tsx} | 59 +- app/routes/index.js | 50 - app/routes/index.tsx | 69 + app/routes/{settings.js => settings.tsx} | 29 +- .../{APITokenNew.js => APITokenNew.tsx} | 35 +- app/scenes/{Archive.js => Archive.tsx} | 14 +- app/scenes/{Collection.js => Collection.tsx} | 132 +- ...llectionDelete.js => CollectionDelete.tsx} | 34 +- .../{CollectionEdit.js => CollectionEdit.tsx} | 54 +- ...llectionExport.js => CollectionExport.tsx} | 30 +- .../{CollectionNew.js => CollectionNew.tsx} | 84 +- ...ollection.js => AddGroupsToCollection.tsx} | 76 +- ...ollection.js => AddPeopleToCollection.tsx} | 71 +- ...m.js => CollectionGroupMemberListItem.tsx} | 40 +- .../{MemberListItem.js => MemberListItem.tsx} | 48 +- .../{UserListItem.js => UserListItem.tsx} | 19 +- .../{index.js => index.tsx} | 105 +- app/scenes/Document/{Shared.js => Shared.tsx} | 36 +- .../components/{Container.js => Container.ts} | 3 +- .../components/{Contents.js => Contents.tsx} | 28 +- .../{DataLoader.js => DataLoader.tsx} | 127 +- .../components/{Document.js => Document.tsx} | 244 +- .../{EditableTitle.js => EditableTitle.tsx} | 56 +- .../components/{Editor.js => Editor.tsx} | 65 +- .../components/{Header.js => Header.tsx} | 102 +- .../{HideSidebar.js => HideSidebar.ts} | 7 +- ...sButton.js => KeyboardShortcutsButton.tsx} | 12 +- .../components/{Loading.js => Loading.tsx} | 16 +- .../{MarkAsViewed.js => MarkAsViewed.ts} | 15 +- ...iplayerEditor.js => MultiplayerEditor.tsx} | 88 +- ...blicBreadcrumb.js => PublicBreadcrumb.tsx} | 38 +- ...blicReferences.js => PublicReferences.tsx} | 22 +- ...renceListItem.js => ReferenceListItem.tsx} | 29 +- app/scenes/Document/components/References.js | 82 - app/scenes/Document/components/References.tsx | 74 + .../{ShareButton.js => ShareButton.tsx} | 19 +- .../{SharePopover.js => SharePopover.tsx} | 80 +- .../{SocketPresence.js => SocketPresence.ts} | 24 +- app/scenes/Document/{index.js => index.tsx} | 40 +- .../{DocumentDelete.js => DocumentDelete.tsx} | 56 +- .../{DocumentMove.js => DocumentMove.tsx} | 55 +- .../{DocumentNew.js => DocumentNew.tsx} | 25 +- ...tDelete.js => DocumentPermanentDelete.tsx} | 44 +- ...cumentReparent.js => DocumentReparent.tsx} | 74 +- ...ntTemplatize.js => DocumentTemplatize.tsx} | 46 +- app/scenes/{Drafts.js => Drafts.tsx} | 80 +- app/scenes/{Error404.js => Error404.tsx} | 8 +- .../{ErrorOffline.js => ErrorOffline.tsx} | 8 +- .../{ErrorSuspended.js => ErrorSuspended.tsx} | 23 +- .../{GroupDelete.js => GroupDelete.tsx} | 37 +- app/scenes/{GroupEdit.js => GroupEdit.tsx} | 39 +- ...dPeopleToGroup.js => AddPeopleToGroup.tsx} | 74 +- .../{GroupMembers.js => GroupMembers.tsx} | 65 +- ...berListItem.js => GroupMemberListItem.tsx} | 27 +- .../{UserListItem.js => UserListItem.tsx} | 19 +- .../GroupMembers/{index.js => index.ts} | 2 +- app/scenes/{GroupNew.js => GroupNew.tsx} | 36 +- app/scenes/{Home.js => Home.tsx} | 37 +- app/scenes/{Invite.js => Invite.tsx} | 107 +- ...oardShortcuts.js => KeyboardShortcuts.tsx} | 39 +- app/scenes/Login/{Notices.js => Notices.tsx} | 5 +- .../Login/{Provider.js => Provider.tsx} | 53 +- app/scenes/Login/{index.js => index.tsx} | 54 +- app/scenes/{Logout.js => Logout.tsx} | 3 +- app/scenes/Search/{Search.js => Search.tsx} | 164 +- ...llectionFilter.js => CollectionFilter.tsx} | 16 +- app/scenes/Search/components/DateFilter.js | 35 - app/scenes/Search/components/DateFilter.tsx | 49 + .../{SearchInput.js => SearchInput.tsx} | 13 +- .../{StatusFilter.js => StatusFilter.tsx} | 14 +- .../{UserFilter.js => UserFilter.tsx} | 18 +- app/scenes/Search/{index.js => index.ts} | 2 +- .../Settings/{Details.js => Details.tsx} | 51 +- .../Settings/{Features.js => Features.tsx} | 24 +- app/scenes/Settings/{Groups.js => Groups.tsx} | 31 +- .../{ImportExport.js => ImportExport.tsx} | 118 +- .../{Notifications.js => Notifications.tsx} | 32 +- app/scenes/Settings/{People.js => People.tsx} | 54 +- .../Settings/{Profile.js => Profile.tsx} | 42 +- .../Settings/{Security.js => Security.tsx} | 42 +- app/scenes/Settings/{Shares.js => Shares.tsx} | 21 +- app/scenes/Settings/{Slack.js => Slack.tsx} | 64 +- app/scenes/Settings/{Tokens.js => Tokens.tsx} | 27 +- app/scenes/Settings/{Zapier.js => Zapier.tsx} | 11 +- ...nListItem.js => FileOperationListItem.tsx} | 24 +- .../{ImageUpload.js => ImageUpload.tsx} | 67 +- ...onListItem.js => NotificationListItem.tsx} | 17 +- .../{PeopleTable.js => PeopleTable.tsx} | 50 +- .../{ShareListItem.js => ShareListItem.tsx} | 19 +- .../{SlackButton.js => SlackButton.tsx} | 24 +- .../{TokenListItem.js => TokenListItem.tsx} | 15 +- .../Settings/components/UserListItem.js | 78 - .../Settings/components/UserListItem.tsx | 52 + ...erStatusFilter.js => UserStatusFilter.tsx} | 12 +- app/scenes/{Templates.js => Templates.tsx} | 29 +- app/scenes/{Trash.js => Trash.tsx} | 14 +- app/scenes/{UserDelete.js => UserDelete.tsx} | 31 +- .../{UserProfile.js => UserProfile.tsx} | 39 +- .../{ApiKeysStore.js => ApiKeysStore.ts} | 7 +- app/stores/{AuthStore.js => AuthStore.ts} | 137 +- app/stores/{BaseStore.js => BaseStore.ts} | 139 +- ....js => CollectionGroupMembershipsStore.ts} | 33 +- ...ollectionsStore.js => CollectionsStore.ts} | 83 +- .../{DialogsStore.js => DialogsStore.ts} | 40 +- ...senceStore.js => DocumentPresenceStore.ts} | 26 +- .../{DocumentsStore.js => DocumentsStore.ts} | 276 +- app/stores/{EventsStore.js => EventsStore.ts} | 7 +- ...rationsStore.js => FileOperationsStore.ts} | 7 +- ...shipsStore.js => GroupMembershipsStore.ts} | 26 +- app/stores/{GroupsStore.js => GroupsStore.ts} | 23 +- ...egrationsStore.js => IntegrationsStore.ts} | 14 +- ...embershipsStore.js => MembershipsStore.ts} | 31 +- app/stores/NotificationSettingsStore.js | 17 - app/stores/NotificationSettingsStore.ts | 20 + .../{PoliciesStore.js => PoliciesStore.ts} | 3 +- app/stores/RevisionsStore.js | 87 - app/stores/RevisionsStore.ts | 66 + app/stores/{RootStore.js => RootStore.ts} | 1 - app/stores/{SharesStore.js => SharesStore.ts} | 41 +- app/stores/ToastsStore.test.js | 28 - app/stores/ToastsStore.test.ts | 25 + app/stores/{ToastsStore.js => ToastsStore.ts} | 9 +- app/stores/{UiStore.js => UiStore.ts} | 88 +- app/stores/{UsersStore.js => UsersStore.ts} | 93 +- app/stores/{ViewsStore.js => ViewsStore.ts} | 7 +- app/stores/{index.js => index.ts} | 3 +- app/styles/{animations.js => animations.ts} | 1 - app/styles/{globals.js => globals.ts} | 1 - app/test/setup.js | 14 - app/test/setup.ts | 16 + app/test/{support.js => support.ts} | 1 - app/types.ts | 167 + app/types/index.js | 163 - app/typings/index.d.ts | 63 + app/typings/styled-components.d.ts | 182 + app/utils/{ApiClient.js => ApiClient.ts} | 54 +- .../__mocks__/{ApiClient.js => ApiClient.ts} | 6 +- .../{compressImage.js => compressImage.ts} | 12 +- app/utils/{dates.js => dates.ts} | 18 +- app/utils/{developer.js => developer.ts} | 3 +- app/utils/{domains.js => domains.ts} | 6 +- app/utils/{download.js => download.ts} | 74 +- app/utils/{emoji.js => emoji.ts} | 2 - app/utils/{errors.js => errors.ts} | 8 +- app/utils/getDataTransferFiles.js | 22 - app/utils/getDataTransferFiles.ts | 29 + app/utils/{history.js => history.ts} | 1 - app/utils/{i18n.js => i18n.ts} | 3 +- app/utils/{isTextInput.js => isTextInput.ts} | 4 +- app/utils/{keyboard.js => keyboard.ts} | 3 +- app/utils/{language.js => language.ts} | 7 +- app/utils/{motion.js => motion.ts} | 2 +- .../{pageVisibility.js => pageVisibility.ts} | 2 - .../{routeHelpers.js => routeHelpers.ts} | 25 +- app/utils/{sentry.js => sentry.ts} | 7 +- app/utils/{uploadFile.js => uploadFile.ts} | 31 +- app/utils/{urls.test.js => urls.test.ts} | 1 - app/utils/{urls.js => urls.ts} | 4 +- docs/ARCHITECTURE.md | 4 +- flow-typed/globals.js | 16 - flow-typed/npm/@sentry/node_vx.x.x.js | 309 - flow-typed/npm/@tippy.js/react_vx.x.x.js | 59 - .../npm/@tommoor/remove-markdown_vx.x.x.js | 38 - flow-typed/npm/autotrack_vx.x.x.js | 434 - flow-typed/npm/aws-sdk_vx.x.x.js | 2682 ---- .../boundless-arrow-key-navigation_vx.x.x.js | 58 - flow-typed/npm/bull_vx.x.x.js | 132 - flow-typed/npm/cancan_vx.x.x.js | 33 - flow-typed/npm/classnames_v2.x.x.js | 18 - flow-typed/npm/copy-to-clipboard_v3.x.x.js | 13 - flow-typed/npm/core-js_vx.x.x.js | 12113 ---------------- flow-typed/npm/debug_v2.x.x.js | 30 - flow-typed/npm/debug_vx.x.x.js | 63 - flow-typed/npm/diff_vx.x.x.js | 172 - flow-typed/npm/dotenv_v4.x.x.js | 19 - flow-typed/npm/emoji-name-map_vx.x.x.js | 32 - flow-typed/npm/emoji-regex_vx.x.x.js | 55 - flow-typed/npm/enzyme-to-json_vx.x.x.js | 74 - flow-typed/npm/enzyme_v2.3.x.js | 110 - flow-typed/npm/es6-error_v4.x.x.js | 8 - flow-typed/npm/exports-loader_vx.x.x.js | 33 - flow-typed/npm/fbemitter_vx.x.x.js | 59 - flow-typed/npm/fetch-test-server_vx.x.x.js | 38 - flow-typed/npm/file-loader_vx.x.x.js | 42 - flow-typed/npm/flow-bin_v0.x.x.js | 6 - flow-typed/npm/flow-typed_vx.x.x.js | 186 - flow-typed/npm/fs-extra_vx.x.x.js | 288 - flow-typed/npm/highlight.js_vx.x.x.js | 1124 -- flow-typed/npm/history_vx.x.x.js | 172 - flow-typed/npm/html-webpack-plugin_vx.x.x.js | 59 - flow-typed/npm/http-errors_v1.x.x.js | 60 - flow-typed/npm/imports-loader_vx.x.x.js | 33 - flow-typed/npm/invariant_v2.x.x.js | 6 - flow-typed/npm/ioredis_vx.x.x.js | 270 - flow-typed/npm/isomorphic-fetch_v2.x.x.js | 9 - flow-typed/npm/jest-cli_vx.x.x.js | 104 - flow-typed/npm/jest_v22.x.x.js | 988 -- flow-typed/npm/js-cookie_v2.x.x.js | 0 flow-typed/npm/js-search_vx.x.x.js | 215 - flow-typed/npm/js-tree_vx.x.x.js | 38 - flow-typed/npm/json-loader_vx.x.x.js | 33 - flow-typed/npm/jsonwebtoken_v8.3.x.js | 164 - flow-typed/npm/jszip_vx.x.x.js | 304 - flow-typed/npm/koa-bodyparser_v4.x.x.js | 29 - flow-typed/npm/koa-compress_vx.x.x.js | 33 - flow-typed/npm/koa-connect_vx.x.x.js | 66 - flow-typed/npm/koa-convert_vx.x.x.js | 38 - flow-typed/npm/koa-helmet_vx.x.x.js | 39 - flow-typed/npm/koa-jwt_vx.x.x.js | 77 - flow-typed/npm/koa-logger_vx.x.x.js | 33 - flow-typed/npm/koa-mount_vx.x.x.js | 33 - flow-typed/npm/koa-onerror_vx.x.x.js | 33 - flow-typed/npm/koa-router_vx.x.x.js | 39 - flow-typed/npm/koa-sslify_vx.x.x.js | 52 - flow-typed/npm/koa-static_v4.x.x.js | 34 - .../npm/koa-webpack-dev-middleware_vx.x.x.js | 35 - .../npm/koa-webpack-hot-middleware_vx.x.x.js | 33 - flow-typed/npm/koa_v2.0.x.js | 316 - flow-typed/npm/lib0_vx.x.x.js | 377 - flow-typed/npm/lib0_vx.x.x.js~4256e7ec (flow) | 377 - flow-typed/npm/lodash.orderby_vx.x.x.js | 33 - flow-typed/npm/lodash_v4.x.x.js | 6113 -------- flow-typed/npm/marked-sanitized_vx.x.x.js | 39 - flow-typed/npm/mobx-react-devtools_vx.x.x.js | 38 - flow-typed/npm/mobx-react_vx.x.x.js | 108 - flow-typed/npm/natural-sort_vx.x.x.js | 32 - flow-typed/npm/node-dev_vx.x.x.js | 88 - flow-typed/npm/node-sass_vx.x.x.js | 249 - flow-typed/npm/nodemailer_vx.x.x.js | 306 - flow-typed/npm/nodemon_vx.x.x.js | 249 - flow-typed/npm/normalizr_v2.x.x.js | 26 - flow-typed/npm/outline-icons_vx.x.x.js | 602 - flow-typed/npm/oy-vey_vx.x.x.js | 521 - flow-typed/npm/parse-domain_vx.x.x.js | 151 - flow-typed/npm/pg-hstore_vx.x.x.js | 59 - flow-typed/npm/pg_v6.x.x.js | 302 - flow-typed/npm/polished_vx.x.x.js | 1852 --- flow-typed/npm/prettier_v1.x.x.js | 228 - flow-typed/npm/pui-react-tooltip_vx.x.x.js | 32 - flow-typed/npm/query-string_vx.x.x.js | 33 - flow-typed/npm/raf_vx.x.x.js | 52 - flow-typed/npm/randomstring_v1.x.x.js | 13 - flow-typed/npm/raw-loader_vx.x.x.js | 33 - .../npm/react-addons-test-utils_v15.x.x.js | 28 - flow-typed/npm/react-avatar-editor_vx.x.x.js | 73 - flow-typed/npm/react-color_v2.x.x.js | 242 - flow-typed/npm/react-helmet_v5.x.x.js | 60 - flow-typed/npm/react-i18next_vx.x.x.js | 109 - .../npm/react-medium-image-zoom_vx.x.x.js | 94 - flow-typed/npm/react-portal_vx.x.x.js | 115 - flow-typed/npm/react-router-dom_v5.x.x.js | 181 - flow-typed/npm/react-test-renderer_v16.x.x.js | 75 - flow-typed/npm/react-waypoint_vx.x.x.js | 45 - flow-typed/npm/redis-lock_vx.x.x.js | 38 - flow-typed/npm/redis_v2.x.x.js | 354 - flow-typed/npm/rich-markdown-editor_vx.x.x.js | 1266 -- flow-typed/npm/rimraf_v2.x.x.js | 20 - flow-typed/npm/sass-loader_vx.x.x.js | 33 - flow-typed/npm/semver_vx.x.x.js | 349 - flow-typed/npm/sequelize-cli_vx.x.x.js | 796 - flow-typed/npm/sequelize-encrypted_vx.x.x.js | 41 - flow-typed/npm/sequelize_vx.x.x.js | 706 - flow-typed/npm/slate-md-serializer_vx.x.x.js | 53 - flow-typed/npm/slate_vx.x.x.js | 53 - flow-typed/npm/slug_v0.9.x.js | 26 - flow-typed/npm/slug_vx.x.x.js | 46 - flow-typed/npm/socket.io-redis_vx.x.x.js | 33 - flow-typed/npm/socket.io_vx.x.x.js | 63 - flow-typed/npm/socketio-auth_vx.x.x.js | 45 - flow-typed/npm/string-hash_vx.x.x.js | 38 - .../npm/string-replace-to-array_vx.x.x.js | 39 - .../styled-components-breakpoint_vx.x.x.js | 32 - flow-typed/npm/styled-components_vx.x.x.js | 143 - flow-typed/npm/styled-normalize_vx.x.x.js | 35 - flow-typed/npm/tiny-cookie_vx.x.x.js | 100 - flow-typed/npm/tmp_vx.x.x.js | 32 - flow-typed/npm/url-loader_vx.x.x.js | 33 - flow-typed/npm/uuid_v2.x.x.js | 17 - flow-typed/npm/validator_vx.x.x.js | 479 - flow-typed/npm/y-indexeddb_vx.x.x.js | 39 - flow-typed/npm/y-prosemirror_vx.x.x.js | 67 - flow-typed/npm/y-protocols_vx.x.x.js | 67 - flow-typed/npm/yjs_vx.x.x.js | 430 - i18next-parser.config.js | 1 - package.json | 85 +- server/.babelrc | 3 +- server/.eslintrc | 9 + server/.jestconfig.json | 6 +- server/__mocks__/events.js | 4 - server/__mocks__/events.ts | 5 + ...ailer.test.js.snap => mailer.test.ts.snap} | 0 .../{authentication.js => authentication.ts} | 24 +- server/collaboration/logger.js | 22 - server/collaboration/logger.ts | 22 + .../{persistence.js => persistence.ts} | 35 +- server/collaboration/tracing.js | 66 - server/collaboration/tracing.ts | 60 + .../{markdownToYDoc.js => markdownToYDoc.ts} | 9 +- ...ner.test.js => accountProvisioner.test.ts} | 41 +- ...ntProvisioner.js => accountProvisioner.ts} | 83 +- ...achmentCreator.js => attachmentCreator.ts} | 26 +- ...ctionExporter.js => collectionExporter.ts} | 20 +- ...ter.test.js => collectionImporter.test.ts} | 17 +- ...ctionImporter.js => collectionImporter.ts} | 66 +- ...{documentCreator.js => documentCreator.ts} | 58 +- ...orter.test.js => documentImporter.test.ts} | 33 +- ...ocumentImporter.js => documentImporter.ts} | 56 +- server/commands/documentMover.js | 180 - ...entMover.test.js => documentMover.test.ts} | 39 +- server/commands/documentMover.ts | 222 + ...st.js => documentPermanentDeleter.test.ts} | 62 +- ...Deleter.js => documentPermanentDeleter.ts} | 15 +- ...{documentUpdater.js => documentUpdater.ts} | 12 +- server/commands/fileOperationDeleter.js | 31 - ...r.test.js => fileOperationDeleter.test.ts} | 16 +- server/commands/fileOperationDeleter.ts | 34 + ...reator.test.js => revisionCreator.test.ts} | 16 +- ...{revisionCreator.js => revisionCreator.ts} | 21 +- ...eamCreator.test.js => teamCreator.test.ts} | 23 +- .../{teamCreator.js => teamCreator.ts} | 47 +- ...r.test.js => teamPermanentDeleter.test.ts} | 60 +- ...nentDeleter.js => teamPermanentDeleter.ts} | 102 +- ...serCreator.test.js => userCreator.test.ts} | 40 +- .../{userCreator.js => userCreator.ts} | 76 +- ...estroyer.test.js => userDestroyer.test.ts} | 26 +- .../{userDestroyer.js => userDestroyer.ts} | 35 +- server/commands/userInviter.test.js | 63 - server/commands/userInviter.test.ts | 93 + .../{userInviter.js => userInviter.ts} | 38 +- ...uspender.test.js => userSuspender.test.ts} | 28 +- .../{userSuspender.js => userSuspender.ts} | 33 +- ...ail.js => CollectionNotificationEmail.tsx} | 13 +- ...Email.js => DocumentNotificationEmail.tsx} | 18 +- ...FailureEmail.js => ExportFailureEmail.tsx} | 1 - ...SuccessEmail.js => ExportSuccessEmail.tsx} | 5 +- .../{InviteEmail.js => InviteEmail.tsx} | 11 +- .../{SigninEmail.js => SigninEmail.tsx} | 5 +- .../{WelcomeEmail.js => WelcomeEmail.tsx} | 3 +- .../emails/components/{Body.js => Body.tsx} | 4 +- .../components/{Button.js => Button.tsx} | 7 +- .../{EmailLayout.js => EmailLayout.tsx} | 5 +- .../{EmptySpace.js => EmptySpace.tsx} | 6 +- .../components/{Footer.js => Footer.tsx} | 11 +- .../components/{Header.js => Header.tsx} | 1 - .../components/{Heading.js => Heading.tsx} | 4 +- server/emails/{index.js => index.ts} | 13 +- server/env.js | 4 - server/env.ts | 6 + server/errors.js | 117 - server/errors.ts | 146 + server/{index.js => index.ts} | 21 +- server/logging/{logger.js => logger.ts} | 23 +- server/logging/{metrics.js => metrics.ts} | 10 +- server/logging/{sentry.js => sentry.ts} | 18 +- server/{mailer.test.js => mailer.test.ts} | 8 +- server/{mailer.js => mailer.tsx} | 66 +- .../{apexRedirect.js => apexRedirect.ts} | 5 +- ...ication.test.js => authentication.test.ts} | 47 +- .../{authentication.js => authentication.ts} | 44 +- .../{errorHandling.js => errorHandling.ts} | 8 +- .../{methodOverride.js => methodOverride.ts} | 8 +- .../middlewares/{passport.js => passport.ts} | 20 +- server/middlewares/validation.js | 78 - server/migrations/20160619080644-initial.js | 98 +- .../20160622043741-add-parent-document.js | 5 +- .../migrations/20160626063409-add-indexes.js | 38 +- .../20160626175224-add-revisions.js | 46 +- .../migrations/20160711071958-search-index.js | 3 - .../20160726061511-atlas-creator.js | 5 +- ...160812145029-document-atlas-soft-delete.js | 10 +- .../20160814083127-paranoia-indeces.js | 78 +- ...20160814095336-add-document-createdById.js | 9 +- ...0814111419-add-document-collaboratorIds.js | 4 +- .../20160815142720-app-collection-urlId.js | 5 +- .../20160816082738-add-revision-index.js | 5 +- .../migrations/20160824061730-add-apikeys.js | 21 +- .../20160824062457-add-apikey-indeces.js | 9 +- .../20160911230444-user-optional-slack-id.js | 9 +- .../20160911232911-user-unique-fields.js | 10 +- .../20160911234928-user-password.js | 5 +- ...-collection-documentStructure-migration.js | 9 +- server/migrations/20170604052346-add-views.js | 68 +- server/migrations/20170604052347-add-stars.js | 56 +- .../20170712055148-non-unique-email.js | 9 +- .../20170712072234-uniq-slack-id.js | 5 +- server/migrations/20170729215619-emoji.js | 5 +- .../20170827182423-improve-references.js | 31 +- .../20170904202454-allow-null-username.js | 5 +- server/migrations/20171010042938-add-event.js | 11 +- ...012353-remove-collection-navigationtree.js | 5 +- .../20171017055026-remove-document-html.js | 17 +- .../20171019071915-user-avatar-url.js | 5 +- .../20171023064220-collection-color.js | 5 +- .../20171218043717-add-authentications.js | 9 +- .../migrations/20171225143838-set-admins.js | 16 +- .../migrations/20180115021837-add-drafts.js | 30 +- .../20180212033504-add-integrations.js | 13 +- .../20180225203847-document-pinning.js | 9 +- .../20180303193036-suspended-users.js | 15 +- .../20180324214403-serializer-upgrade.js | 23 +- .../20180513041057-add-share-links.js | 11 +- .../migrations/20180528233909-google-auth.js | 28 +- .../20180528233910-rename-serviceid.js | 20 +- .../20180604182823-user-tracking.js | 28 +- .../20180604191743-revoke-share-links.js | 16 +- .../20180707220121-more-soft-delete.js | 16 +- .../20180707231201-remove-passwords.js | 8 +- .../20180708231200-serviceid-null.js | 8 +- server/migrations/20180808061353-cleanup.js | 16 +- .../20180819054252-disable-sharing.js | 10 +- .../20181031015046-add-subdomain-to-team.js | 14 +- .../20181124000438-add-notifications.js | 43 +- .../20181215192422-document-embeds.js | 10 +- .../20181227001547-collection-permissions.js | 33 +- .../migrations/20190404035736-add-archive.js | 10 +- .../20190423051708-add-search-indexes.js | 13 +- server/migrations/20190606035733-events.js | 37 +- .../migrations/20190704070630-welcome-docs.js | 10 +- server/migrations/20190706213213-backlinks.js | 15 +- .../migrations/20190811231511-maintainers.js | 32 +- .../20191118023010-cascade-delete.js | 28 +- .../20191119023010-cascade-backlinks.js | 31 +- ...20191119023011-cascade-parent-documents.js | 31 +- .../20191119023012-cascade-shares.js | 28 +- .../20191119023013-cascade-backlinks2.js | 28 +- .../migrations/20191121035144-guest-invite.js | 18 +- .../20191211044318-create-groups.js | 24 +- .../20191211044319-create-group-users.js | 24 +- .../20191228031525-edit-presence.js | 9 +- .../migrations/20200104233831-attachments.js | 13 +- ...20200122083721-create-collection-groups.js | 33 +- .../20200316040755-document-editor-version.js | 15 +- .../20200328175012-cascade-delete.js | 31 +- .../20200330053639-document-version.js | 15 +- .../migrations/20200519032353-text-backup.js | 15 +- .../20200522054958-collection-icon.js | 11 +- .../20200723055414-add-published-to-shares.js | 14 +- .../20200727051157-add-templates.js | 19 +- .../20200812170227-remove-collection-type.js | 13 +- .../20200915010511-create-search-queries.js | 1 + .../20200926204620-add-missing-indexes.js | 7 +- ...0201028043021-reverse-document-id-index.js | 5 +- .../20201103050534-custom-domains.js | 13 +- server/migrations/20201106122752-i18n.js | 9 +- .../20201206210619-update-attachment-cols.js | 41 +- .../20201211080408-attachment-no-cascade.js | 43 +- .../20201230031607-collection-sort.js | 13 +- ...0110143902-collection-rename-creator-id.js | 1 - ...210208062816-disable-collection-sharing.js | 10 +- .../20210218111237-add-collection-index.js | 1 - ...20210226232041-authentication-providers.js | 25 +- server/migrations/20210310051804-passport.js | 9 +- server/migrations/20210314173941-isViewer.js | 7 +- .../20210327005406-read-only-collections.js | 11 +- .../20210418053152-share-last-viewed.js | 5 +- .../20210426055334-nested-document-sharing.js | 5 +- .../20210430024222-marketing-tracking.js | 7 +- ...20210716064654-introduce-previousTitles.js | 11 +- ...10716071454-search-index-previousTitles.js | 6 +- .../20210716162923-events-indexes.js | 5 +- .../20210730042450-remove-unused-indexes.js | 8 +- .../20210730044247-remove-backup-column.js | 15 +- .../20210730044248-create-realtime.js | 1 - .../20210730210120-add-fileOperations.js | 39 +- ...0210915051740-collaborative-collections.js | 1 - .../20210921031555-missing-cascades.js | 100 +- .../20210923031555-missing-cascades.js | 52 +- .../20211003021903-missing-cascades.js | 30 +- .../20211015170955-add-defaultUserRole.js | 5 +- .../20211107021900-missing-cascades.js | 29 +- server/models/{ApiKey.js => ApiKey.ts} | 8 +- .../models/{Attachment.js => Attachment.ts} | 13 +- ...nProvider.js => AuthenticationProvider.ts} | 16 +- server/models/{Backlink.js => Backlink.ts} | 2 +- ...{Collection.test.js => Collection.test.ts} | 162 +- .../models/{Collection.js => Collection.ts} | 134 +- ...{CollectionGroup.js => CollectionGroup.ts} | 2 +- .../{CollectionUser.js => CollectionUser.ts} | 2 +- .../{Document.test.js => Document.test.ts} | 123 +- server/models/{Document.js => Document.ts} | 261 +- server/models/{Event.js => Event.ts} | 8 +- .../{FileOperation.js => FileOperation.ts} | 6 +- .../models/{Group.test.js => Group.test.ts} | 50 +- server/models/{Group.js => Group.ts} | 27 +- server/models/{GroupUser.js => GroupUser.ts} | 8 +- .../models/{Integration.js => Integration.ts} | 2 +- ...cation.js => IntegrationAuthentication.ts} | 2 +- .../{Notification.js => Notification.ts} | 2 +- ...ationSetting.js => NotificationSetting.ts} | 3 +- .../{Revision.test.js => Revision.test.ts} | 12 +- server/models/{Revision.js => Revision.ts} | 28 +- .../models/{SearchQuery.js => SearchQuery.ts} | 5 +- server/models/{Share.js => Share.ts} | 26 +- server/models/{Star.js => Star.ts} | 2 +- server/models/{Team.test.js => Team.test.ts} | 36 +- server/models/{Team.js => Team.ts} | 84 +- server/models/{User.test.js => User.test.ts} | 35 +- server/models/{User.js => User.ts} | 169 +- ...uthentication.js => UserAuthentication.ts} | 2 +- server/models/{View.js => View.ts} | 19 +- server/models/{index.js => index.ts} | 1 - server/policies/{apiKey.js => apiKey.ts} | 3 +- .../policies/{attachment.js => attachment.ts} | 3 +- ...nProvider.js => authenticationProvider.ts} | 11 +- ...{collection.test.js => collection.test.ts} | 43 +- .../policies/{collection.js => collection.ts} | 18 +- .../{document.test.js => document.test.ts} | 20 +- server/policies/{document.js => document.ts} | 21 +- server/policies/{group.js => group.ts} | 9 +- .../policies/{index.test.js => index.test.ts} | 11 +- server/policies/{index.js => index.ts} | 23 +- .../{integration.js => integration.ts} | 9 +- ...ationSetting.js => notificationSetting.ts} | 3 +- server/policies/{policy.js => policy.ts} | 2 +- server/policies/{share.js => share.ts} | 6 +- .../policies/{team.test.js => team.test.ts} | 17 +- server/policies/{team.js => team.ts} | 3 +- server/policies/{user.js => user.ts} | 23 +- .../{user.test.js.snap => user.test.ts.snap} | 0 server/presenters/apiKey.js | 11 - server/presenters/apiKey.ts | 11 + ...nProvider.js => authenticationProvider.ts} | 4 +- .../{collection.js => collection.ts} | 21 +- ...ership.js => collectionGroupMembership.ts} | 12 +- server/presenters/document.js | 81 - server/presenters/document.ts | 106 + server/presenters/{env.js => env.ts} | 4 +- server/presenters/event.js | 24 - server/presenters/event.ts | 33 + .../{fileOperation.js => fileOperation.ts} | 4 +- server/presenters/{group.js => group.ts} | 4 +- server/presenters/groupMembership.js | 18 - server/presenters/groupMembership.ts | 19 + server/presenters/{index.js => index.ts} | 1 - .../{integration.js => integration.ts} | 4 +- .../{membership.js => membership.ts} | 12 +- server/presenters/notificationSetting.js | 9 - server/presenters/notificationSetting.ts | 9 + server/presenters/policy.js | 13 - server/presenters/policy.ts | 20 + .../presenters/{revision.js => revision.ts} | 5 +- server/presenters/{share.js => share.ts} | 8 +- server/presenters/slackAttachment.js | 34 - server/presenters/slackAttachment.ts | 39 + server/presenters/{team.js => team.ts} | 4 +- server/presenters/user.js | 39 - .../presenters/{user.test.js => user.test.ts} | 4 - server/presenters/user.ts | 53 + server/presenters/{view.js => view.ts} | 4 +- server/queues/{index.js => index.ts} | 6 +- .../{backlinks.test.js => backlinks.test.ts} | 84 +- .../processors/{backlinks.js => backlinks.ts} | 41 +- .../processors/{debouncer.js => debouncer.ts} | 17 +- server/queues/processors/emails.js | 14 - server/queues/processors/emails.ts | 12 + .../processors/{exports.js => exports.ts} | 38 +- .../processors/{imports.js => imports.ts} | 14 +- ...ications.test.js => notifications.test.ts} | 46 +- .../{notifications.js => notifications.ts} | 20 +- .../{revisions.test.js => revisions.test.ts} | 38 +- .../processors/{revisions.js => revisions.ts} | 16 +- .../queues/processors/{slack.js => slack.ts} | 17 +- .../{websockets.js => websockets.ts} | 63 +- server/{redis.js => redis.ts} | 7 +- ....test.js.snap => collections.test.ts.snap} | 0 ...ts.test.js.snap => documents.test.ts.snap} | 0 ...vents.test.js.snap => events.test.ts.snap} | 0 ...roups.test.js.snap => groups.test.ts.snap} | 0 ...hares.test.js.snap => shares.test.ts.snap} | 0 ...{users.test.js.snap => users.test.ts.snap} | 0 ...{views.test.js.snap => views.test.ts.snap} | 0 server/routes/api/{apiKeys.js => apiKeys.ts} | 32 +- ...ttachments.test.js => attachments.test.ts} | 90 +- .../api/{attachments.js => attachments.ts} | 50 +- .../routes/api/{auth.test.js => auth.test.ts} | 62 +- server/routes/api/{auth.js => auth.ts} | 33 +- ...est.js => authenticationProviders.test.ts} | 46 +- ...roviders.js => authenticationProviders.ts} | 34 +- ...ollections.test.js => collections.test.ts} | 526 +- .../api/{collections.js => collections.ts} | 233 +- .../{documents.test.js => documents.test.ts} | 831 +- .../routes/api/{documents.js => documents.ts} | 712 +- .../api/{events.test.js => events.test.ts} | 64 +- server/routes/api/{events.js => events.ts} | 38 +- ...rations.test.js => fileOperations.test.ts} | 116 +- .../{fileOperations.js => fileOperations.ts} | 54 +- .../api/{groups.test.js => groups.test.ts} | 428 +- server/routes/api/{groups.js => groups.ts} | 97 +- .../api/{hooks.test.js => hooks.test.ts} | 66 +- server/routes/api/{hooks.js => hooks.ts} | 61 +- .../api/{index.test.js => index.test.ts} | 10 +- server/routes/api/{index.js => index.ts} | 17 +- .../api/{integrations.js => integrations.ts} | 27 +- .../{apiWrapper.js => apiWrapper.ts} | 9 +- .../api/middlewares/{editor.js => editor.ts} | 14 +- ...{pagination.test.js => pagination.test.ts} | 40 +- .../{pagination.js => pagination.ts} | 36 +- ...ionSettings.js => notificationSettings.ts} | 24 +- .../{revisions.test.js => revisions.test.ts} | 26 +- .../routes/api/{revisions.js => revisions.ts} | 39 +- .../api/{shares.test.js => shares.test.ts} | 264 +- server/routes/api/{shares.js => shares.ts} | 78 +- .../routes/api/{team.test.js => team.test.ts} | 38 +- server/routes/api/{team.js => team.ts} | 13 +- .../api/{users.test.js => users.test.ts} | 261 +- server/routes/api/{users.js => users.ts} | 171 +- .../api/{utils.test.js => utils.test.ts} | 59 +- server/routes/api/{utils.js => utils.ts} | 22 +- .../api/{views.test.js => views.test.ts} | 73 +- server/routes/api/{views.js => views.ts} | 36 +- .../auth/{index.test.js => index.test.ts} | 24 +- server/routes/auth/{index.js => index.ts} | 23 +- .../auth/providers/{azure.js => azure.ts} | 34 +- .../{email.test.js => email.test.ts} | 132 +- .../auth/providers/{email.js => email.ts} | 58 +- .../auth/providers/{google.js => google.ts} | 22 +- .../auth/providers/{index.js => index.ts} | 10 +- .../auth/providers/{oidc.js => oidc.ts} | 47 +- server/routes/auth/providers/slack.js | 203 - server/routes/auth/providers/slack.ts | 211 + .../routes/{index.test.js => index.test.ts} | 34 +- server/routes/{index.js => index.ts} | 34 +- ...0226232041-migrate-authentication.test.ts} | 20 +- ... 20210226232041-migrate-authentication.ts} | 18 +- ...20210716000000-backfill-revisions.test.ts} | 17 +- ...s => 20210716000000-backfill-revisions.ts} | 10 +- server/scripts/bootstrap.js | 6 - server/scripts/bootstrap.ts | 11 + server/{sequelize.js => sequelize.ts} | 5 +- server/services/{admin.js => admin.ts} | 5 +- .../{collaboration.js => collaboration.ts} | 15 +- server/services/{index.js => index.ts} | 9 +- server/services/{web.js => web.ts} | 39 +- .../services/{websockets.js => websockets.ts} | 50 +- server/services/{worker.js => worker.ts} | 15 +- server/test/{factories.js => factories.ts} | 73 +- server/test/{setup.js => setup.ts} | 2 - server/test/{support.js => support.ts} | 9 +- server/{tracing.js => tracing.ts} | 5 +- server/types.js | 241 - server/types.ts | 253 + server/typings/cancan.d.ts | 35 + server/typings/fetch-with-proxy.d.ts | 3 + server/typings/koa-onerror.d.ts | 1 + server/utils/__mocks__/{s3.js => s3.ts} | 2 - server/utils/{args.js => args.ts} | 2 - .../{authentication.js => authentication.ts} | 29 +- .../{avatars.test.js => avatars.test.ts} | 5 - server/utils/{avatars.js => avatars.ts} | 10 +- ...ctionIndexing.js => collectionIndexing.ts} | 19 +- server/utils/{color.js => color.ts} | 3 +- ...Buffer.test.js => dataURItoBuffer.test.ts} | 4 +- ...{dataURItoBuffer.js => dataURItoBuffer.ts} | 4 - server/utils/{domains.js => domains.ts} | 4 +- server/utils/{fs.test.js => fs.test.ts} | 2 - server/utils/{fs.js => fs.ts} | 7 +- server/utils/jwt.js | 99 - server/utils/jwt.ts | 114 + server/utils/{opensearch.js => opensearch.ts} | 1 - ...Ids.test.js => parseAttachmentIds.test.ts} | 22 +- ...AttachmentIds.js => parseAttachmentIds.ts} | 1 - ...ntIds.test.js => parseDocumentIds.test.ts} | 8 - ...arseDocumentIds.js => parseDocumentIds.ts} | 9 +- ...arseImages.test.js => parseImages.test.ts} | 5 - .../utils/{parseImages.js => parseImages.ts} | 5 +- server/utils/{passport.js => passport.ts} | 25 +- .../{prefetchTags.js => prefetchTags.tsx} | 6 +- server/utils/{queue.js => queue.ts} | 14 +- ...exCollision.js => removeIndexCollision.ts} | 13 +- server/utils/{robots.js => robots.ts} | 7 +- server/utils/{s3.js => s3.ts} | 49 +- server/utils/{slack.js => slack.ts} | 22 +- server/utils/{slugify.js => slugify.ts} | 1 - server/utils/{startup.js => startup.ts} | 17 +- server/utils/{updates.js => updates.ts} | 9 +- server/utils/{zip.js => zip.ts} | 48 +- server/validation.ts | 91 + shared/{constants.js => constants.ts} | 4 +- .../{Abstract.test.js => Abstract.test.ts} | 4 +- shared/embeds/{Abstract.js => Abstract.tsx} | 15 +- .../{Airtable.test.js => Airtable.test.ts} | 2 +- shared/embeds/{Airtable.js => Airtable.tsx} | 16 +- .../{Bilibili.test.js => Bilibili.test.ts} | 2 +- shared/embeds/{Bilibili.js => Bilibili.tsx} | 16 +- .../embeds/{Cawemo.test.js => Cawemo.test.ts} | 2 +- shared/embeds/{Cawemo.js => Cawemo.tsx} | 16 +- .../{ClickUp.test.js => ClickUp.test.ts} | 2 +- shared/embeds/{ClickUp.js => ClickUp.tsx} | 15 +- .../{Codepen.test.js => Codepen.test.ts} | 2 +- shared/embeds/{Codepen.js => Codepen.tsx} | 16 +- shared/embeds/{Descript.js => Descript.tsx} | 15 +- .../{Diagrams.test.js => Diagrams.test.ts} | 1 - shared/embeds/{Diagrams.js => Diagrams.tsx} | 15 +- .../embeds/{Figma.test.js => Figma.test.ts} | 2 +- shared/embeds/{Figma.js => Figma.tsx} | 15 +- .../embeds/{Framer.test.js => Framer.test.ts} | 2 +- shared/embeds/{Framer.js => Framer.tsx} | 15 +- shared/embeds/{Gist.test.js => Gist.test.ts} | 4 +- shared/embeds/{Gist.js => Gist.tsx} | 24 +- ...alendar.test.js => GoogleCalendar.test.ts} | 2 +- .../{GoogleCalendar.js => GoogleCalendar.tsx} | 15 +- ...tudio.test.js => GoogleDataStudio.test.ts} | 2 +- ...ogleDataStudio.js => GoogleDataStudio.tsx} | 15 +- ...{GoogleDocs.test.js => GoogleDocs.test.ts} | 2 +- .../embeds/{GoogleDocs.js => GoogleDocs.tsx} | 15 +- ...rawings.test.js => GoogleDrawings.test.ts} | 2 +- .../{GoogleDrawings.js => GoogleDrawings.tsx} | 15 +- ...oogleDrive.test.js => GoogleDrive.test.ts} | 1 - .../{GoogleDrive.js => GoogleDrive.tsx} | 15 +- ...gleSheets.test.js => GoogleSheets.test.ts} | 2 +- .../{GoogleSheets.js => GoogleSheets.tsx} | 15 +- ...gleSlides.test.js => GoogleSlides.test.ts} | 2 +- .../{GoogleSlides.js => GoogleSlides.tsx} | 15 +- .../{InVision.test.js => InVision.test.ts} | 2 +- shared/embeds/{InVision.js => InVision.tsx} | 20 +- shared/embeds/{Loom.test.js => Loom.test.ts} | 2 +- shared/embeds/{Loom.js => Loom.tsx} | 16 +- ...{Lucidchart.test.js => Lucidchart.test.ts} | 6 +- .../embeds/{Lucidchart.js => Lucidchart.tsx} | 16 +- .../embeds/{Marvel.test.js => Marvel.test.ts} | 2 +- shared/embeds/{Marvel.js => Marvel.tsx} | 15 +- ...indmeister.test.js => Mindmeister.test.ts} | 1 - .../{Mindmeister.js => Mindmeister.tsx} | 16 +- shared/embeds/{Miro.test.js => Miro.test.ts} | 3 +- shared/embeds/{Miro.js => Miro.tsx} | 16 +- ...nalytics.test.js => ModeAnalytics.test.ts} | 2 +- .../{ModeAnalytics.js => ModeAnalytics.tsx} | 16 +- shared/embeds/{Pitch.js => Pitch.tsx} | 16 +- .../embeds/{Prezi.test.js => Prezi.test.ts} | 2 +- shared/embeds/{Prezi.js => Prezi.tsx} | 16 +- .../{Spotify.test.js => Spotify.test.ts} | 2 +- shared/embeds/{Spotify.js => Spotify.tsx} | 17 +- shared/embeds/{Trello.js => Trello.tsx} | 16 +- .../{Typeform.test.js => Typeform.test.ts} | 2 +- shared/embeds/{Typeform.js => Typeform.tsx} | 15 +- .../embeds/{Vimeo.test.js => Vimeo.test.ts} | 2 +- shared/embeds/{Vimeo.js => Vimeo.tsx} | 16 +- .../{YouTube.test.js => YouTube.test.ts} | 2 +- shared/embeds/{YouTube.js => YouTube.tsx} | 18 +- .../embeds/components/{Frame.js => Frame.tsx} | 37 +- .../embeds/components/{Image.js => Image.tsx} | 15 +- shared/embeds/{index.js => index.tsx} | 37 +- shared/i18n/{index.test.js => index.test.ts} | 4 - shared/i18n/{index.js => index.ts} | 77 +- shared/{random.js => random.ts} | 1 - shared/{theme.js => theme.ts} | 62 +- shared/types.js | 2 - shared/types.ts | 20 + shared/utils/{color.js => color.ts} | 2 - shared/utils/{date.js => date.ts} | 11 +- .../{domains.test.js => domains.test.ts} | 11 +- shared/utils/{domains.js => domains.ts} | 14 +- shared/utils/{getTasks.js => getTasks.ts} | 10 +- ...{indexCharacters.js => indexCharacters.ts} | 2 - shared/utils/naturalSort.test.js | 61 - shared/utils/naturalSort.test.ts | 140 + .../utils/{naturalSort.js => naturalSort.ts} | 20 +- ...Slug.test.js => parseDocumentSlug.test.ts} | 1 - ...seDocumentSlug.js => parseDocumentSlug.ts} | 3 +- ...{parseTitle.test.js => parseTitle.test.ts} | 5 - shared/utils/{parseTitle.js => parseTitle.ts} | 8 +- .../{routeHelpers.js => routeHelpers.ts} | 9 +- shared/utils/slugify.js | 8 - shared/utils/slugify.ts | 10 + shared/utils/{unescape.js => unescape.ts} | 2 - shared/utils/{urls.js => urls.ts} | 1 - shared/utils/{zip.js => zip.ts} | 30 +- tsconfig.json | 35 + webpack.config.js | 7 +- yarn.lock | 3282 +++-- 1017 files changed, 17410 insertions(+), 54942 deletions(-) delete mode 100644 .flowconfig create mode 100644 app/.eslintrc rename app/actions/definitions/{collections.js => collections.tsx} (83%) rename app/actions/definitions/{debug.js => debug.tsx} (79%) rename app/actions/definitions/{documents.js => documents.tsx} (88%) rename app/actions/definitions/{navigation.js => navigation.tsx} (92%) rename app/actions/definitions/{settings.js => settings.tsx} (79%) rename app/actions/definitions/{users.js => users.tsx} (77%) rename app/actions/{index.js => index.ts} (76%) rename app/actions/{root.js => root.ts} (98%) rename app/actions/{sections.js => sections.ts} (89%) rename app/components/{Actions.js => Actions.ts} (95%) rename app/components/{Analytics.js => Analytics.ts} (78%) rename app/components/{Arrow.js => Arrow.tsx} (98%) rename app/components/AuthLogo/{GoogleLogo.js => GoogleLogo.tsx} (93%) rename app/components/AuthLogo/{MicrosoftLogo.js => MicrosoftLogo.tsx} (94%) rename app/components/AuthLogo/{SlackLogo.js => SlackLogo.tsx} (98%) rename app/components/AuthLogo/{index.js => index.tsx} (91%) rename app/components/{Authenticated.js => Authenticated.tsx} (84%) rename app/components/Avatar/{Avatar.js => Avatar.tsx} (82%) rename app/components/Avatar/{AvatarWithPresence.js => AvatarWithPresence.tsx} (75%) rename app/components/Avatar/{index.js => index.ts} (94%) rename app/components/{Badge.js => Badge.ts} (89%) rename app/components/{Branding.js => Branding.tsx} (93%) rename app/components/{Breadcrumb.js => Breadcrumb.tsx} (73%) rename app/components/{Bubble.js => Bubble.tsx} (88%) rename app/components/{Button.js => Button.tsx} (64%) rename app/components/{ButtonLarge.js => ButtonLarge.ts} (95%) rename app/components/{ButtonLink.js => ButtonLink.tsx} (81%) rename app/components/{CenteredContent.js => CenteredContent.tsx} (73%) rename app/components/{Checkbox.js => Checkbox.tsx} (68%) rename app/components/{CircularProgressBar.js => CircularProgressBar.tsx} (87%) rename app/components/{ClickablePadding.js => ClickablePadding.ts} (78%) rename app/components/{Collaborators.js => Collaborators.tsx} (82%) rename app/components/{CollectionDescription.js => CollectionDescription.tsx} (82%) rename app/components/{CollectionIcon.js => CollectionIcon.tsx} (83%) rename app/components/{CommandBar.js => CommandBar.tsx} (82%) rename app/components/{CommandBarItem.js => CommandBarItem.tsx} (63%) rename app/components/{CommandBarResults.js => CommandBarResults.tsx} (71%) rename app/components/{ConnectionStatus.js => ConnectionStatus.tsx} (87%) rename app/components/{ContentEditable.js => ContentEditable.tsx} (66%) rename app/components/ContextMenu/{Header.js => Header.ts} (96%) rename app/components/ContextMenu/{MenuItem.js => MenuItem.tsx} (87%) rename app/components/ContextMenu/{OverflowMenuButton.js => OverflowMenuButton.tsx} (70%) rename app/components/ContextMenu/{Separator.js => Separator.tsx} (84%) delete mode 100644 app/components/ContextMenu/Template.js create mode 100644 app/components/ContextMenu/Template.tsx rename app/components/ContextMenu/{index.js => index.tsx} (64%) rename app/components/{CopyToClipboard.js => CopyToClipboard.ts} (73%) rename app/components/{DelayedMount.js => DelayedMount.ts} (88%) rename app/components/{Dialogs.js => Dialogs.tsx} (84%) rename app/components/{Divider.js => Divider.ts} (95%) rename app/components/{DocumentBreadcrumb.js => DocumentBreadcrumb.tsx} (62%) rename app/components/{DocumentHistory.js => DocumentHistory.tsx} (72%) rename app/components/{DocumentList.js => DocumentList.tsx} (63%) rename app/components/{DocumentListItem.js => DocumentListItem.tsx} (81%) rename app/components/{DocumentMeta.js => DocumentMeta.tsx} (83%) rename app/components/{DocumentMetaWithViews.js => DocumentMetaWithViews.tsx} (80%) rename app/components/{DocumentTasks.js => DocumentTasks.tsx} (66%) rename app/components/{DocumentViews.js => DocumentViews.tsx} (78%) rename app/components/{Editor.js => Editor.tsx} (69%) rename app/components/{Empty.js => Empty.ts} (93%) rename app/components/{ErrorBoundary.js => ErrorBoundary.tsx} (76%) rename app/components/{EventBoundary.js => EventBoundary.tsx} (69%) rename app/components/{EventListItem.js => EventListItem.tsx} (86%) rename app/components/{Facepile.js => Facepile.tsx} (78%) rename app/components/{Fade.js => Fade.ts} (57%) rename app/components/{FilterOptions.js => FilterOptions.tsx} (66%) rename app/components/{Flex.js => Flex.tsx} (56%) rename app/components/{FullscreenLoading.js => FullscreenLoading.tsx} (76%) rename app/components/{GithubLogo.js => GithubLogo.tsx} (94%) rename app/components/{GroupListItem.js => GroupListItem.tsx} (66%) rename app/components/{Guide.js => Guide.tsx} (88%) rename app/components/{Header.js => Header.tsx} (90%) rename app/components/{Heading.js => Heading.ts} (85%) rename app/components/{HelpText.js => HelpText.ts} (80%) rename app/components/{Highlight.js => Highlight.tsx} (75%) rename app/components/{HoverPreview.js => HoverPreview.tsx} (84%) rename app/components/{HoverPreviewDocument.js => HoverPreviewDocument.tsx} (67%) rename app/components/{IconPicker.js => IconPicker.tsx} (90%) rename app/components/{Input.js => Input.tsx} (67%) rename app/components/{InputLarge.js => InputLarge.ts} (96%) rename app/components/{InputRich.js => InputRich.tsx} (72%) rename app/components/{InputSearch.js => InputSearch.tsx} (77%) rename app/components/{InputSearchPage.js => InputSearchPage.tsx} (74%) rename app/components/{InputSelect.js => InputSelect.tsx} (78%) rename app/components/{InputSelectPermission.js => InputSelectPermission.tsx} (57%) delete mode 100644 app/components/InputSelectRole.js create mode 100644 app/components/InputSelectRole.tsx rename app/components/{Key.js => Key.ts} (98%) rename app/components/{Labeled.js => Labeled.tsx} (78%) rename app/components/{LanguagePrompt.js => LanguagePrompt.tsx} (85%) rename app/components/{Layout.js => Layout.tsx} (66%) rename app/components/List/{Item.js => Item.tsx} (65%) rename app/components/List/{List.js => List.ts} (93%) rename app/components/List/{Placeholder.js => Placeholder.tsx} (76%) rename app/components/List/{index.js => index.ts} (84%) delete mode 100644 app/components/LoadingIndicator/LoadingIndicator.js create mode 100644 app/components/LoadingIndicator/LoadingIndicator.ts rename app/components/LoadingIndicator/{LoadingIndicatorBar.js => LoadingIndicatorBar.tsx} (98%) rename app/components/LoadingIndicator/{index.js => index.ts} (95%) rename app/components/{LocaleTime.js => LocaleTime.tsx} (76%) rename app/components/{MenuIconWrapper.js => MenuIconWrapper.ts} (95%) rename app/components/{Modal.js => Modal.tsx} (83%) delete mode 100644 app/components/NavLink.js create mode 100644 app/components/NavLink.tsx rename app/components/{Notice.js => Notice.ts} (96%) rename app/components/{NoticeAlert.js => NoticeAlert.tsx} (78%) rename app/components/{NoticeTip.js => NoticeTip.ts} (97%) delete mode 100644 app/components/NudeButton.js create mode 100644 app/components/NudeButton.tsx rename app/components/{OutlineLogo.js => OutlineLogo.tsx} (96%) rename app/components/{PageTheme.js => PageTheme.ts} (94%) rename app/components/{PageTitle.js => PageTitle.tsx} (82%) rename app/components/{PaginatedDocumentList.js => PaginatedDocumentList.tsx} (50%) rename app/components/{PaginatedEventList.js => PaginatedEventList.tsx} (69%) rename app/components/{PaginatedList.test.js => PaginatedList.test.tsx} (74%) rename app/components/{PaginatedList.js => PaginatedList.tsx} (78%) rename app/components/{PathToDocument.js => PathToDocument.tsx} (70%) rename app/components/{PlaceholderDocument.js => PlaceholderDocument.tsx} (79%) rename app/components/{PlaceholderText.js => PlaceholderText.tsx} (68%) rename app/components/{Popover.js => Popover.tsx} (79%) rename app/components/{ProfiledRoute.js => ProfiledRoute.ts} (58%) rename app/components/{RegisterKeyDown.js => RegisterKeyDown.ts} (71%) rename app/components/{Scene.js => Scene.tsx} (67%) rename app/components/{ScrollToTop.js => ScrollToTop.ts} (88%) rename app/components/{Scrollable.js => Scrollable.tsx} (83%) rename app/components/Sidebar/{Main.js => Main.tsx} (86%) rename app/components/Sidebar/{Settings.js => Settings.tsx} (93%) rename app/components/Sidebar/{Sidebar.js => Sidebar.tsx} (81%) rename app/components/Sidebar/components/{ArchiveLink.js => ArchiveLink.tsx} (65%) rename app/components/Sidebar/components/{CollectionLink.js => CollectionLink.tsx} (79%) rename app/components/Sidebar/components/{Collections.js => Collections.tsx} (83%) rename app/components/Sidebar/components/{Disclosure.js => Disclosure.ts} (97%) rename app/components/Sidebar/components/{DocumentLink.js => DocumentLink.tsx} (78%) rename app/components/Sidebar/components/{DropCursor.js => DropCursor.tsx} (72%) rename app/components/Sidebar/components/{DropToImport.js => DropToImport.tsx} (66%) rename app/components/Sidebar/components/{EditableTitle.js => EditableTitle.tsx} (93%) rename app/components/Sidebar/components/{Header.js => Header.ts} (86%) rename app/components/Sidebar/components/{NavLink.js => NavLink.tsx} (68%) rename app/components/Sidebar/components/{PlaceholderCollections.js => PlaceholderCollections.tsx} (85%) rename app/components/Sidebar/components/{ResizeBorder.js => ResizeBorder.ts} (95%) rename app/components/Sidebar/components/{Section.js => Section.ts} (86%) rename app/components/Sidebar/components/{SidebarAction.js => SidebarAction.tsx} (76%) rename app/components/Sidebar/components/{SidebarLink.js => SidebarLink.tsx} (75%) rename app/components/Sidebar/components/{Starred.js => Starred.tsx} (87%) rename app/components/Sidebar/components/{StarredLink.js => StarredLink.tsx} (76%) rename app/components/Sidebar/components/{TeamButton.js => TeamButton.tsx} (80%) rename app/components/Sidebar/components/{Toggle.js => Toggle.tsx} (83%) rename app/components/Sidebar/components/{TrashLink.js => TrashLink.tsx} (72%) rename app/components/Sidebar/components/{Version.js => Version.tsx} (96%) rename app/components/Sidebar/{index.js => index.ts} (85%) rename app/components/{SkipNavContent.js => SkipNavContent.tsx} (93%) rename app/components/{SkipNavLink.js => SkipNavLink.tsx} (92%) rename app/components/{SlackIcon.js => SlackIcon.tsx} (98%) rename app/components/{SocketProvider.js => SocketProvider.tsx} (81%) rename app/components/{Star.js => Star.tsx} (89%) rename app/components/{Subheading.js => Subheading.tsx} (90%) rename app/components/{Switch.js => Switch.tsx} (79%) rename app/components/{Tab.js => Tab.tsx} (85%) rename app/components/{Table.js => Table.tsx} (67%) rename app/components/{Tabs.js => Tabs.tsx} (91%) rename app/components/{TeamLogo.js => TeamLogo.ts} (83%) rename app/components/{Theme.js => Theme.tsx} (51%) rename app/components/{Time.js => Time.tsx} (69%) rename app/components/{Toast.js => Toast.tsx} (73%) rename app/components/{Toasts.js => Toasts.tsx} (77%) delete mode 100644 app/components/Tooltip.js create mode 100644 app/components/Tooltip.tsx rename app/components/{ZapierIcon.js => ZapierIcon.tsx} (98%) create mode 100644 app/components/withStores.tsx delete mode 100644 app/env.js create mode 100644 app/env.ts rename app/hooks/{useBoolean.js => useBoolean.ts} (79%) rename app/hooks/{useCommandBarActions.js => useCommandBarActions.ts} (85%) rename app/hooks/{useCurrentTeam.js => useCurrentTeam.ts} (95%) rename app/hooks/{useCurrentToken.js => useCurrentToken.ts} (96%) rename app/hooks/{useCurrentUser.js => useCurrentUser.ts} (95%) rename app/hooks/{useDebouncedCallback.js => useDebouncedCallback.ts} (75%) rename app/hooks/{useIdle.js => useIdle.ts} (94%) rename app/hooks/{useImportDocument.js => useImportDocument.ts} (86%) rename app/hooks/{useIsMounted.js => useIsMounted.ts} (97%) rename app/hooks/{useKeyDown.js => useKeyDown.ts} (78%) rename app/hooks/{useMediaQuery.js => useMediaQuery.ts} (95%) rename app/hooks/{useMenuHeight.js => useMenuHeight.ts} (68%) rename app/hooks/{useMobile.js => useMobile.ts} (76%) rename app/hooks/{usePageVisibility.js => usePageVisibility.ts} (98%) rename app/hooks/{usePrevious.js => usePrevious.ts} (81%) rename app/hooks/{useQuery.js => useQuery.ts} (93%) rename app/hooks/{useSessions.js => useSessions.ts} (83%) delete mode 100644 app/hooks/useStores.js create mode 100644 app/hooks/useStores.ts rename app/hooks/{useToasts.js => useToasts.ts} (57%) rename app/hooks/{useUnmount.js => useUnmount.ts} (76%) rename app/hooks/{useUserLocale.js => useUserLocale.ts} (95%) rename app/hooks/{useWindowScrollPosition.js => useWindowScrollPosition.ts} (62%) rename app/hooks/{useWindowSize.js => useWindowSize.ts} (77%) rename app/{index.js => index.tsx} (79%) rename app/menus/{AccountMenu.js => AccountMenu.tsx} (63%) rename app/menus/{BreadcrumbMenu.js => BreadcrumbMenu.tsx} (65%) rename app/menus/{CollectionGroupMemberMenu.js => CollectionGroupMemberMenu.tsx} (69%) rename app/menus/{CollectionMenu.js => CollectionMenu.tsx} (79%) rename app/menus/{CollectionSortMenu.js => CollectionSortMenu.tsx} (79%) rename app/menus/{DocumentMenu.js => DocumentMenu.tsx} (77%) rename app/menus/{FileOperationMenu.js => FileOperationMenu.tsx} (67%) rename app/menus/{GroupMemberMenu.js => GroupMemberMenu.tsx} (67%) rename app/menus/{GroupMenu.js => GroupMenu.tsx} (74%) rename app/menus/{MemberMenu.js => MemberMenu.tsx} (65%) rename app/menus/{NewChildDocumentMenu.js => NewChildDocumentMenu.tsx} (67%) rename app/menus/{NewDocumentMenu.js => NewDocumentMenu.tsx} (66%) rename app/menus/{NewTemplateMenu.js => NewTemplateMenu.tsx} (65%) rename app/menus/{RevisionMenu.js => RevisionMenu.tsx} (64%) rename app/menus/{ShareMenu.js => ShareMenu.tsx} (67%) rename app/menus/{TableOfContentsMenu.js => TableOfContentsMenu.tsx} (79%) rename app/menus/{TemplatesMenu.js => TemplatesMenu.tsx} (73%) rename app/menus/{UserMenu.js => UserMenu.tsx} (80%) delete mode 100644 app/menus/separator.js create mode 100644 app/menus/separator.ts rename app/models/{ApiKey.js => ApiKey.ts} (94%) rename app/models/{BaseModel.js => BaseModel.ts} (75%) delete mode 100644 app/models/Collection.js rename app/models/{Collection.test.js => Collection.test.ts} (73%) create mode 100644 app/models/Collection.ts rename app/models/{CollectionGroupMembership.js => CollectionGroupMembership.ts} (97%) rename app/models/{Document.js => Document.ts} (70%) rename app/models/{Event.js => Event.ts} (52%) rename app/models/{FileOperation.js => FileOperation.ts} (90%) rename app/models/{Group.js => Group.ts} (96%) rename app/models/{GroupMembership.js => GroupMembership.ts} (94%) rename app/models/{Integration.js => Integration.ts} (59%) rename app/models/{Membership.js => Membership.ts} (97%) rename app/models/{NotificationSetting.js => NotificationSetting.ts} (94%) rename app/models/{Policy.js => Policy.ts} (66%) rename app/models/{Revision.js => Revision.ts} (96%) rename app/models/{Share.js => Share.ts} (86%) rename app/models/{Team.js => Team.ts} (81%) rename app/models/{User.js => User.ts} (92%) rename app/models/{View.js => View.ts} (97%) rename app/multiplayer/{MultiplayerExtension.js => MultiplayerExtension.ts} (96%) rename app/routes/{authenticated.js => authenticated.tsx} (64%) delete mode 100644 app/routes/index.js create mode 100644 app/routes/index.tsx rename app/routes/{settings.js => settings.tsx} (63%) rename app/scenes/{APITokenNew.js => APITokenNew.tsx} (70%) rename app/scenes/{Archive.js => Archive.tsx} (72%) rename app/scenes/{Collection.js => Collection.tsx} (80%) rename app/scenes/{CollectionDelete.js => CollectionDelete.tsx} (66%) rename app/scenes/{CollectionEdit.js => CollectionEdit.tsx} (73%) rename app/scenes/{CollectionExport.js => CollectionExport.tsx} (69%) rename app/scenes/{CollectionNew.js => CollectionNew.tsx} (69%) rename app/scenes/CollectionPermissions/{AddGroupsToCollection.js => AddGroupsToCollection.tsx} (65%) rename app/scenes/CollectionPermissions/{AddPeopleToCollection.js => AddPeopleToCollection.tsx} (64%) rename app/scenes/CollectionPermissions/components/{CollectionGroupMemberListItem.js => CollectionGroupMemberListItem.tsx} (62%) rename app/scenes/CollectionPermissions/components/{MemberListItem.js => MemberListItem.tsx} (67%) rename app/scenes/CollectionPermissions/components/{UserListItem.js => UserListItem.tsx} (76%) rename app/scenes/CollectionPermissions/{index.js => index.tsx} (80%) rename app/scenes/Document/{Shared.js => Shared.tsx} (59%) rename app/scenes/Document/components/{Container.js => Container.ts} (73%) rename app/scenes/Document/components/{Contents.js => Contents.tsx} (85%) rename app/scenes/Document/components/{DataLoader.js => DataLoader.tsx} (77%) rename app/scenes/Document/components/{Document.js => Document.tsx} (80%) rename app/scenes/Document/components/{EditableTitle.js => EditableTitle.tsx} (74%) rename app/scenes/Document/components/{Editor.js => Editor.tsx} (70%) rename app/scenes/Document/components/{Header.js => Header.tsx} (79%) rename app/scenes/Document/components/{HideSidebar.js => HideSidebar.ts} (78%) rename app/scenes/Document/components/{KeyboardShortcutsButton.js => KeyboardShortcutsButton.tsx} (82%) rename app/scenes/Document/components/{Loading.js => Loading.tsx} (61%) rename app/scenes/Document/components/{MarkAsViewed.js => MarkAsViewed.ts} (77%) rename app/scenes/Document/components/{MultiplayerEditor.js => MultiplayerEditor.tsx} (74%) rename app/scenes/Document/components/{PublicBreadcrumb.js => PublicBreadcrumb.tsx} (58%) rename app/scenes/Document/components/{PublicReferences.js => PublicReferences.tsx} (81%) rename app/scenes/Document/components/{ReferenceListItem.js => ReferenceListItem.tsx} (79%) delete mode 100644 app/scenes/Document/components/References.js create mode 100644 app/scenes/Document/components/References.tsx rename app/scenes/Document/components/{ShareButton.js => ShareButton.tsx} (86%) rename app/scenes/Document/components/{SharePopover.js => SharePopover.tsx} (77%) rename app/scenes/Document/components/{SocketPresence.js => SocketPresence.ts} (78%) rename app/scenes/Document/{index.js => index.tsx} (70%) rename app/scenes/{DocumentDelete.js => DocumentDelete.tsx} (73%) rename app/scenes/{DocumentMove.js => DocumentMove.tsx} (79%) rename app/scenes/{DocumentNew.js => DocumentNew.tsx} (70%) rename app/scenes/{DocumentPermanentDelete.js => DocumentPermanentDelete.tsx} (62%) rename app/scenes/{DocumentReparent.js => DocumentReparent.tsx} (65%) rename app/scenes/{DocumentTemplatize.js => DocumentTemplatize.tsx} (63%) rename app/scenes/{Drafts.js => Drafts.tsx} (58%) rename app/scenes/{Error404.js => Error404.tsx} (77%) rename app/scenes/{ErrorOffline.js => ErrorOffline.tsx} (71%) rename app/scenes/{ErrorSuspended.js => ErrorSuspended.tsx} (57%) rename app/scenes/{GroupDelete.js => GroupDelete.tsx} (62%) rename app/scenes/{GroupEdit.js => GroupEdit.tsx} (64%) rename app/scenes/GroupMembers/{AddPeopleToGroup.js => AddPeopleToGroup.tsx} (63%) rename app/scenes/GroupMembers/{GroupMembers.js => GroupMembers.tsx} (64%) rename app/scenes/GroupMembers/components/{GroupMemberListItem.js => GroupMemberListItem.tsx} (66%) rename app/scenes/GroupMembers/components/{UserListItem.js => UserListItem.tsx} (73%) rename app/scenes/GroupMembers/{index.js => index.ts} (88%) rename app/scenes/{GroupNew.js => GroupNew.tsx} (67%) rename app/scenes/{Home.js => Home.tsx} (71%) rename app/scenes/{Invite.js => Invite.tsx} (77%) rename app/scenes/{KeyboardShortcuts.js => KeyboardShortcuts.tsx} (93%) rename app/scenes/Login/{Notices.js => Notices.tsx} (95%) rename app/scenes/Login/{Provider.js => Provider.tsx} (72%) rename app/scenes/Login/{index.js => index.tsx} (85%) rename app/scenes/{Logout.js => Logout.tsx} (79%) rename app/scenes/Search/{Search.js => Search.tsx} (77%) rename app/scenes/Search/components/{CollectionFilter.js => CollectionFilter.tsx} (81%) delete mode 100644 app/scenes/Search/components/DateFilter.js create mode 100644 app/scenes/Search/components/DateFilter.tsx rename app/scenes/Search/components/{SearchInput.js => SearchInput.tsx} (87%) rename app/scenes/Search/components/{StatusFilter.js => StatusFilter.tsx} (79%) rename app/scenes/Search/components/{UserFilter.js => UserFilter.tsx} (77%) rename app/scenes/Search/{index.js => index.ts} (85%) rename app/scenes/Settings/{Details.js => Details.tsx} (80%) rename app/scenes/Settings/{Features.js => Features.tsx} (78%) rename app/scenes/Settings/{Groups.js => Groups.tsx} (73%) rename app/scenes/Settings/{ImportExport.js => ImportExport.tsx} (73%) rename app/scenes/Settings/{Notifications.js => Notifications.tsx} (85%) rename app/scenes/Settings/{People.js => People.tsx} (85%) rename app/scenes/Settings/{Profile.js => Profile.tsx} (82%) rename app/scenes/Settings/{Security.js => Security.tsx} (80%) rename app/scenes/Settings/{Shares.js => Shares.tsx} (77%) rename app/scenes/Settings/{Slack.js => Slack.tsx} (74%) rename app/scenes/Settings/{Tokens.js => Tokens.tsx} (77%) rename app/scenes/Settings/{Zapier.js => Zapier.tsx} (78%) rename app/scenes/Settings/components/{FileOperationListItem.js => FileOperationListItem.tsx} (78%) rename app/scenes/Settings/components/{ImageUpload.js => ImageUpload.tsx} (72%) rename app/scenes/Settings/components/{NotificationListItem.js => NotificationListItem.tsx} (57%) rename app/scenes/Settings/components/{PeopleTable.js => PeopleTable.tsx} (55%) rename app/scenes/Settings/components/{ShareListItem.js => ShareListItem.tsx} (69%) rename app/scenes/Settings/components/{SlackButton.js => SlackButton.tsx} (60%) rename app/scenes/Settings/components/{TokenListItem.js => TokenListItem.tsx} (65%) delete mode 100644 app/scenes/Settings/components/UserListItem.js create mode 100644 app/scenes/Settings/components/UserListItem.tsx rename app/scenes/Settings/components/{UserStatusFilter.js => UserStatusFilter.tsx} (85%) rename app/scenes/{Templates.js => Templates.tsx} (71%) rename app/scenes/{Trash.js => Trash.tsx} (71%) rename app/scenes/{UserDelete.js => UserDelete.tsx} (72%) rename app/scenes/{UserProfile.js => UserProfile.tsx} (77%) rename app/stores/{ApiKeysStore.js => ApiKeysStore.ts} (52%) rename app/stores/{AuthStore.js => AuthStore.ts} (73%) rename app/stores/{BaseStore.js => BaseStore.ts} (57%) rename app/stores/{CollectionGroupMembershipsStore.js => CollectionGroupMembershipsStore.ts} (71%) rename app/stores/{CollectionsStore.js => CollectionsStore.ts} (72%) rename app/stores/{DialogsStore.js => DialogsStore.ts} (63%) rename app/stores/{DocumentPresenceStore.js => DocumentPresenceStore.ts} (81%) rename app/stores/{DocumentsStore.js => DocumentsStore.ts} (74%) rename app/stores/{EventsStore.js => EventsStore.ts} (80%) rename app/stores/{FileOperationsStore.js => FileOperationsStore.ts} (77%) rename app/stores/{GroupMembershipsStore.js => GroupMembershipsStore.ts} (76%) rename app/stores/{GroupsStore.js => GroupsStore.ts} (78%) rename app/stores/{IntegrationsStore.js => IntegrationsStore.ts} (61%) rename app/stores/{MembershipsStore.js => MembershipsStore.ts} (74%) delete mode 100644 app/stores/NotificationSettingsStore.js create mode 100644 app/stores/NotificationSettingsStore.ts rename app/stores/{PoliciesStore.js => PoliciesStore.ts} (88%) delete mode 100644 app/stores/RevisionsStore.js create mode 100644 app/stores/RevisionsStore.ts rename app/stores/{RootStore.js => RootStore.ts} (99%) rename app/stores/{SharesStore.js => SharesStore.ts} (70%) delete mode 100644 app/stores/ToastsStore.test.js create mode 100644 app/stores/ToastsStore.test.ts rename app/stores/{ToastsStore.js => ToastsStore.ts} (90%) rename app/stores/{UiStore.js => UiStore.ts} (71%) rename app/stores/{UsersStore.js => UsersStore.ts} (77%) rename app/stores/{ViewsStore.js => ViewsStore.ts} (85%) rename app/stores/{index.js => index.ts} (52%) rename app/styles/{animations.js => animations.ts} (99%) rename app/styles/{globals.js => globals.ts} (99%) delete mode 100644 app/test/setup.js create mode 100644 app/test/setup.ts rename app/test/{support.js => support.ts} (88%) create mode 100644 app/types.ts delete mode 100644 app/types/index.js create mode 100644 app/typings/index.d.ts create mode 100644 app/typings/styled-components.d.ts rename app/utils/{ApiClient.js => ApiClient.ts} (68%) rename app/utils/__mocks__/{ApiClient.js => ApiClient.ts} (50%) rename app/utils/{compressImage.js => compressImage.ts} (54%) rename app/utils/{dates.js => dates.ts} (81%) rename app/utils/{developer.js => developer.ts} (96%) rename app/utils/{domains.js => domains.ts} (77%) rename app/utils/{download.js => download.ts} (54%) rename app/utils/{emoji.js => emoji.ts} (95%) rename app/utils/{errors.js => errors.ts} (98%) delete mode 100644 app/utils/getDataTransferFiles.js create mode 100644 app/utils/getDataTransferFiles.ts rename app/utils/{history.js => history.ts} (92%) rename app/utils/{i18n.js => i18n.ts} (85%) rename app/utils/{isTextInput.js => isTextInput.ts} (73%) rename app/utils/{keyboard.js => keyboard.ts} (76%) rename app/utils/{language.js => language.ts} (81%) rename app/utils/{motion.js => motion.ts} (87%) rename app/utils/{pageVisibility.js => pageVisibility.ts} (96%) rename app/utils/{routeHelpers.js => routeHelpers.ts} (87%) rename app/utils/{sentry.js => sentry.ts} (84%) rename app/utils/{uploadFile.js => uploadFile.ts} (64%) rename app/utils/{urls.test.js => urls.test.ts} (97%) rename app/utils/{urls.js => urls.ts} (93%) delete mode 100644 flow-typed/globals.js delete mode 100644 flow-typed/npm/@sentry/node_vx.x.x.js delete mode 100644 flow-typed/npm/@tippy.js/react_vx.x.x.js delete mode 100644 flow-typed/npm/@tommoor/remove-markdown_vx.x.x.js delete mode 100644 flow-typed/npm/autotrack_vx.x.x.js delete mode 100644 flow-typed/npm/aws-sdk_vx.x.x.js delete mode 100644 flow-typed/npm/boundless-arrow-key-navigation_vx.x.x.js delete mode 100644 flow-typed/npm/bull_vx.x.x.js delete mode 100644 flow-typed/npm/cancan_vx.x.x.js delete mode 100644 flow-typed/npm/classnames_v2.x.x.js delete mode 100644 flow-typed/npm/copy-to-clipboard_v3.x.x.js delete mode 100644 flow-typed/npm/core-js_vx.x.x.js delete mode 100644 flow-typed/npm/debug_v2.x.x.js delete mode 100644 flow-typed/npm/debug_vx.x.x.js delete mode 100644 flow-typed/npm/diff_vx.x.x.js delete mode 100644 flow-typed/npm/dotenv_v4.x.x.js delete mode 100644 flow-typed/npm/emoji-name-map_vx.x.x.js delete mode 100644 flow-typed/npm/emoji-regex_vx.x.x.js delete mode 100644 flow-typed/npm/enzyme-to-json_vx.x.x.js delete mode 100644 flow-typed/npm/enzyme_v2.3.x.js delete mode 100644 flow-typed/npm/es6-error_v4.x.x.js delete mode 100644 flow-typed/npm/exports-loader_vx.x.x.js delete mode 100644 flow-typed/npm/fbemitter_vx.x.x.js delete mode 100644 flow-typed/npm/fetch-test-server_vx.x.x.js delete mode 100644 flow-typed/npm/file-loader_vx.x.x.js delete mode 100644 flow-typed/npm/flow-bin_v0.x.x.js delete mode 100644 flow-typed/npm/flow-typed_vx.x.x.js delete mode 100644 flow-typed/npm/fs-extra_vx.x.x.js delete mode 100644 flow-typed/npm/highlight.js_vx.x.x.js delete mode 100644 flow-typed/npm/history_vx.x.x.js delete mode 100644 flow-typed/npm/html-webpack-plugin_vx.x.x.js delete mode 100644 flow-typed/npm/http-errors_v1.x.x.js delete mode 100644 flow-typed/npm/imports-loader_vx.x.x.js delete mode 100644 flow-typed/npm/invariant_v2.x.x.js delete mode 100644 flow-typed/npm/ioredis_vx.x.x.js delete mode 100644 flow-typed/npm/isomorphic-fetch_v2.x.x.js delete mode 100644 flow-typed/npm/jest-cli_vx.x.x.js delete mode 100644 flow-typed/npm/jest_v22.x.x.js delete mode 100644 flow-typed/npm/js-cookie_v2.x.x.js delete mode 100644 flow-typed/npm/js-search_vx.x.x.js delete mode 100644 flow-typed/npm/js-tree_vx.x.x.js delete mode 100644 flow-typed/npm/json-loader_vx.x.x.js delete mode 100644 flow-typed/npm/jsonwebtoken_v8.3.x.js delete mode 100644 flow-typed/npm/jszip_vx.x.x.js delete mode 100644 flow-typed/npm/koa-bodyparser_v4.x.x.js delete mode 100644 flow-typed/npm/koa-compress_vx.x.x.js delete mode 100644 flow-typed/npm/koa-connect_vx.x.x.js delete mode 100644 flow-typed/npm/koa-convert_vx.x.x.js delete mode 100644 flow-typed/npm/koa-helmet_vx.x.x.js delete mode 100644 flow-typed/npm/koa-jwt_vx.x.x.js delete mode 100644 flow-typed/npm/koa-logger_vx.x.x.js delete mode 100644 flow-typed/npm/koa-mount_vx.x.x.js delete mode 100644 flow-typed/npm/koa-onerror_vx.x.x.js delete mode 100644 flow-typed/npm/koa-router_vx.x.x.js delete mode 100644 flow-typed/npm/koa-sslify_vx.x.x.js delete mode 100644 flow-typed/npm/koa-static_v4.x.x.js delete mode 100644 flow-typed/npm/koa-webpack-dev-middleware_vx.x.x.js delete mode 100644 flow-typed/npm/koa-webpack-hot-middleware_vx.x.x.js delete mode 100644 flow-typed/npm/koa_v2.0.x.js delete mode 100644 flow-typed/npm/lib0_vx.x.x.js delete mode 100644 flow-typed/npm/lib0_vx.x.x.js~4256e7ec (flow) delete mode 100644 flow-typed/npm/lodash.orderby_vx.x.x.js delete mode 100644 flow-typed/npm/lodash_v4.x.x.js delete mode 100644 flow-typed/npm/marked-sanitized_vx.x.x.js delete mode 100644 flow-typed/npm/mobx-react-devtools_vx.x.x.js delete mode 100644 flow-typed/npm/mobx-react_vx.x.x.js delete mode 100644 flow-typed/npm/natural-sort_vx.x.x.js delete mode 100644 flow-typed/npm/node-dev_vx.x.x.js delete mode 100644 flow-typed/npm/node-sass_vx.x.x.js delete mode 100644 flow-typed/npm/nodemailer_vx.x.x.js delete mode 100644 flow-typed/npm/nodemon_vx.x.x.js delete mode 100644 flow-typed/npm/normalizr_v2.x.x.js delete mode 100644 flow-typed/npm/outline-icons_vx.x.x.js delete mode 100644 flow-typed/npm/oy-vey_vx.x.x.js delete mode 100644 flow-typed/npm/parse-domain_vx.x.x.js delete mode 100644 flow-typed/npm/pg-hstore_vx.x.x.js delete mode 100644 flow-typed/npm/pg_v6.x.x.js delete mode 100644 flow-typed/npm/polished_vx.x.x.js delete mode 100644 flow-typed/npm/prettier_v1.x.x.js delete mode 100644 flow-typed/npm/pui-react-tooltip_vx.x.x.js delete mode 100644 flow-typed/npm/query-string_vx.x.x.js delete mode 100644 flow-typed/npm/raf_vx.x.x.js delete mode 100644 flow-typed/npm/randomstring_v1.x.x.js delete mode 100644 flow-typed/npm/raw-loader_vx.x.x.js delete mode 100644 flow-typed/npm/react-addons-test-utils_v15.x.x.js delete mode 100644 flow-typed/npm/react-avatar-editor_vx.x.x.js delete mode 100644 flow-typed/npm/react-color_v2.x.x.js delete mode 100644 flow-typed/npm/react-helmet_v5.x.x.js delete mode 100644 flow-typed/npm/react-i18next_vx.x.x.js delete mode 100644 flow-typed/npm/react-medium-image-zoom_vx.x.x.js delete mode 100644 flow-typed/npm/react-portal_vx.x.x.js delete mode 100644 flow-typed/npm/react-router-dom_v5.x.x.js delete mode 100644 flow-typed/npm/react-test-renderer_v16.x.x.js delete mode 100644 flow-typed/npm/react-waypoint_vx.x.x.js delete mode 100644 flow-typed/npm/redis-lock_vx.x.x.js delete mode 100644 flow-typed/npm/redis_v2.x.x.js delete mode 100644 flow-typed/npm/rich-markdown-editor_vx.x.x.js delete mode 100644 flow-typed/npm/rimraf_v2.x.x.js delete mode 100644 flow-typed/npm/sass-loader_vx.x.x.js delete mode 100644 flow-typed/npm/semver_vx.x.x.js delete mode 100644 flow-typed/npm/sequelize-cli_vx.x.x.js delete mode 100644 flow-typed/npm/sequelize-encrypted_vx.x.x.js delete mode 100644 flow-typed/npm/sequelize_vx.x.x.js delete mode 100644 flow-typed/npm/slate-md-serializer_vx.x.x.js delete mode 100644 flow-typed/npm/slate_vx.x.x.js delete mode 100644 flow-typed/npm/slug_v0.9.x.js delete mode 100644 flow-typed/npm/slug_vx.x.x.js delete mode 100644 flow-typed/npm/socket.io-redis_vx.x.x.js delete mode 100644 flow-typed/npm/socket.io_vx.x.x.js delete mode 100644 flow-typed/npm/socketio-auth_vx.x.x.js delete mode 100644 flow-typed/npm/string-hash_vx.x.x.js delete mode 100644 flow-typed/npm/string-replace-to-array_vx.x.x.js delete mode 100644 flow-typed/npm/styled-components-breakpoint_vx.x.x.js delete mode 100644 flow-typed/npm/styled-components_vx.x.x.js delete mode 100644 flow-typed/npm/styled-normalize_vx.x.x.js delete mode 100644 flow-typed/npm/tiny-cookie_vx.x.x.js delete mode 100644 flow-typed/npm/tmp_vx.x.x.js delete mode 100644 flow-typed/npm/url-loader_vx.x.x.js delete mode 100644 flow-typed/npm/uuid_v2.x.x.js delete mode 100644 flow-typed/npm/validator_vx.x.x.js delete mode 100644 flow-typed/npm/y-indexeddb_vx.x.x.js delete mode 100644 flow-typed/npm/y-prosemirror_vx.x.x.js delete mode 100644 flow-typed/npm/y-protocols_vx.x.x.js delete mode 100644 flow-typed/npm/yjs_vx.x.x.js create mode 100644 server/.eslintrc delete mode 100644 server/__mocks__/events.js create mode 100644 server/__mocks__/events.ts rename server/__snapshots__/{mailer.test.js.snap => mailer.test.ts.snap} (100%) rename server/collaboration/{authentication.js => authentication.ts} (62%) delete mode 100644 server/collaboration/logger.js create mode 100644 server/collaboration/logger.ts rename server/collaboration/{persistence.js => persistence.ts} (77%) delete mode 100644 server/collaboration/tracing.js create mode 100644 server/collaboration/tracing.ts rename server/collaboration/utils/{markdownToYDoc.js => markdownToYDoc.ts} (69%) rename server/commands/{accountProvisioner.test.js => accountProvisioner.test.ts} (89%) rename server/commands/{accountProvisioner.js => accountProvisioner.ts} (66%) rename server/commands/{attachmentCreator.js => attachmentCreator.ts} (61%) rename server/commands/{collectionExporter.js => collectionExporter.ts} (54%) rename server/commands/{collectionImporter.test.js => collectionImporter.test.ts} (86%) rename server/commands/{collectionImporter.js => collectionImporter.ts} (68%) rename server/commands/{documentCreator.js => documentCreator.ts} (53%) rename server/commands/{documentImporter.test.js => documentImporter.test.ts} (95%) rename server/commands/{documentImporter.js => documentImporter.ts} (75%) delete mode 100644 server/commands/documentMover.js rename server/commands/{documentMover.test.js => documentMover.test.ts} (77%) create mode 100644 server/commands/documentMover.ts rename server/commands/{documentPermanentDeleter.test.js => documentPermanentDeleter.test.ts} (80%) rename server/commands/{documentPermanentDeleter.js => documentPermanentDeleter.ts} (59%) rename server/commands/{documentUpdater.js => documentUpdater.ts} (89%) delete mode 100644 server/commands/fileOperationDeleter.js rename server/commands/{fileOperationDeleter.test.js => fileOperationDeleter.test.ts} (68%) create mode 100644 server/commands/fileOperationDeleter.ts rename server/commands/{revisionCreator.test.js => revisionCreator.test.ts} (72%) rename server/commands/{revisionCreator.js => revisionCreator.ts} (50%) rename server/commands/{teamCreator.test.js => teamCreator.test.ts} (93%) rename server/commands/{teamCreator.js => teamCreator.ts} (72%) rename server/commands/{teamPermanentDeleter.test.js => teamPermanentDeleter.test.ts} (70%) rename server/commands/{teamPermanentDeleter.js => teamPermanentDeleter.ts} (58%) rename server/commands/{userCreator.test.js => userCreator.test.ts} (95%) rename server/commands/{userCreator.js => userCreator.ts} (74%) rename server/commands/{userDestroyer.test.js => userDestroyer.test.ts} (83%) rename server/commands/{userDestroyer.js => userDestroyer.ts} (54%) delete mode 100644 server/commands/userInviter.test.js create mode 100644 server/commands/userInviter.test.ts rename server/commands/{userInviter.js => userInviter.ts} (71%) rename server/commands/{userSuspender.test.js => userSuspender.test.ts} (68%) rename server/commands/{userSuspender.js => userSuspender.ts} (52%) rename server/emails/{CollectionNotificationEmail.js => CollectionNotificationEmail.tsx} (73%) rename server/emails/{DocumentNotificationEmail.js => DocumentNotificationEmail.tsx} (69%) rename server/emails/{ExportFailureEmail.js => ExportFailureEmail.tsx} (99%) rename server/emails/{ExportSuccessEmail.js => ExportSuccessEmail.tsx} (96%) rename server/emails/{InviteEmail.js => InviteEmail.tsx} (91%) rename server/emails/{SigninEmail.js => SigninEmail.tsx} (96%) rename server/emails/{WelcomeEmail.js => WelcomeEmail.tsx} (98%) rename server/emails/components/{Body.js => Body.tsx} (92%) rename server/emails/components/{Button.js => Button.tsx} (85%) rename server/emails/components/{EmailLayout.js => EmailLayout.tsx} (84%) rename server/emails/components/{EmptySpace.js => EmptySpace.tsx} (85%) rename server/emails/components/{Footer.js => Footer.tsx} (89%) rename server/emails/components/{Header.js => Header.tsx} (98%) rename server/emails/components/{Heading.js => Heading.tsx} (86%) rename server/emails/{index.js => index.ts} (68%) delete mode 100644 server/env.js create mode 100644 server/env.ts delete mode 100644 server/errors.js create mode 100644 server/errors.ts rename server/{index.js => index.ts} (91%) rename server/logging/{logger.js => logger.ts} (89%) rename server/logging/{metrics.js => metrics.ts} (78%) rename server/logging/{sentry.js => sentry.ts} (84%) rename server/{mailer.test.js => mailer.test.ts} (54%) rename server/{mailer.js => mailer.tsx} (81%) rename server/middlewares/{apexRedirect.js => apexRedirect.ts} (79%) rename server/middlewares/{authentication.test.js => authentication.test.ts} (58%) rename server/middlewares/{authentication.js => authentication.ts} (69%) rename server/middlewares/{errorHandling.js => errorHandling.ts} (84%) rename server/middlewares/{methodOverride.js => methodOverride.ts} (81%) rename server/middlewares/{passport.js => passport.ts} (71%) delete mode 100644 server/middlewares/validation.js rename server/models/{ApiKey.js => ApiKey.ts} (68%) rename server/models/{Attachment.js => Attachment.ts} (73%) rename server/models/{AuthenticationProvider.js => AuthenticationProvider.ts} (67%) rename server/models/{Backlink.js => Backlink.ts} (85%) rename server/models/{Collection.test.js => Collection.test.ts} (89%) rename server/models/{Collection.js => Collection.ts} (68%) rename server/models/{CollectionGroup.js => CollectionGroup.ts} (89%) rename server/models/{CollectionUser.js => CollectionUser.ts} (88%) rename server/models/{Document.test.js => Document.test.ts} (89%) rename server/models/{Document.js => Document.ts} (74%) rename server/models/{Event.js => Event.ts} (87%) rename server/models/{FileOperation.js => FileOperation.ts} (86%) rename server/models/{Group.test.js => Group.test.ts} (56%) rename server/models/{Group.js => Group.ts} (74%) rename server/models/{GroupUser.js => GroupUser.ts} (78%) rename server/models/{Integration.js => Integration.ts} (89%) rename server/models/{IntegrationAuthentication.js => IntegrationAuthentication.ts} (87%) rename server/models/{Notification.js => Notification.ts} (87%) rename server/models/{NotificationSetting.js => NotificationSetting.ts} (88%) rename server/models/{Revision.test.js => Revision.test.ts} (77%) rename server/models/{Revision.js => Revision.ts} (70%) rename server/models/{SearchQuery.js => SearchQuery.ts} (81%) rename server/models/{Share.js => Share.ts} (71%) rename server/models/{Star.js => Star.ts} (77%) rename server/models/{Team.test.js => Team.test.ts} (73%) rename server/models/{Team.js => Team.ts} (73%) rename server/models/{User.test.js => User.test.ts} (81%) rename server/models/{User.js => User.ts} (70%) rename server/models/{UserAuthentication.js => UserAuthentication.ts} (87%) rename server/models/{View.js => View.ts} (67%) rename server/models/{index.js => index.ts} (99%) rename server/policies/{apiKey.js => apiKey.ts} (86%) rename server/policies/{attachment.js => attachment.ts} (92%) rename server/policies/{authenticationProvider.js => authenticationProvider.ts} (81%) rename server/policies/{collection.test.js => collection.test.ts} (85%) rename server/policies/{collection.js => collection.ts} (94%) rename server/policies/{document.test.js => document.test.ts} (90%) rename server/policies/{document.js => document.ts} (94%) rename server/policies/{group.js => group.ts} (85%) rename server/policies/{index.test.js => index.test.ts} (74%) rename server/policies/{index.js => index.ts} (69%) rename server/policies/{integration.js => integration.ts} (82%) rename server/policies/{notificationSetting.js => notificationSetting.ts} (82%) rename server/policies/{policy.js => policy.ts} (86%) rename server/policies/{share.js => share.ts} (89%) rename server/policies/{team.test.js => team.test.ts} (80%) rename server/policies/{team.js => team.ts} (90%) rename server/policies/{user.js => user.ts} (84%) rename server/presenters/__snapshots__/{user.test.js.snap => user.test.ts.snap} (100%) delete mode 100644 server/presenters/apiKey.js create mode 100644 server/presenters/apiKey.ts rename server/presenters/{authenticationProvider.js => authenticationProvider.ts} (59%) rename server/presenters/{collection.js => collection.ts} (72%) rename server/presenters/{collectionGroupMembership.js => collectionGroupMembership.ts} (50%) delete mode 100644 server/presenters/document.js create mode 100644 server/presenters/document.ts rename server/presenters/{env.js => env.ts} (88%) delete mode 100644 server/presenters/event.js create mode 100644 server/presenters/event.ts rename server/presenters/{fileOperation.js => fileOperation.ts} (64%) rename server/presenters/{group.js => group.ts} (53%) delete mode 100644 server/presenters/groupMembership.js create mode 100644 server/presenters/groupMembership.ts rename server/presenters/{index.js => index.ts} (99%) rename server/presenters/{integration.js => integration.ts} (70%) rename server/presenters/{membership.js => membership.ts} (50%) delete mode 100644 server/presenters/notificationSetting.js create mode 100644 server/presenters/notificationSetting.ts delete mode 100644 server/presenters/policy.js create mode 100644 server/presenters/policy.ts rename server/presenters/{revision.js => revision.ts} (63%) rename server/presenters/{share.js => share.ts} (65%) delete mode 100644 server/presenters/slackAttachment.js create mode 100644 server/presenters/slackAttachment.ts rename server/presenters/{team.js => team.ts} (68%) delete mode 100644 server/presenters/user.js rename server/presenters/{user.test.js => user.test.ts} (86%) create mode 100644 server/presenters/user.ts rename server/presenters/{view.js => view.ts} (60%) rename server/queues/{index.js => index.ts} (81%) rename server/queues/processors/{backlinks.test.js => backlinks.test.ts} (73%) rename server/queues/processors/{backlinks.js => backlinks.ts} (84%) rename server/queues/processors/{debouncer.js => debouncer.ts} (75%) delete mode 100644 server/queues/processors/emails.js create mode 100644 server/queues/processors/emails.ts rename server/queues/processors/{exports.js => exports.ts} (74%) rename server/queues/processors/{imports.js => imports.ts} (75%) rename server/queues/processors/{notifications.test.js => notifications.test.ts} (77%) rename server/queues/processors/{notifications.js => notifications.ts} (94%) rename server/queues/processors/{revisions.test.js => revisions.test.ts} (59%) rename server/queues/processors/{revisions.js => revisions.ts} (77%) rename server/queues/processors/{slack.js => slack.ts} (89%) rename server/queues/processors/{websockets.js => websockets.ts} (97%) rename server/{redis.js => redis.ts} (96%) rename server/routes/api/__snapshots__/{collections.test.js.snap => collections.test.ts.snap} (100%) rename server/routes/api/__snapshots__/{documents.test.js.snap => documents.test.ts.snap} (100%) rename server/routes/api/__snapshots__/{events.test.js.snap => events.test.ts.snap} (100%) rename server/routes/api/__snapshots__/{groups.test.js.snap => groups.test.ts.snap} (100%) rename server/routes/api/__snapshots__/{shares.test.js.snap => shares.test.ts.snap} (100%) rename server/routes/api/__snapshots__/{users.test.js.snap => users.test.ts.snap} (100%) rename server/routes/api/__snapshots__/{views.test.js.snap => views.test.ts.snap} (100%) rename server/routes/api/{apiKeys.js => apiKeys.ts} (77%) rename server/routes/api/{attachments.test.js => attachments.test.ts} (82%) rename server/routes/api/{attachments.js => attachments.ts} (80%) rename server/routes/api/{auth.test.js => auth.test.ts} (85%) rename server/routes/api/{auth.js => auth.ts} (71%) rename server/routes/api/{authenticationProviders.test.js => authenticationProviders.test.ts} (86%) rename server/routes/api/{authenticationProviders.js => authenticationProviders.ts} (70%) rename server/routes/api/{collections.test.js => collections.test.ts} (85%) rename server/routes/api/{collections.js => collections.ts} (80%) rename server/routes/api/{documents.test.js => documents.test.ts} (87%) rename server/routes/api/{documents.js => documents.ts} (66%) rename server/routes/api/{events.test.js => events.test.ts} (88%) rename server/routes/api/{events.js => events.ts} (55%) rename server/routes/api/{fileOperations.test.js => fileOperations.test.ts} (83%) rename server/routes/api/{fileOperations.js => fileOperations.ts} (69%) rename server/routes/api/{groups.test.js => groups.test.ts} (71%) rename server/routes/api/{groups.js => groups.ts} (76%) rename server/routes/api/{hooks.test.js => hooks.test.ts} (89%) rename server/routes/api/{hooks.js => hooks.ts} (85%) rename server/routes/api/{index.test.js => index.test.ts} (68%) rename server/routes/api/{index.js => index.ts} (84%) rename server/routes/api/{integrations.js => integrations.ts} (68%) rename server/routes/api/middlewares/{apiWrapper.js => apiWrapper.ts} (64%) rename server/routes/api/middlewares/{editor.js => editor.ts} (66%) rename server/routes/api/middlewares/{pagination.test.js => pagination.test.ts} (64%) rename server/routes/api/middlewares/{pagination.js => pagination.ts} (52%) rename server/routes/api/{notificationSettings.js => notificationSettings.ts} (78%) rename server/routes/api/{revisions.test.js => revisions.test.ts} (84%) rename server/routes/api/{revisions.js => revisions.ts} (57%) rename server/routes/api/{shares.test.js => shares.test.ts} (84%) rename server/routes/api/{shares.js => shares.ts} (82%) rename server/routes/api/{team.test.js => team.test.ts} (69%) rename server/routes/api/{team.js => team.ts} (87%) rename server/routes/api/{users.test.js => users.test.ts} (79%) rename server/routes/api/{users.js => users.ts} (62%) rename server/routes/api/{utils.test.js => utils.test.ts} (77%) rename server/routes/api/{utils.js => utils.ts} (77%) rename server/routes/api/{views.test.js => views.test.ts} (71%) rename server/routes/api/{views.js => views.ts} (55%) rename server/routes/auth/{index.test.js => index.test.ts} (63%) rename server/routes/auth/{index.js => index.ts} (64%) rename server/routes/auth/providers/{azure.js => azure.ts} (68%) rename server/routes/auth/providers/{email.test.js => email.test.ts} (70%) rename server/routes/auth/providers/{email.js => email.ts} (72%) rename server/routes/auth/providers/{google.js => google.ts} (74%) rename server/routes/auth/providers/{index.js => index.ts} (52%) rename server/routes/auth/providers/{oidc.js => oidc.ts} (75%) delete mode 100644 server/routes/auth/providers/slack.js create mode 100644 server/routes/auth/providers/slack.ts rename server/routes/{index.test.js => index.test.ts} (76%) rename server/routes/{index.js => index.ts} (83%) rename server/scripts/{20210226232041-migrate-authentication.test.js => 20210226232041-migrate-authentication.test.ts} (97%) rename server/scripts/{20210226232041-migrate-authentication.js => 20210226232041-migrate-authentication.ts} (87%) rename server/scripts/{20210716000000-backfill-revisions.test.js => 20210716000000-backfill-revisions.test.ts} (89%) rename server/scripts/{20210716000000-backfill-revisions.js => 20210716000000-backfill-revisions.ts} (81%) delete mode 100644 server/scripts/bootstrap.js create mode 100644 server/scripts/bootstrap.ts rename server/{sequelize.js => sequelize.ts} (72%) rename server/services/{admin.js => admin.ts} (86%) rename server/services/{collaboration.js => collaboration.ts} (60%) rename server/services/{index.js => index.ts} (68%) rename server/services/{web.js => web.ts} (82%) rename server/services/{websockets.js => websockets.ts} (86%) rename server/services/{worker.js => worker.ts} (91%) rename server/test/{factories.js => factories.ts} (77%) rename server/test/{setup.js => setup.ts} (97%) rename server/test/{support.js => support.ts} (96%) rename server/{tracing.js => tracing.ts} (83%) delete mode 100644 server/types.js create mode 100644 server/types.ts create mode 100644 server/typings/cancan.d.ts create mode 100644 server/typings/fetch-with-proxy.d.ts create mode 100644 server/typings/koa-onerror.d.ts rename server/utils/__mocks__/{s3.js => s3.ts} (71%) rename server/utils/{args.js => args.ts} (97%) rename server/utils/{authentication.js => authentication.ts} (81%) rename server/utils/{avatars.test.js => avatars.test.ts} (99%) rename server/utils/{avatars.js => avatars.ts} (93%) rename server/utils/{collectionIndexing.js => collectionIndexing.ts} (60%) rename server/utils/{color.js => color.ts} (90%) rename server/utils/{dataURItoBuffer.test.js => dataURItoBuffer.test.ts} (99%) rename server/utils/{dataURItoBuffer.js => dataURItoBuffer.ts} (97%) rename server/utils/{domains.js => domains.ts} (63%) rename server/utils/{fs.test.js => fs.test.ts} (98%) rename server/utils/{fs.js => fs.ts} (84%) delete mode 100644 server/utils/jwt.js create mode 100644 server/utils/jwt.ts rename server/utils/{opensearch.js => opensearch.ts} (98%) rename server/utils/{parseAttachmentIds.test.js => parseAttachmentIds.test.ts} (92%) rename server/utils/{parseAttachmentIds.js => parseAttachmentIds.ts} (97%) rename server/utils/{parseDocumentIds.test.js => parseDocumentIds.test.ts} (98%) rename server/utils/{parseDocumentIds.js => parseDocumentIds.ts} (63%) rename server/utils/{parseImages.test.js => parseImages.test.ts} (97%) rename server/utils/{parseImages.js => parseImages.ts} (52%) rename server/utils/{passport.js => passport.ts} (57%) rename server/utils/{prefetchTags.js => prefetchTags.tsx} (89%) rename server/utils/{queue.js => queue.ts} (92%) rename server/utils/{removeIndexCollision.js => removeIndexCollision.ts} (89%) rename server/utils/{robots.js => robots.ts} (56%) rename server/utils/{s3.js => s3.ts} (75%) rename server/utils/{slack.js => slack.ts} (68%) rename server/utils/{slugify.js => slugify.ts} (94%) rename server/utils/{startup.js => startup.ts} (94%) rename server/utils/{updates.js => updates.ts} (95%) rename server/utils/{zip.js => zip.ts} (61%) create mode 100644 server/validation.ts rename shared/{constants.js => constants.ts} (92%) rename shared/embeds/{Abstract.test.js => Abstract.test.ts} (96%) rename shared/embeds/{Abstract.js => Abstract.tsx} (70%) rename shared/embeds/{Airtable.test.js => Airtable.test.ts} (91%) rename shared/embeds/{Airtable.js => Airtable.tsx} (68%) rename shared/embeds/{Bilibili.test.js => Bilibili.test.ts} (92%) rename shared/embeds/{Bilibili.js => Bilibili.tsx} (70%) rename shared/embeds/{Cawemo.test.js => Cawemo.test.ts} (90%) rename shared/embeds/{Cawemo.js => Cawemo.tsx} (69%) rename shared/embeds/{ClickUp.test.js => ClickUp.test.ts} (89%) rename shared/embeds/{ClickUp.js => ClickUp.tsx} (63%) rename shared/embeds/{Codepen.test.js => Codepen.test.ts} (90%) rename shared/embeds/{Codepen.js => Codepen.tsx} (65%) rename shared/embeds/{Descript.js => Descript.tsx} (68%) rename shared/embeds/{Diagrams.test.js => Diagrams.test.ts} (88%) rename shared/embeds/{Diagrams.js => Diagrams.tsx} (79%) rename shared/embeds/{Figma.test.js => Figma.test.ts} (90%) rename shared/embeds/{Figma.js => Figma.tsx} (68%) rename shared/embeds/{Framer.test.js => Framer.test.ts} (84%) rename shared/embeds/{Framer.js => Framer.tsx} (63%) rename shared/embeds/{Gist.test.js => Gist.test.ts} (91%) rename shared/embeds/{Gist.js => Gist.tsx} (81%) rename shared/embeds/{GoogleCalendar.test.js => GoogleCalendar.test.ts} (90%) rename shared/embeds/{GoogleCalendar.js => GoogleCalendar.tsx} (65%) rename shared/embeds/{GoogleDataStudio.test.js => GoogleDataStudio.test.ts} (91%) rename shared/embeds/{GoogleDataStudio.js => GoogleDataStudio.tsx} (77%) rename shared/embeds/{GoogleDocs.test.js => GoogleDocs.test.ts} (94%) rename shared/embeds/{GoogleDocs.js => GoogleDocs.tsx} (75%) rename shared/embeds/{GoogleDrawings.test.js => GoogleDrawings.test.ts} (94%) rename shared/embeds/{GoogleDrawings.js => GoogleDrawings.tsx} (76%) rename shared/embeds/{GoogleDrive.test.js => GoogleDrive.test.ts} (93%) rename shared/embeds/{GoogleDrive.js => GoogleDrive.tsx} (74%) rename shared/embeds/{GoogleSheets.test.js => GoogleSheets.test.ts} (92%) rename shared/embeds/{GoogleSheets.js => GoogleSheets.tsx} (75%) rename shared/embeds/{GoogleSlides.test.js => GoogleSlides.test.ts} (94%) rename shared/embeds/{GoogleSlides.js => GoogleSlides.tsx} (76%) rename shared/embeds/{InVision.test.js => InVision.test.ts} (91%) rename shared/embeds/{InVision.js => InVision.tsx} (74%) rename shared/embeds/{Loom.test.js => Loom.test.ts} (93%) rename shared/embeds/{Loom.js => Loom.tsx} (64%) rename shared/embeds/{Lucidchart.test.js => Lucidchart.test.ts} (96%) rename shared/embeds/{Lucidchart.js => Lucidchart.tsx} (76%) rename shared/embeds/{Marvel.test.js => Marvel.test.ts} (87%) rename shared/embeds/{Marvel.js => Marvel.tsx} (63%) rename shared/embeds/{Mindmeister.test.js => Mindmeister.test.ts} (95%) rename shared/embeds/{Mindmeister.js => Mindmeister.tsx} (73%) rename shared/embeds/{Miro.test.js => Miro.test.ts} (91%) rename shared/embeds/{Miro.js => Miro.tsx} (72%) rename shared/embeds/{ModeAnalytics.test.js => ModeAnalytics.test.ts} (90%) rename shared/embeds/{ModeAnalytics.js => ModeAnalytics.tsx} (71%) rename shared/embeds/{Pitch.js => Pitch.tsx} (71%) rename shared/embeds/{Prezi.test.js => Prezi.test.ts} (90%) rename shared/embeds/{Prezi.js => Prezi.tsx} (64%) rename shared/embeds/{Spotify.test.js => Spotify.test.ts} (92%) rename shared/embeds/{Spotify.js => Spotify.tsx} (79%) rename shared/embeds/{Trello.js => Trello.tsx} (67%) rename shared/embeds/{Typeform.test.js => Typeform.test.ts} (89%) rename shared/embeds/{Typeform.js => Typeform.tsx} (65%) rename shared/embeds/{Vimeo.test.js => Vimeo.test.ts} (90%) rename shared/embeds/{Vimeo.js => Vimeo.tsx} (70%) rename shared/embeds/{YouTube.test.js => YouTube.test.ts} (93%) rename shared/embeds/{YouTube.js => YouTube.tsx} (69%) rename shared/embeds/components/{Frame.js => Frame.tsx} (82%) rename shared/embeds/components/{Image.js => Image.tsx} (63%) rename shared/embeds/{index.js => index.tsx} (72%) rename shared/i18n/{index.test.js => index.test.ts} (97%) rename shared/i18n/{index.js => index.ts} (57%) rename shared/{random.js => random.ts} (94%) rename shared/{theme.js => theme.ts} (92%) delete mode 100644 shared/types.js create mode 100644 shared/types.ts rename shared/utils/{color.js => color.ts} (91%) rename shared/utils/{date.js => date.ts} (74%) rename shared/utils/{domains.test.js => domains.test.ts} (93%) rename shared/utils/{domains.js => domains.ts} (95%) rename shared/utils/{getTasks.js => getTasks.ts} (78%) rename shared/utils/{indexCharacters.js => indexCharacters.ts} (91%) delete mode 100644 shared/utils/naturalSort.test.js create mode 100644 shared/utils/naturalSort.test.ts rename shared/utils/{naturalSort.js => naturalSort.ts} (56%) rename shared/utils/{parseDocumentSlug.test.js => parseDocumentSlug.test.ts} (98%) rename shared/utils/{parseDocumentSlug.js => parseDocumentSlug.ts} (96%) rename shared/utils/{parseTitle.test.js => parseTitle.test.ts} (93%) rename shared/utils/{parseTitle.js => parseTitle.ts} (86%) rename shared/utils/{routeHelpers.js => routeHelpers.ts} (82%) delete mode 100644 shared/utils/slugify.js create mode 100644 shared/utils/slugify.ts rename shared/utils/{unescape.js => unescape.ts} (94%) rename shared/utils/{urls.js => urls.ts} (94%) rename shared/utils/{zip.js => zip.ts} (74%) create mode 100644 tsconfig.json diff --git a/.babelrc b/.babelrc index 38bb56bdf5..5a7af638eb 100644 --- a/.babelrc +++ b/.babelrc @@ -1,7 +1,7 @@ { "presets": [ "@babel/preset-react", - "@babel/preset-flow", + "@babel/preset-typescript", [ "@babel/preset-env", { diff --git a/.circleci/config.yml b/.circleci/config.yml index bf7e675460..e847cf3b11 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -41,8 +41,8 @@ jobs: name: lint command: yarn lint - run: - name: flow - command: yarn flow check --max-workers 4 + name: typescript + command: yarn tsc - run: name: test command: yarn test diff --git a/.dockerignore b/.dockerignore index 9a2b306ca5..f2dc90801d 100644 --- a/.dockerignore +++ b/.dockerignore @@ -6,14 +6,13 @@ __mocks__ .DS_Store .env* .eslint* -.flowconfig .log Makefile Procfile app.json +crowdin.yml build docker-compose.yml fakes3 -flow-typed node_modules -setupJest.js \ No newline at end of file +tsconfig.json diff --git a/.eslintrc b/.eslintrc index 8eeaa95b1d..7e2d5b3542 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,20 +1,34 @@ { - "parser": "babel-eslint", + "parser": "@typescript-eslint/parser", + "parserOptions": { + "sourceType": "module", + "extraFileExtensions": [".json"], + "ecmaFeatures": { + "jsx": true + } + }, "extends": [ - "react-app", - "plugin:import/errors", - "plugin:import/warnings", - "plugin:flowtype/recommended", - "plugin:react-hooks/recommended" + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + "plugin:import/recommended", + "plugin:import/typescript", + "plugin:react-hooks/recommended", + "plugin:prettier/recommended" ], "plugins": [ - "prettier", - "flowtype" + "@typescript-eslint", + "eslint-plugin-import", + "eslint-plugin-node", + "eslint-plugin-react", + "eslint-plugin-react-hooks", + "import" ], "rules": { "eqeqeq": 2, - "no-unused-vars": 2, "no-mixed-operators": "off", + "padding-line-between-statements": ["error", { "blankLine": "always", "prev": "*", "next": "export" }], + "lines-between-class-members": ["error", "always", { "exceptAfterSingleLine": true }], + "import/newline-after-import": 2, "import/order": [ "error", { @@ -23,53 +37,48 @@ }, "pathGroups": [ { - "pattern": "shared/**", + "pattern": "@shared/**", "group": "external", "position": "after" }, { - "pattern": "stores", + "pattern": "@server/**", "group": "external", "position": "after" }, { - "pattern": "stores/**", + "pattern": "~/stores", "group": "external", "position": "after" }, { - "pattern": "models/**", + "pattern": "~/stores/**", "group": "external", "position": "after" }, { - "pattern": "scenes/**", + "pattern": "~/models/**", "group": "external", "position": "after" }, { - "pattern": "components/**", + "pattern": "~/scenes/**", + "group": "external", + "position": "after" + }, + { + "pattern": "~/components/**", + "group": "external", + "position": "after" + }, + { + "pattern": "~/**", "group": "external", "position": "after" } ] } ], - "flowtype/require-valid-file-annotation": [ - 2, - "always", - { - "annotationStyle": "line" - } - ], - "flowtype/space-after-type-colon": [ - 2, - "always" - ], - "flowtype/space-before-type-colon": [ - 2, - "never" - ], "prettier/prettier": [ "error", { @@ -84,21 +93,13 @@ "pragma": "React", "version": "detect" }, - "import/resolver": { - "node": { - "paths": [ - "app", - "." - ] - } + "import/parsers": { + "@typescript-eslint/parser": [".ts", ".tsx"] }, - "flowtype": { - "onlyFilesWithFlowAnnotation": false + "import/resolver": { + "typescript": {} } }, - "env": { - "jest": true - }, "globals": { "EDITOR_VERSION": true } diff --git a/.flowconfig b/.flowconfig deleted file mode 100644 index 7e630b5656..0000000000 --- a/.flowconfig +++ /dev/null @@ -1,44 +0,0 @@ -[include] -.*/app/.* -.*/server/.* -.*/shared/.* - -[ignore] -.*/node_modules/tiny-cookie/flow/.* -.*/node_modules/styled-components/.* -.*/node_modules/polished/.* -.*/node_modules/mobx/.*.flow -.*/node_modules/react-side-effect/.* -.*/node_modules/fbjs/.* -.*/node_modules/config-chain/.* -.*/node_modules/yjs/.* -.*/node_modules/y-prosemirror/.* -.*/node_modules/y-protocols/.* -.*/node_modules/y-indexeddb/.* -.*/node_modules/lib0/.* -.*/server/scripts/.* -*.test.js - -[libs] - -[options] -emoji=true -sharedmemory.heap_size=3221225472 - -module.system.node.resolve_dirname=node_modules -module.system.node.resolve_dirname=app - -module.name_mapper='^\(.*\)\.md$' -> 'empty/object' -module.name_mapper='^shared\/\(.*\)$' -> '/shared/\1' - -module.file_ext=.js -module.file_ext=.md -module.file_ext=.json - -esproposal.decorators=ignore -esproposal.class_static_fields=enable -esproposal.class_instance_fields=enable -esproposal.optional_chaining=enable - -suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe -suppress_comment=\\(.\\|\n\\)*\\$FlowIssue diff --git a/.vscode/settings.json b/.vscode/settings.json index 041e71a9e7..77b6256c8c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,7 +1,7 @@ { - "javascript.validate.enable": false, - "javascript.format.enable": false, - "typescript.validate.enable": false, - "typescript.format.enable": false, + "javascript.validate.enable": true, + "javascript.format.enable": true, + "typescript.validate.enable": true, + "typescript.format.enable": true, "editor.formatOnSave": true, } \ No newline at end of file diff --git a/README.md b/README.md index 018a15e9b9..8c869ed40d 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,10 @@
- - - + + + +
- - Reload - + {t("Reload")}
{error.toString()}
- - Reload - {" "} + {t("Reload")}{" "} {this.showDetails ? ( Report a Bug… @@ -121,6 +118,7 @@ class ErrorBoundary extends React.Component { ); } + return this.props.children; } } @@ -133,4 +131,4 @@ const Pre = styled.pre` white-space: pre-wrap; `; -export default withTranslation()(ErrorBoundary); +export default withTranslation()(ErrorBoundary); diff --git a/app/components/EventBoundary.js b/app/components/EventBoundary.tsx similarity index 69% rename from app/components/EventBoundary.js rename to app/components/EventBoundary.tsx index bbe28f46d2..41ae1878c0 100644 --- a/app/components/EventBoundary.js +++ b/app/components/EventBoundary.tsx @@ -1,14 +1,12 @@ -// @flow - import * as React from "react"; type Props = { - children: React.Node, - className?: string, + children: React.ReactNode; + className?: string; }; export default function EventBoundary({ children, className }: Props) { - const handleClick = React.useCallback((event: SyntheticEvent<>) => { + const handleClick = React.useCallback((event: React.SyntheticEvent) => { event.preventDefault(); event.stopPropagation(); }, []); diff --git a/app/components/EventListItem.js b/app/components/EventListItem.tsx similarity index 86% rename from app/components/EventListItem.js rename to app/components/EventListItem.tsx index 8da20ba944..e8bd8d8ff0 100644 --- a/app/components/EventListItem.js +++ b/app/components/EventListItem.tsx @@ -1,4 +1,3 @@ -// @flow import { TrashIcon, ArchiveIcon, @@ -10,23 +9,25 @@ import { import * as React from "react"; import { useTranslation } from "react-i18next"; import styled from "styled-components"; -import Document from "models/Document"; -import Event from "models/Event"; -import Avatar from "components/Avatar"; -import Item, { Actions } from "components/List/Item"; -import Time from "components/Time"; -import RevisionMenu from "menus/RevisionMenu"; -import { documentHistoryUrl } from "utils/routeHelpers"; +import Document from "~/models/Document"; +import Event from "~/models/Event"; +import Avatar from "~/components/Avatar"; +import Item, { Actions } from "~/components/List/Item"; +import Time from "~/components/Time"; +import RevisionMenu from "~/menus/RevisionMenu"; +import { documentHistoryUrl } from "~/utils/routeHelpers"; -type Props = {| - document: Document, - event: Event, - latest?: boolean, -|}; +type Props = { + document: Document; + event: Event; + latest?: boolean; +}; const EventListItem = ({ event, latest, document }: Props) => { const { t } = useTranslation(); - const opts = { userName: event.actor.name }; + const opts = { + userName: event.actor.name, + }; const isRevision = event.name === "revisions.create"; let meta, icon, to; @@ -45,28 +46,35 @@ const EventListItem = ({ event, latest, document }: Props) => { break; } } + case "documents.archive": icon = ; meta = t("{{userName}} archived", opts); break; + case "documents.unarchive": meta = t("{{userName}} restored", opts); break; + case "documents.delete": icon = ; meta = t("{{userName}} deleted", opts); break; + case "documents.restore": meta = t("{{userName}} moved from trash", opts); break; + case "documents.publish": icon = ; meta = t("{{userName}} published", opts); break; + case "documents.move": icon = ; meta = t("{{userName}} moved", opts); break; + default: console.warn("Unhandled event: ", event.name); } @@ -78,7 +86,6 @@ const EventListItem = ({ event, latest, document }: Props) => { return ( { } actions={ - isRevision ? ( + isRevision && event.modelId ? ( ) : undefined } diff --git a/app/components/Facepile.js b/app/components/Facepile.tsx similarity index 78% rename from app/components/Facepile.js rename to app/components/Facepile.tsx index cf7a942942..9a4e48b415 100644 --- a/app/components/Facepile.js +++ b/app/components/Facepile.tsx @@ -1,22 +1,21 @@ -// @flow import { observer } from "mobx-react"; import * as React from "react"; import styled from "styled-components"; -import User from "models/User"; -import Avatar from "components/Avatar"; -import Flex from "components/Flex"; +import User from "~/models/User"; +import Avatar from "~/components/Avatar"; +import Flex from "~/components/Flex"; -type Props = {| - users: User[], - size?: number, - overflow: number, - onClick?: (event: SyntheticEvent<>) => mixed, - renderAvatar?: (user: User) => React.Node, -|}; +type Props = { + users: User[]; + size?: number; + overflow?: number; + onClick?: React.MouseEventHandler; + renderAvatar?: (user: User) => React.ReactNode; +}; function Facepile({ users, - overflow, + overflow = 0, size = 32, renderAvatar = DefaultAvatar, ...rest @@ -47,7 +46,7 @@ const AvatarWrapper = styled.div` } `; -const More = styled.div` +const More = styled.div<{ size: number }>` display: flex; flex-direction: column; align-items: center; diff --git a/app/components/Fade.js b/app/components/Fade.ts similarity index 57% rename from app/components/Fade.js rename to app/components/Fade.ts index baff26a394..ad6791d2ae 100644 --- a/app/components/Fade.js +++ b/app/components/Fade.ts @@ -1,8 +1,7 @@ -// @flow import styled from "styled-components"; -import { fadeIn } from "styles/animations"; +import { fadeIn } from "~/styles/animations"; -const Fade = styled.span` +const Fade = styled.span<{ timing?: number | string }>` animation: ${fadeIn} ${(props) => props.timing || "250ms"} ease-in-out; `; diff --git a/app/components/FilterOptions.js b/app/components/FilterOptions.tsx similarity index 66% rename from app/components/FilterOptions.js rename to app/components/FilterOptions.tsx index de41077dfa..d1ef547d8e 100644 --- a/app/components/FilterOptions.js +++ b/app/components/FilterOptions.tsx @@ -1,51 +1,51 @@ -// @flow import { find } from "lodash"; import * as React from "react"; import { useMenuState, MenuButton } from "reakit/Menu"; import styled from "styled-components"; -import Button, { Inner } from "components/Button"; -import ContextMenu from "components/ContextMenu"; -import MenuItem from "components/ContextMenu/MenuItem"; -import HelpText from "components/HelpText"; +import Button, { Inner } from "~/components/Button"; +import ContextMenu from "~/components/ContextMenu"; +import MenuItem from "~/components/ContextMenu/MenuItem"; +import HelpText from "~/components/HelpText"; -type TFilterOption = {| - key: string, - label: string, - note?: string, -|}; +type TFilterOption = { + key: string; + label: string; + note?: string; +}; -type Props = {| - options: TFilterOption[], - activeKey: ?string, - defaultLabel?: string, - selectedPrefix?: string, - className?: string, - onSelect: (key: ?string) => void, -|}; +type Props = { + options: TFilterOption[]; + activeKey: string | null | undefined; + defaultLabel?: string; + selectedPrefix?: string; + className?: string; + onSelect: (key: string | null | undefined) => void; +}; const FilterOptions = ({ options, activeKey = "", - defaultLabel, + defaultLabel = "Filter options", selectedPrefix = "", className, onSelect, }: Props) => { - const menu = useMenuState({ modal: true }); - const selected = find(options, { key: activeKey }) || options[0]; + const menu = useMenuState({ + modal: true, + }); + const selected = + find(options, { + key: activeKey, + }) || options[0]; + + // @ts-expect-error ts-migrate(2339) FIXME: Property 'label' does not exist on type 'number | ... Remove this comment to see the full error message const selectedLabel = selected ? `${selectedPrefix} ${selected.label}` : ""; return ( {(props) => ( - + {activeKey ? selectedLabel : defaultLabel} )} diff --git a/app/components/Flex.js b/app/components/Flex.tsx similarity index 56% rename from app/components/Flex.js rename to app/components/Flex.tsx index 13dee7a9c1..f61e01b52d 100644 --- a/app/components/Flex.js +++ b/app/components/Flex.tsx @@ -1,5 +1,3 @@ -// @flow -import * as React from "react"; import styled from "styled-components"; type JustifyValues = @@ -16,29 +14,14 @@ type AlignValues = | "flex-start" | "flex-end"; -type Props = {| - column?: ?boolean, - shrink?: ?boolean, - align?: AlignValues, - justify?: JustifyValues, - auto?: ?boolean, - className?: string, - children?: React.Node, - role?: string, - gap?: number, -|}; - -const Flex = React.forwardRef((props: Props, ref) => { - const { children, ...restProps } = props; - - return ( - - {children} - - ); -}); - -const Container = styled.div` +const Flex = styled.div<{ + auto?: boolean; + column?: boolean; + align?: AlignValues; + justify?: JustifyValues; + shrink?: boolean; + gap?: number; +}>` display: flex; flex: ${({ auto }) => (auto ? "1 1 auto" : "initial")}; flex-direction: ${({ column }) => (column ? "column" : "row")}; diff --git a/app/components/FullscreenLoading.js b/app/components/FullscreenLoading.tsx similarity index 76% rename from app/components/FullscreenLoading.js rename to app/components/FullscreenLoading.tsx index d645361a18..a7c5ec9f98 100644 --- a/app/components/FullscreenLoading.js +++ b/app/components/FullscreenLoading.tsx @@ -1,9 +1,8 @@ -// @flow import * as React from "react"; import styled from "styled-components"; -import Empty from "components/Empty"; -import Fade from "components/Fade"; -import Flex from "components/Flex"; +import Empty from "~/components/Empty"; +import Fade from "~/components/Fade"; +import Flex from "~/components/Flex"; export default function FullscreenLoading() { return ( diff --git a/app/components/GithubLogo.js b/app/components/GithubLogo.tsx similarity index 94% rename from app/components/GithubLogo.js rename to app/components/GithubLogo.tsx index 7b63aaf6e5..803277628c 100644 --- a/app/components/GithubLogo.js +++ b/app/components/GithubLogo.tsx @@ -1,10 +1,9 @@ -// @flow import * as React from "react"; type Props = { - size?: number, - fill?: string, - className?: string, + size?: number; + fill?: string; + className?: string; }; function GithubLogo({ size = 34, fill = "#FFF", className }: Props) { diff --git a/app/components/GroupListItem.js b/app/components/GroupListItem.tsx similarity index 66% rename from app/components/GroupListItem.js rename to app/components/GroupListItem.tsx index 249080eb31..4604d49bd0 100644 --- a/app/components/GroupListItem.js +++ b/app/components/GroupListItem.tsx @@ -1,31 +1,31 @@ -// @flow import { observable } from "mobx"; -import { observer, inject } from "mobx-react"; +import { observer } from "mobx-react"; import { GroupIcon } from "outline-icons"; import * as React from "react"; import styled from "styled-components"; -import { MAX_AVATAR_DISPLAY } from "shared/constants"; -import GroupMembershipsStore from "stores/GroupMembershipsStore"; -import CollectionGroupMembership from "models/CollectionGroupMembership"; -import Group from "models/Group"; -import GroupMembers from "scenes/GroupMembers"; -import Facepile from "components/Facepile"; -import Flex from "components/Flex"; -import ListItem from "components/List/Item"; -import Modal from "components/Modal"; +import { MAX_AVATAR_DISPLAY } from "@shared/constants"; +import RootStore from "~/stores/RootStore"; +import CollectionGroupMembership from "~/models/CollectionGroupMembership"; +import Group from "~/models/Group"; +import GroupMembers from "~/scenes/GroupMembers"; +import Facepile from "~/components/Facepile"; +import Flex from "~/components/Flex"; +import ListItem from "~/components/List/Item"; +import Modal from "~/components/Modal"; +import withStores from "~/components/withStores"; -type Props = { - group: Group, - groupMemberships: GroupMembershipsStore, - membership?: CollectionGroupMembership, - showFacepile?: boolean, - showAvatar?: boolean, - renderActions: ({ openMembersModal: () => void }) => React.Node, +type Props = RootStore & { + group: Group; + membership?: CollectionGroupMembership; + showFacepile?: boolean; + showAvatar?: boolean; + renderActions: (arg0: { openMembersModal: () => void }) => React.ReactNode; }; @observer class GroupListItem extends React.Component { - @observable membersModalOpen: boolean = false; + @observable + membersModalOpen = false; handleMembersModalOpen = () => { this.membersModalOpen = true; @@ -37,14 +37,12 @@ class GroupListItem extends React.Component { render() { const { group, groupMemberships, showFacepile, renderActions } = this.props; - const memberCount = group.memberCount; - const membershipsInGroup = groupMemberships.inGroup(group.id); const users = membershipsInGroup .slice(0, MAX_AVATAR_DISPLAY) + // @ts-expect-error ts-migrate(2339) FIXME: Property 'user' does not exist on type 'GroupMembe... Remove this comment to see the full error message .map((gm) => gm.user); - const overflow = memberCount - users.length; return ( @@ -84,7 +82,7 @@ class GroupListItem extends React.Component { onRequestClose={this.handleMembersModalClose} isOpen={this.membersModalOpen} > - + > ); @@ -107,4 +105,4 @@ const Title = styled.span` } `; -export default inject("groupMemberships")(GroupListItem); +export default withStores(GroupListItem); diff --git a/app/components/Guide.js b/app/components/Guide.tsx similarity index 88% rename from app/components/Guide.js rename to app/components/Guide.tsx index 28277d4882..9d2727ab05 100644 --- a/app/components/Guide.js +++ b/app/components/Guide.tsx @@ -1,17 +1,16 @@ -// @flow import { observer } from "mobx-react"; import * as React from "react"; import { Dialog, DialogBackdrop, useDialogState } from "reakit/Dialog"; import styled from "styled-components"; -import Scrollable from "components/Scrollable"; -import usePrevious from "hooks/usePrevious"; +import Scrollable from "~/components/Scrollable"; +import usePrevious from "~/hooks/usePrevious"; -type Props = {| - children?: React.Node, - isOpen: boolean, - title?: string, - onRequestClose: () => void, -|}; +type Props = { + children?: React.ReactNode; + isOpen: boolean; + title?: string; + onRequestClose: () => void; +}; const Guide = ({ children, @@ -20,13 +19,16 @@ const Guide = ({ onRequestClose, ...rest }: Props) => { - const dialog = useDialogState({ animated: 250 }); + const dialog = useDialogState({ + animated: 250, + }); const wasOpen = usePrevious(isOpen); React.useEffect(() => { if (!wasOpen && isOpen) { dialog.show(); } + if (wasOpen && !isOpen) { dialog.hide(); } diff --git a/app/components/Header.js b/app/components/Header.tsx similarity index 90% rename from app/components/Header.js rename to app/components/Header.tsx index 5fcb6747ac..012b7ef127 100644 --- a/app/components/Header.js +++ b/app/components/Header.tsx @@ -1,22 +1,20 @@ -// @flow import { throttle } from "lodash"; import { observer } from "mobx-react"; import { transparentize } from "polished"; import * as React from "react"; import styled from "styled-components"; import breakpoint from "styled-components-breakpoint"; -import Fade from "components/Fade"; -import Flex from "components/Flex"; +import Fade from "~/components/Fade"; +import Flex from "~/components/Flex"; -type Props = {| - breadcrumb?: React.Node, - title: React.Node, - actions?: React.Node, -|}; +type Props = { + breadcrumb?: React.ReactNode; + title: React.ReactNode; + actions?: React.ReactNode; +}; function Header({ breadcrumb, title, actions }: Props) { const [isScrolled, setScrolled] = React.useState(false); - const handleScroll = React.useCallback( throttle(() => setScrolled(window.scrollY > 75), 50), [] @@ -24,7 +22,6 @@ function Header({ breadcrumb, title, actions }: Props) { React.useEffect(() => { window.addEventListener("scroll", handleScroll); - return () => window.removeEventListener("scroll", handleScroll); }, [handleScroll]); @@ -39,7 +36,7 @@ function Header({ breadcrumb, title, actions }: Props) { {breadcrumb ? {breadcrumb} : null} {isScrolled ? ( - + {title} ) : ( diff --git a/app/components/Heading.js b/app/components/Heading.ts similarity index 85% rename from app/components/Heading.js rename to app/components/Heading.ts index f625d43efb..2137d4a8ad 100644 --- a/app/components/Heading.js +++ b/app/components/Heading.ts @@ -1,7 +1,6 @@ -// @flow import styled from "styled-components"; -const Heading = styled.h1` +const Heading = styled.h1<{ centered?: boolean }>` display: flex; align-items: center; ${(props) => (props.centered ? "text-align: center;" : "")} diff --git a/app/components/HelpText.js b/app/components/HelpText.ts similarity index 80% rename from app/components/HelpText.js rename to app/components/HelpText.ts index e9f6de9704..375a17c7d8 100644 --- a/app/components/HelpText.js +++ b/app/components/HelpText.ts @@ -1,7 +1,6 @@ -// @flow import styled from "styled-components"; -const HelpText = styled.p` +const HelpText = styled.p<{ small?: boolean }>` margin-top: 0; color: ${(props) => props.theme.textSecondary}; font-size: ${(props) => (props.small ? "13px" : "inherit")}; diff --git a/app/components/Highlight.js b/app/components/Highlight.tsx similarity index 75% rename from app/components/Highlight.js rename to app/components/Highlight.tsx index 85f0040bb2..40761b17b3 100644 --- a/app/components/Highlight.js +++ b/app/components/Highlight.tsx @@ -1,24 +1,24 @@ -// @flow import * as React from "react"; import replace from "string-replace-to-array"; import styled from "styled-components"; -type Props = { - highlight: ?string | RegExp, - processResult?: (tag: string) => string, - text: string, - caseSensitive?: boolean, +type Props = React.HTMLAttributes & { + highlight: (string | null | undefined) | RegExp; + processResult?: (tag: string) => string; + text: string | undefined; + caseSensitive?: boolean; }; function Highlight({ highlight, processResult, caseSensitive, - text, + text = "", ...rest }: Props) { let regex; let index = 0; + if (highlight instanceof RegExp) { regex = highlight; } else { @@ -27,10 +27,11 @@ function Highlight({ caseSensitive ? "g" : "gi" ); } + return ( {highlight - ? replace(text, regex, (tag) => ( + ? replace(text, regex, (tag: string) => ( {processResult ? processResult(tag) : tag} diff --git a/app/components/HoverPreview.js b/app/components/HoverPreview.tsx similarity index 84% rename from app/components/HoverPreview.js rename to app/components/HoverPreview.tsx index c4bfae5dd9..51ffdb8fdf 100644 --- a/app/components/HoverPreview.js +++ b/app/components/HoverPreview.tsx @@ -1,32 +1,29 @@ -// @flow -import { inject } from "mobx-react"; import { transparentize } from "polished"; import * as React from "react"; import { Portal } from "react-portal"; import styled from "styled-components"; -import parseDocumentSlug from "shared/utils/parseDocumentSlug"; -import DocumentsStore from "stores/DocumentsStore"; -import HoverPreviewDocument from "components/HoverPreviewDocument"; -import { fadeAndSlideDown } from "styles/animations"; -import { isInternalUrl } from "utils/urls"; +import parseDocumentSlug from "@shared/utils/parseDocumentSlug"; +import HoverPreviewDocument from "~/components/HoverPreviewDocument"; +import useStores from "~/hooks/useStores"; +import { fadeAndSlideDown } from "~/styles/animations"; +import { isInternalUrl } from "~/utils/urls"; const DELAY_OPEN = 300; const DELAY_CLOSE = 300; type Props = { - node: HTMLAnchorElement, - event: MouseEvent, - documents: DocumentsStore, - onClose: () => void, + node: HTMLAnchorElement; + event: MouseEvent; + onClose: () => void; }; -function HoverPreviewInternal({ node, documents, onClose, event }: Props) { +function HoverPreviewInternal({ node, onClose }: Props) { + const { documents } = useStores(); const slug = parseDocumentSlug(node.href); - const [isVisible, setVisible] = React.useState(false); - const timerClose = React.useRef(); - const timerOpen = React.useRef(); - const cardRef = React.useRef(); + const timerClose = React.useRef>(); + const timerOpen = React.useRef>(); + const cardRef = React.useRef(null); const startCloseTimer = () => { stopOpenTimer(); @@ -54,9 +51,7 @@ function HoverPreviewInternal({ node, documents, onClose, event }: Props) { React.useEffect(() => { if (slug) { - documents.prefetchDocument(slug, { - prefetch: true, - }); + documents.prefetchDocument(slug); } startOpenTimer(); @@ -64,6 +59,7 @@ function HoverPreviewInternal({ node, documents, onClose, event }: Props) { if (cardRef.current) { cardRef.current.addEventListener("mouseenter", stopCloseTimer); } + if (cardRef.current) { cardRef.current.addEventListener("mouseleave", startCloseTimer); } @@ -71,7 +67,6 @@ function HoverPreviewInternal({ node, documents, onClose, event }: Props) { node.addEventListener("mouseout", startCloseTimer); node.addEventListener("mouseover", stopCloseTimer); node.addEventListener("mouseover", startOpenTimer); - return () => { node.removeEventListener("mouseout", startCloseTimer); node.removeEventListener("mouseover", stopCloseTimer); @@ -80,6 +75,7 @@ function HoverPreviewInternal({ node, documents, onClose, event }: Props) { if (cardRef.current) { cardRef.current.removeEventListener("mouseenter", stopCloseTimer); } + if (cardRef.current) { cardRef.current.removeEventListener("mouseleave", startCloseTimer); } @@ -88,12 +84,10 @@ function HoverPreviewInternal({ node, documents, onClose, event }: Props) { clearTimeout(timerClose.current); } }; - }, [node]); + }, [node, slug]); const anchorBounds = node.getBoundingClientRect(); - const cardBounds = cardRef.current - ? cardRef.current.getBoundingClientRect() - : undefined; + const cardBounds = cardRef.current?.getBoundingClientRect(); const left = cardBounds ? Math.min(anchorBounds.left, window.innerWidth - 16 - 350) : anchorBounds.left; @@ -108,7 +102,7 @@ function HoverPreviewInternal({ node, documents, onClose, event }: Props) { > - {(content) => + {(content: React.ReactNode) => isVisible ? ( @@ -196,7 +190,7 @@ const Card = styled.div` } `; -const Position = styled.div` +const Position = styled.div<{ fixed?: boolean; top?: number; left?: number }>` margin-top: 10px; position: ${({ fixed }) => (fixed ? "fixed" : "absolute")}; z-index: ${(props) => props.theme.depths.hoverPreview}; @@ -207,7 +201,7 @@ const Position = styled.div` ${({ left }) => (left !== undefined ? `left: ${left}px` : "")}; `; -const Pointer = styled.div` +const Pointer = styled.div<{ offset: number }>` top: -22px; left: ${(props) => props.offset}px; width: 22px; @@ -238,4 +232,4 @@ const Pointer = styled.div` } `; -export default inject("documents")(HoverPreview); +export default HoverPreview; diff --git a/app/components/HoverPreviewDocument.js b/app/components/HoverPreviewDocument.tsx similarity index 67% rename from app/components/HoverPreviewDocument.js rename to app/components/HoverPreviewDocument.tsx index 2c38afa9d0..7786f32a6a 100644 --- a/app/components/HoverPreviewDocument.js +++ b/app/components/HoverPreviewDocument.tsx @@ -1,25 +1,24 @@ -// @flow import { observer } from "mobx-react"; import * as React from "react"; import { Link } from "react-router-dom"; import styled from "styled-components"; -import parseDocumentSlug from "shared/utils/parseDocumentSlug"; -import DocumentMetaWithViews from "components/DocumentMetaWithViews"; -import Editor from "components/Editor"; -import useStores from "hooks/useStores"; +import parseDocumentSlug from "@shared/utils/parseDocumentSlug"; +import DocumentMetaWithViews from "~/components/DocumentMetaWithViews"; +import Editor from "~/components/Editor"; +import useStores from "~/hooks/useStores"; type Props = { - url: string, - children: (React.Node) => React.Node, + url: string; + children: (arg0: React.ReactNode) => React.ReactNode; }; function HoverPreviewDocument({ url, children }: Props) { const { documents } = useStores(); const slug = parseDocumentSlug(url); - documents.prefetchDocument(slug, { - prefetch: true, - }); + if (slug) { + documents.prefetchDocument(slug); + } const document = slug ? documents.getByUrl(slug) : undefined; if (!document) return null; @@ -50,4 +49,5 @@ const Heading = styled.h2` color: ${(props) => props.theme.text}; `; +// @ts-expect-error ts-migrate(2345) FIXME: Argument of type '({ url, children }: Props) => Re... Remove this comment to see the full error message export default observer(HoverPreviewDocument); diff --git a/app/components/IconPicker.js b/app/components/IconPicker.tsx similarity index 90% rename from app/components/IconPicker.js rename to app/components/IconPicker.tsx index 0547ddfeb8..63dd114877 100644 --- a/app/components/IconPicker.js +++ b/app/components/IconPicker.tsx @@ -1,4 +1,3 @@ -// @flow import { BookmarkedIcon, CollectionIcon, @@ -36,19 +35,22 @@ import { useTranslation } from "react-i18next"; import { useMenuState, MenuButton, MenuItem } from "reakit/Menu"; import styled from "styled-components"; import breakpoint from "styled-components-breakpoint"; -import ContextMenu from "components/ContextMenu"; -import Flex from "components/Flex"; -import HelpText from "components/HelpText"; -import { LabelText } from "components/Input"; -import NudeButton from "components/NudeButton"; +import ContextMenu from "~/components/ContextMenu"; +import Flex from "~/components/Flex"; +import HelpText from "~/components/HelpText"; +import { LabelText } from "~/components/Input"; +import NudeButton from "~/components/NudeButton"; -const style = { width: 30, height: 30 }; - -const TwitterPicker = React.lazy(() => - import( - /* webpackChunkName: "twitter-picker" */ - "react-color/lib/components/twitter/Twitter" - ) +const style = { + width: 30, + height: 30, +}; +const TwitterPicker = React.lazy( + () => + import( + /* webpackChunkName: "twitter-picker" */ + "react-color/lib/components/twitter/Twitter" + ) ); export const icons = { @@ -173,7 +175,6 @@ export const icons = { keywords: "warning alert error", }, }; - const colors = [ "#4E5C6E", "#0366d6", @@ -186,14 +187,13 @@ const colors = [ "#FF4DFA", "#2F362F", ]; - -type Props = {| - onOpen?: () => void, - onClose?: () => void, - onChange: (color: string, icon: string) => void, - icon: string, - color: string, -|}; +type Props = { + onOpen?: () => void; + onClose?: () => void; + onChange: (color: string, icon: string) => void; + icon: string; + color: string; +}; function IconPicker({ onOpen, onClose, icon, color, onChange }: Props) { const { t } = useTranslation(); diff --git a/app/components/Input.js b/app/components/Input.tsx similarity index 67% rename from app/components/Input.js rename to app/components/Input.tsx index 2497c04e8b..0733618061 100644 --- a/app/components/Input.js +++ b/app/components/Input.tsx @@ -1,13 +1,12 @@ -// @flow import { observable } from "mobx"; import { observer } from "mobx-react"; import * as React from "react"; import { VisuallyHidden } from "reakit/VisuallyHidden"; import styled from "styled-components"; import breakpoint from "styled-components-breakpoint"; -import Flex from "components/Flex"; +import Flex from "~/components/Flex"; -const RealTextarea = styled.textarea` +const RealTextarea = styled.textarea<{ hasIcon?: boolean }>` border: 0; flex: 1; padding: 8px 12px 8px ${(props) => (props.hasIcon ? "8px" : "12px")}; @@ -21,7 +20,7 @@ const RealTextarea = styled.textarea` } `; -const RealInput = styled.input` +const RealInput = styled.input<{ hasIcon?: boolean }>` border: 0; flex: 1; padding: 8px 12px 8px ${(props) => (props.hasIcon ? "8px" : "12px")}; @@ -48,7 +47,12 @@ const RealInput = styled.input` `}; `; -const Wrapper = styled.div` +const Wrapper = styled.div<{ + flex?: boolean; + short?: boolean; + minHeight?: number; + maxHeight?: number; +}>` flex: ${(props) => (props.flex ? "1" : "0")}; width: ${(props) => (props.short ? "49%" : "auto")}; max-width: ${(props) => (props.short ? "350px" : "100%")}; @@ -63,7 +67,11 @@ const IconWrapper = styled.span` height: 24px; `; -export const Outline = styled(Flex)` +export const Outline = styled(Flex)<{ + margin?: string | number; + hasError?: boolean; + focused?: boolean; +}>` flex: 1; margin: ${(props) => props.margin !== undefined ? props.margin : "0 0 16px"}; @@ -88,56 +96,58 @@ export const LabelText = styled.div` display: inline-block; `; -export type Props = {| - type?: "text" | "email" | "checkbox" | "search" | "textarea", - value?: string, - label?: string, - className?: string, - labelHidden?: boolean, - flex?: boolean, - short?: boolean, - margin?: string | number, - icon?: React.Node, - name?: string, - minLength?: number, - maxLength?: number, - autoFocus?: boolean, - autoComplete?: boolean | string, - readOnly?: boolean, - required?: boolean, - disabled?: boolean, - placeholder?: string, +export type Props = { + type?: "text" | "email" | "checkbox" | "search" | "textarea"; + value?: string; + label?: string; + className?: string; + labelHidden?: boolean; + flex?: boolean; + short?: boolean; + margin?: string | number; + icon?: React.ReactNode; + name?: string; + minLength?: number; + maxLength?: number; + autoFocus?: boolean; + autoComplete?: boolean | string; + readOnly?: boolean; + required?: boolean; + disabled?: boolean; + placeholder?: string; onChange?: ( - ev: SyntheticInputEvent - ) => mixed, - onKeyDown?: (ev: SyntheticKeyboardEvent) => mixed, - onFocus?: (ev: SyntheticEvent<>) => mixed, - onBlur?: (ev: SyntheticEvent<>) => mixed, -|}; + ev: React.ChangeEvent + ) => unknown; + onKeyDown?: (ev: React.KeyboardEvent) => unknown; + onFocus?: (ev: React.SyntheticEvent) => unknown; + onBlur?: (ev: React.SyntheticEvent) => unknown; +}; @observer class Input extends React.Component { - input: ?HTMLInputElement; - @observable focused: boolean = false; + input = React.createRef(); - handleBlur = (ev: SyntheticEvent<>) => { + @observable + focused = false; + + handleBlur = (ev: React.SyntheticEvent) => { this.focused = false; + if (this.props.onBlur) { this.props.onBlur(ev); } }; - handleFocus = (ev: SyntheticEvent<>) => { + handleFocus = (ev: React.SyntheticEvent) => { this.focused = true; + if (this.props.onFocus) { this.props.onFocus(ev); } }; focus() { - if (this.input) { - this.input.focus(); - } + this.input.current?.focus(); } render() { @@ -155,7 +165,8 @@ class Input extends React.Component { ...rest } = this.props; - const InputComponent = type === "textarea" ? RealTextarea : RealInput; + const InputComponent: React.ComponentType = + type === "textarea" ? RealTextarea : RealInput; const wrappedLabel = {label}; return ( @@ -170,11 +181,12 @@ class Input extends React.Component { {icon && {icon}} (this.input = ref)} + // @ts-expect-error no idea why this is not working + ref={this.input} onBlur={this.handleBlur} onFocus={this.handleFocus} - type={type === "textarea" ? undefined : type} hasIcon={!!icon} + type={type === "textarea" ? undefined : type} {...rest} /> diff --git a/app/components/InputLarge.js b/app/components/InputLarge.ts similarity index 96% rename from app/components/InputLarge.js rename to app/components/InputLarge.ts index 68fc10333c..f56b76445c 100644 --- a/app/components/InputLarge.js +++ b/app/components/InputLarge.ts @@ -1,4 +1,3 @@ -// @flow import styled from "styled-components"; import Input from "./Input"; diff --git a/app/components/InputRich.js b/app/components/InputRich.tsx similarity index 72% rename from app/components/InputRich.js rename to app/components/InputRich.tsx index 1bb991ac5c..bc0457ee70 100644 --- a/app/components/InputRich.js +++ b/app/components/InputRich.tsx @@ -1,28 +1,25 @@ -// @flow import { observer } from "mobx-react"; import * as React from "react"; import { Trans } from "react-i18next"; -import styled, { withTheme } from "styled-components"; -import Editor from "components/Editor"; -import HelpText from "components/HelpText"; -import { LabelText, Outline } from "components/Input"; -import useStores from "hooks/useStores"; +import styled from "styled-components"; +import Editor from "~/components/Editor"; +import HelpText from "~/components/HelpText"; +import { LabelText, Outline } from "~/components/Input"; +import useStores from "~/hooks/useStores"; -type Props = {| - label: string, - minHeight?: number, - maxHeight?: number, - readOnly?: boolean, -|}; +type Props = { + label: string; + minHeight?: number; + maxHeight?: number; + readOnly?: boolean; +}; function InputRich({ label, minHeight, maxHeight, ...rest }: Props) { const [focused, setFocused] = React.useState(false); const { ui } = useStores(); - const handleBlur = React.useCallback(() => { setFocused(false); }, []); - const handleFocus = React.useCallback(() => { setFocused(true); }, []); @@ -55,7 +52,11 @@ function InputRich({ label, minHeight, maxHeight, ...rest }: Props) { ); } -const StyledOutline = styled(Outline)` +const StyledOutline = styled(Outline)<{ + minHeight?: number; + maxHeight?: number; + focused?: boolean; +}>` display: block; padding: 8px 12px; min-height: ${({ minHeight }) => (minHeight ? `${minHeight}px` : "0")}; @@ -67,4 +68,4 @@ const StyledOutline = styled(Outline)` } `; -export default observer(withTheme(InputRich)); +export default observer(InputRich); diff --git a/app/components/InputSearch.js b/app/components/InputSearch.tsx similarity index 77% rename from app/components/InputSearch.js rename to app/components/InputSearch.tsx index 6c9180d3cc..1a1fbdca78 100644 --- a/app/components/InputSearch.js +++ b/app/components/InputSearch.tsx @@ -1,23 +1,20 @@ -// @flow import { SearchIcon } from "outline-icons"; import * as React from "react"; import { useTranslation } from "react-i18next"; import { useTheme } from "styled-components"; -import Input, { type Props as InputProps } from "./Input"; +import Input, { Props as InputProps } from "./Input"; -type Props = {| - ...InputProps, - placeholder?: string, - value?: string, - onChange: (event: SyntheticInputEvent<>) => mixed, - onKeyDown?: (event: SyntheticKeyboardEvent) => mixed, -|}; +type Props = InputProps & { + placeholder?: string; + value?: string; + onChange: (event: React.ChangeEvent) => unknown; + onKeyDown?: (event: React.KeyboardEvent) => unknown; +}; export default function InputSearch(props: Props) { const { t } = useTranslation(); const theme = useTheme(); const [isFocused, setIsFocused] = React.useState(false); - const handleFocus = React.useCallback(() => { setIsFocused(true); }, []); diff --git a/app/components/InputSearchPage.js b/app/components/InputSearchPage.tsx similarity index 74% rename from app/components/InputSearchPage.js rename to app/components/InputSearchPage.tsx index 52a5a9fa29..a5d75be87c 100644 --- a/app/components/InputSearchPage.js +++ b/app/components/InputSearchPage.tsx @@ -1,26 +1,25 @@ -// @flow import { observer } from "mobx-react"; import { SearchIcon } from "outline-icons"; import * as React from "react"; import { useTranslation } from "react-i18next"; import { useHistory } from "react-router-dom"; import styled, { useTheme } from "styled-components"; +import useBoolean from "~/hooks/useBoolean"; +import useKeyDown from "~/hooks/useKeyDown"; +import { isModKey } from "~/utils/keyboard"; +import { searchUrl } from "~/utils/routeHelpers"; import Input from "./Input"; -import useBoolean from "hooks/useBoolean"; -import useKeyDown from "hooks/useKeyDown"; -import { isModKey } from "utils/keyboard"; -import { searchUrl } from "utils/routeHelpers"; -type Props = {| - source: string, - placeholder?: string, - label?: string, - labelHidden?: boolean, - collectionId?: string, - value: string, - onChange: (event: SyntheticInputEvent<>) => mixed, - onKeyDown: (event: SyntheticKeyboardEvent) => mixed, -|}; +type Props = { + source: string; + placeholder?: string; + label?: string; + labelHidden?: boolean; + collectionId?: string; + value?: string; + onChange?: (event: React.ChangeEvent) => unknown; + onKeyDown?: (event: React.KeyboardEvent) => unknown; +}; function InputSearchPage({ onKeyDown, @@ -31,12 +30,11 @@ function InputSearchPage({ collectionId, source, }: Props) { - const inputRef = React.useRef(); + const inputRef = React.useRef(null); const theme = useTheme(); const history = useHistory(); const { t } = useTranslation(); const [isFocused, setFocused, setUnfocused] = useBoolean(false); - const focus = React.useCallback(() => { inputRef.current?.focus(); }, []); @@ -49,7 +47,7 @@ function InputSearchPage({ }); const handleKeyDown = React.useCallback( - (ev: SyntheticKeyboardEvent) => { + (ev: React.KeyboardEvent) => { if (ev.key === "Enter") { ev.preventDefault(); history.push( diff --git a/app/components/InputSelect.js b/app/components/InputSelect.tsx similarity index 78% rename from app/components/InputSelect.js rename to app/components/InputSelect.tsx index 250d55de5b..a99d983c37 100644 --- a/app/components/InputSelect.js +++ b/app/components/InputSelect.tsx @@ -1,4 +1,3 @@ -// @flow import { Select, SelectOption, @@ -11,32 +10,38 @@ import * as React from "react"; import { VisuallyHidden } from "reakit/VisuallyHidden"; import scrollIntoView from "smooth-scroll-into-view-if-needed"; import styled, { css } from "styled-components"; -import Button, { Inner } from "components/Button"; -import HelpText from "components/HelpText"; -import { Position, Background, Backdrop } from "./ContextMenu"; +import Button, { Inner } from "~/components/Button"; +import HelpText from "~/components/HelpText"; +import useMenuHeight from "~/hooks/useMenuHeight"; +import { Position, Background, Backdrop, Placement } from "./ContextMenu"; import { MenuAnchorCSS } from "./ContextMenu/MenuItem"; import { LabelText } from "./Input"; -import useMenuHeight from "hooks/useMenuHeight"; -export type Option = { label: string, value: string }; - -export type Props = { - value?: string, - label?: string, - nude?: boolean, - ariaLabel: string, - short?: boolean, - disabled?: boolean, - className?: string, - labelHidden?: boolean, - icon?: React.Node, - options: Option[], - note?: React.Node, - onChange: (string) => Promise | void, +export type Option = { + label: string; + value: string; }; -const getOptionFromValue = (options: Option[], value) => { - return options.find((option) => option.value === value) || {}; +export type Props = { + value?: string; + label?: string; + nude?: boolean; + ariaLabel: string; + short?: boolean; + disabled?: boolean; + className?: string; + labelHidden?: boolean; + icon?: React.ReactNode; + options: Option[]; + note?: React.ReactNode; + onChange: (value: string | null) => void; +}; + +const getOptionFromValue = ( + options: Option[], + value: string | undefined | null +) => { + return options.find((option) => option.value === value); }; const InputSelect = (props: Props) => { @@ -50,7 +55,6 @@ const InputSelect = (props: Props) => { ariaLabel, onChange, disabled, - nude, note, icon, } = props; @@ -69,13 +73,12 @@ const InputSelect = (props: Props) => { disabled, }); - const previousValue = React.useRef(value); - const contentRef = React.useRef(); - const selectedRef = React.useRef(); - const buttonRef = React.useRef(); + const previousValue = React.useRef(value); + const contentRef = React.useRef(null); + const selectedRef = React.useRef(null); + const buttonRef = React.useRef(null); const [offset, setOffset] = React.useState(0); const minWidth = buttonRef.current?.offsetWidth || 0; - const maxHeight = useMenuHeight( select.visible, select.unstable_disclosureRef @@ -83,16 +86,15 @@ const InputSelect = (props: Props) => { React.useEffect(() => { if (previousValue.current === select.selectedValue) return; - previousValue.current = select.selectedValue; + async function load() { await onChange(select.selectedValue); } + load(); }, [onChange, select.selectedValue]); - const wrappedLabel = {label}; - const selectedValueIndex = options.findIndex( (option) => option.value === select.selectedValue ); @@ -102,7 +104,7 @@ const InputSelect = (props: Props) => { if (!select.animating && selectedRef.current) { scrollIntoView(selectedRef.current, { scrollMode: "if-needed", - behavior: "instant", + behavior: "auto", block: "start", }); } @@ -134,18 +136,24 @@ const InputSelect = (props: Props) => { neutral disclosure className={className} - nude={nude} icon={icon} {...props} > - {getOptionFromValue(options, select.selectedValue).label || ( + {getOptionFromValue(options, select.selectedValue)?.label || ( Select a {ariaLabel.toLowerCase()} )} )} - {(props) => { + {( + props: React.HTMLAttributes & { + placement: Placement; + } + ) => { + if (!props.style) { + props.style = {}; + } const topAnchor = props.style.top === "0"; const rightAnchor = props.placement === "bottom-end"; @@ -163,8 +171,13 @@ const InputSelect = (props: Props) => { rightAnchor={rightAnchor} style={ maxHeight && topAnchor - ? { maxHeight, minWidth } - : { minWidth } + ? { + maxHeight, + minWidth, + } + : { + minWidth, + } } > {select.visible || select.animating @@ -173,7 +186,7 @@ const InputSelect = (props: Props) => { {...select} value={option.value} key={option.value} - animating={select.animating} + $animating={select.animating} ref={ select.selectedValue === option.value ? selectedRef @@ -201,7 +214,6 @@ const InputSelect = (props: Props) => { {note && {note}} - {(select.visible || select.animating) && } > ); @@ -217,7 +229,7 @@ const Spacer = styled.div` flex-shrink: 0; `; -const StyledButton = styled(Button)` +const StyledButton = styled(Button)<{ nude?: boolean }>` font-weight: normal; text-transform: none; margin-bottom: 16px; @@ -243,17 +255,17 @@ const StyledButton = styled(Button)` } `; -export const StyledSelectOption = styled(SelectOption)` +export const StyledSelectOption = styled(SelectOption)<{ $animating: boolean }>` ${MenuAnchorCSS} ${(props) => - props.animating && + props.$animating && css` pointer-events: none; `} `; -const Wrapper = styled.label` +const Wrapper = styled.label<{ short?: boolean }>` display: block; max-width: ${(props) => (props.short ? "350px" : "100%")}; `; diff --git a/app/components/InputSelectPermission.js b/app/components/InputSelectPermission.tsx similarity index 57% rename from app/components/InputSelectPermission.js rename to app/components/InputSelectPermission.tsx index c03d5f23bb..bbe37751f4 100644 --- a/app/components/InputSelectPermission.js +++ b/app/components/InputSelectPermission.tsx @@ -1,19 +1,25 @@ -// @flow import * as React from "react"; import { useTranslation } from "react-i18next"; -import InputSelect, { type Props, type Option } from "./InputSelect"; +import { $Diff } from "utility-types"; +import InputSelect, { Props, Option } from "./InputSelect"; export default function InputSelectPermission( - props: $Rest<$Exact, {| options: Array, ariaLabel: string |}> + props: $Diff< + Props, + { + options: Array; + ariaLabel: string; + } + > ) { const { value, onChange, ...rest } = props; const { t } = useTranslation(); - const handleChange = React.useCallback( (value) => { if (value === "no_access") { value = ""; } + onChange(value); }, [onChange] @@ -23,9 +29,18 @@ export default function InputSelectPermission( , {| options: Array, ariaLabel: string |}> -) => { - const { t } = useTranslation(); - - return ( - - ); -}; - -export default InputSelectRole; diff --git a/app/components/InputSelectRole.tsx b/app/components/InputSelectRole.tsx new file mode 100644 index 0000000000..15aa55114a --- /dev/null +++ b/app/components/InputSelectRole.tsx @@ -0,0 +1,39 @@ +import * as React from "react"; +import { useTranslation } from "react-i18next"; +import { $Diff } from "utility-types"; +import InputSelect, { Props, Option } from "~/components/InputSelect"; + +const InputSelectRole = ( + props: $Diff< + Props, + { + options: Array; + ariaLabel: string; + } + > +) => { + const { t } = useTranslation(); + return ( + + ); +}; + +export default InputSelectRole; diff --git a/app/components/Key.js b/app/components/Key.ts similarity index 98% rename from app/components/Key.js rename to app/components/Key.ts index 09891971b0..94526da5c7 100644 --- a/app/components/Key.js +++ b/app/components/Key.ts @@ -1,4 +1,3 @@ -// @flow import styled from "styled-components"; const Key = styled.kbd` diff --git a/app/components/Labeled.js b/app/components/Labeled.tsx similarity index 78% rename from app/components/Labeled.js rename to app/components/Labeled.tsx index 58194ef717..6c90c7fef0 100644 --- a/app/components/Labeled.js +++ b/app/components/Labeled.tsx @@ -1,13 +1,12 @@ -// @flow import { observer } from "mobx-react"; import * as React from "react"; import styled from "styled-components"; -import Flex from "components/Flex"; +import Flex from "~/components/Flex"; -type Props = {| - label: React.Node | string, - children: React.Node, -|}; +type Props = { + label: React.ReactNode | string; + children?: React.ReactNode; +}; const Labeled = ({ label, children, ...props }: Props) => ( diff --git a/app/components/LanguagePrompt.js b/app/components/LanguagePrompt.tsx similarity index 85% rename from app/components/LanguagePrompt.js rename to app/components/LanguagePrompt.tsx index 46e61f64df..e28ad91d0d 100644 --- a/app/components/LanguagePrompt.js +++ b/app/components/LanguagePrompt.tsx @@ -1,17 +1,16 @@ -// @flow import { find } from "lodash"; import * as React from "react"; import { Trans, useTranslation } from "react-i18next"; import styled from "styled-components"; -import { languages, languageOptions } from "shared/i18n"; -import ButtonLink from "components/ButtonLink"; -import Flex from "components/Flex"; -import NoticeTip from "components/NoticeTip"; -import useCurrentUser from "hooks/useCurrentUser"; -import useStores from "hooks/useStores"; -import { detectLanguage } from "utils/language"; +import { languages, languageOptions } from "@shared/i18n"; +import ButtonLink from "~/components/ButtonLink"; +import Flex from "~/components/Flex"; +import NoticeTip from "~/components/NoticeTip"; +import useCurrentUser from "~/hooks/useCurrentUser"; +import useStores from "~/hooks/useStores"; +import { detectLanguage } from "~/utils/language"; -function Icon(props) { +function Icon(props: any) { return ( - Outline is available in your language {{ optionLabel }}, would you - like to change? + Outline is available in your language{" "} + {{ + optionLabel, + }} + , would you like to change? - import( - /* webpackChunkName: "document-history" */ "components/DocumentHistory" - ) +const DocumentHistory = React.lazy( + () => + import( + /* webpackChunkName: "document-history" */ + "~/components/DocumentHistory" + ) +); +const CommandBar = React.lazy( + () => + import( + /* webpackChunkName: "command-bar" */ + "~/components/CommandBar" + ) ); -const CommandBar = React.lazy(() => - import( - /* webpackChunkName: "command-bar" */ - "components/CommandBar" - ) -); - -type Props = { - documents: DocumentsStore, - children?: ?React.Node, - actions?: ?React.Node, - title?: ?React.Node, - auth: AuthStore, - ui: UiStore, - history: RouterHistory, - policies: PoliciesStore, - notifications?: React.Node, -}; +type Props = WithTranslation & + RootStore & { + children?: React.ReactNode; + }; @observer class Layout extends React.Component { - scrollable: ?HTMLDivElement; - @observable keyboardShortcutsOpen: boolean = false; + scrollable: HTMLDivElement | null | undefined; + + @observable + keyboardShortcutsOpen = false; goToSearch = (ev: KeyboardEvent) => { ev.preventDefault(); ev.stopPropagation(); - this.props.history.push(searchUrl()); + history.push(searchUrl()); }; goToNewDocument = () => { const { activeCollectionId } = this.props.ui; if (!activeCollectionId) return; - const can = this.props.policies.abilities(activeCollectionId); if (!can.update) return; - - this.props.history.push(newDocumentPath(activeCollectionId)); + history.push(newDocumentPath(activeCollectionId)); }; render() { @@ -85,7 +73,6 @@ class Layout extends React.Component { const { user, team } = auth; const showSidebar = auth.authenticated && user && team; const sidebarCollapsed = ui.isEditing || ui.sidebarCollapsed; - if (auth.isSuspended) return ; return ( @@ -111,7 +98,6 @@ class Layout extends React.Component { {this.props.ui.progressBarVisible && } - {this.props.notifications} { style={ sidebarCollapsed ? undefined - : { marginLeft: `${ui.sidebarWidth}px` } + : { + marginLeft: `${ui.sidebarWidth}px`, + } } > {this.props.children} @@ -181,7 +169,10 @@ const MobileMenuButton = styled(Button)` } `; -const Content = styled(Flex)` +const Content = styled(Flex)<{ + $isResizing?: boolean; + $sidebarCollapsed?: boolean; +}>` margin: 0; transition: ${(props) => props.$isResizing ? "none" : `margin-left 100ms ease-out`}; @@ -195,12 +186,10 @@ const Content = styled(Flex)` `} ${breakpoint("tablet")` - ${(props) => + ${(props: any) => props.$sidebarCollapsed && `margin-left: ${props.theme.sidebarCollapsedWidth}px;`} `}; `; -export default withTranslation()( - inject("auth", "ui", "documents", "policies")(withRouter(Layout)) -); +export default withTranslation()(withStores(Layout)); diff --git a/app/components/List/Item.js b/app/components/List/Item.tsx similarity index 65% rename from app/components/List/Item.js rename to app/components/List/Item.tsx index 7098d9fe28..804def37ef 100644 --- a/app/components/List/Item.js +++ b/app/components/List/Item.tsx @@ -1,27 +1,26 @@ -// @flow import * as React from "react"; import styled, { useTheme } from "styled-components"; -import Flex from "components/Flex"; -import NavLink from "components/NavLink"; +import Flex from "~/components/Flex"; +import NavLink from "~/components/NavLink"; -type Props = {| - image?: React.Node, - to?: string, - title: React.Node, - subtitle?: React.Node, - actions?: React.Node, - border?: boolean, - small?: boolean, -|}; +type Props = { + image?: React.ReactNode; + to?: string; + title: React.ReactNode; + subtitle?: React.ReactNode; + actions?: React.ReactNode; + border?: boolean; + small?: boolean; +}; const ListItem = ( { image, title, subtitle, actions, small, border, to, ...rest }: Props, - ref + ref?: React.Ref ) => { const theme = useTheme(); const compact = !subtitle; - const content = (selected) => ( + const content = (selected: boolean) => ( <> {image && {image}} ); + if (to) { + return ( + + {content} + + ); + } + return ( - - {to ? content : content(false)} + + {content(false)} ); }; -const Wrapper = styled.div` +const Wrapper = styled.div<{ $border?: boolean }>` display: flex; padding: ${(props) => (props.$border === false ? 0 : "8px 0")}; margin: ${(props) => (props.$border === false ? "8px 0" : 0)}; @@ -80,7 +89,7 @@ const Image = styled(Flex)` align-self: center; `; -const Heading = styled.p` +const Heading = styled.p<{ $small?: boolean }>` font-size: ${(props) => (props.$small ? 14 : 16)}px; font-weight: 500; white-space: nowrap; @@ -90,13 +99,13 @@ const Heading = styled.p` margin: 0; `; -const Content = styled(Flex)` +const Content = styled(Flex)<{ $selected: boolean }>` flex-direction: column; flex-grow: 1; color: ${(props) => (props.$selected ? props.theme.white : props.theme.text)}; `; -const Subtitle = styled.p` +const Subtitle = styled.p<{ $small?: boolean; $selected?: boolean }>` margin: 0; font-size: ${(props) => (props.$small ? 13 : 14)}px; color: ${(props) => @@ -104,11 +113,11 @@ const Subtitle = styled.p` margin-top: -2px; `; -export const Actions = styled(Flex)` +export const Actions = styled(Flex)<{ $selected?: boolean }>` align-self: center; justify-content: center; color: ${(props) => props.$selected ? props.theme.white : props.theme.textSecondary}; `; -export default React.forwardRef(ListItem); +export default React.forwardRef(ListItem); diff --git a/app/components/List/List.js b/app/components/List/List.ts similarity index 93% rename from app/components/List/List.js rename to app/components/List/List.ts index 33fb89e053..f5bba2ecec 100644 --- a/app/components/List/List.js +++ b/app/components/List/List.ts @@ -1,4 +1,3 @@ -// @flow import styled from "styled-components"; const List = styled.ol` diff --git a/app/components/List/Placeholder.js b/app/components/List/Placeholder.tsx similarity index 76% rename from app/components/List/Placeholder.js rename to app/components/List/Placeholder.tsx index 4dc72b9a55..6b27616de2 100644 --- a/app/components/List/Placeholder.js +++ b/app/components/List/Placeholder.tsx @@ -1,13 +1,12 @@ -// @flow import { times } from "lodash"; import * as React from "react"; import styled from "styled-components"; -import Fade from "components/Fade"; -import Flex from "components/Flex"; -import PlaceholderText from "components/PlaceholderText"; +import Fade from "~/components/Fade"; +import Flex from "~/components/Flex"; +import PlaceholderText from "~/components/PlaceholderText"; type Props = { - count?: number, + count?: number; }; const ListPlaceHolder = ({ count }: Props) => { diff --git a/app/components/List/index.js b/app/components/List/index.ts similarity index 84% rename from app/components/List/index.js rename to app/components/List/index.ts index d31f1b0479..e5780f79f6 100644 --- a/app/components/List/index.js +++ b/app/components/List/index.ts @@ -1,3 +1,3 @@ -// @flow import List from "./List"; + export default List; diff --git a/app/components/LoadingIndicator/LoadingIndicator.js b/app/components/LoadingIndicator/LoadingIndicator.js deleted file mode 100644 index 3b25a80a83..0000000000 --- a/app/components/LoadingIndicator/LoadingIndicator.js +++ /dev/null @@ -1,25 +0,0 @@ -// @flow -import { inject, observer } from "mobx-react"; -import * as React from "react"; -import UiStore from "stores/UiStore"; - -type Props = { - ui: UiStore, -}; - -@observer -class LoadingIndicator extends React.Component { - componentDidMount() { - this.props.ui.enableProgressBar(); - } - - componentWillUnmount() { - this.props.ui.disableProgressBar(); - } - - render() { - return null; - } -} - -export default inject("ui")(LoadingIndicator); diff --git a/app/components/LoadingIndicator/LoadingIndicator.ts b/app/components/LoadingIndicator/LoadingIndicator.ts new file mode 100644 index 0000000000..b3fa21ead8 --- /dev/null +++ b/app/components/LoadingIndicator/LoadingIndicator.ts @@ -0,0 +1,16 @@ +import { observer } from "mobx-react"; +import * as React from "react"; +import useStores from "~/hooks/useStores"; + +function LoadingIndicator() { + const { ui } = useStores(); + + React.useEffect(() => { + ui.enableProgressBar(); + return () => ui.disableProgressBar(); + }, [ui]); + + return null; +} + +export default observer(LoadingIndicator); diff --git a/app/components/LoadingIndicator/LoadingIndicatorBar.js b/app/components/LoadingIndicator/LoadingIndicatorBar.tsx similarity index 98% rename from app/components/LoadingIndicator/LoadingIndicatorBar.js rename to app/components/LoadingIndicator/LoadingIndicatorBar.tsx index e8fe70d95d..93f14d3f3f 100644 --- a/app/components/LoadingIndicator/LoadingIndicatorBar.js +++ b/app/components/LoadingIndicator/LoadingIndicatorBar.tsx @@ -1,4 +1,3 @@ -// @flow import * as React from "react"; import styled, { keyframes } from "styled-components"; diff --git a/app/components/LoadingIndicator/index.js b/app/components/LoadingIndicator/index.ts similarity index 95% rename from app/components/LoadingIndicator/index.js rename to app/components/LoadingIndicator/index.ts index f90b3ecbe3..6f69ae96e7 100644 --- a/app/components/LoadingIndicator/index.js +++ b/app/components/LoadingIndicator/index.ts @@ -1,5 +1,6 @@ -// @flow import LoadingIndicator from "./LoadingIndicator"; import LoadingIndicatorBar from "./LoadingIndicatorBar"; + export default LoadingIndicator; + export { LoadingIndicatorBar }; diff --git a/app/components/LocaleTime.js b/app/components/LocaleTime.tsx similarity index 76% rename from app/components/LocaleTime.js rename to app/components/LocaleTime.tsx index eb2eeb2017..24f12d2f28 100644 --- a/app/components/LocaleTime.js +++ b/app/components/LocaleTime.tsx @@ -1,11 +1,10 @@ -// @flow import { format as formatDate, formatDistanceToNow } from "date-fns"; import * as React from "react"; -import Tooltip from "components/Tooltip"; -import useUserLocale from "hooks/useUserLocale"; -import { dateLocale } from "utils/i18n"; +import Tooltip from "~/components/Tooltip"; +import useUserLocale from "~/hooks/useUserLocale"; +import { dateLocale } from "~/utils/i18n"; -let callbacks = []; +let callbacks: (() => void)[] = []; // This is a shared timer that fires every minute, used for // updating all Time components across the page all at once. @@ -13,7 +12,7 @@ setInterval(() => { callbacks.forEach((cb) => cb()); }, 1000 * 60); -function eachMinute(fn) { +function eachMinute(fn: () => void) { callbacks.push(fn); return () => { @@ -22,13 +21,13 @@ function eachMinute(fn) { } type Props = { - dateTime: string, - children?: React.Node, - tooltipDelay?: number, - addSuffix?: boolean, - shorten?: boolean, - relative?: boolean, - format?: string, + dateTime: string; + children?: React.ReactNode; + tooltipDelay?: number; + addSuffix?: boolean; + shorten?: boolean; + relative?: boolean; + format?: string; }; function LocaleTime({ @@ -42,16 +41,16 @@ function LocaleTime({ }: Props) { const userLocale = useUserLocale(); const [_, setMinutesMounted] = React.useState(0); // eslint-disable-line no-unused-vars - const callback = React.useRef(); + + const callback = React.useRef<() => void>(); React.useEffect(() => { callback.current = eachMinute(() => { setMinutesMounted((state) => ++state); }); - return () => { if (callback.current) { - callback.current(); + callback.current?.(); } }; }, []); @@ -72,9 +71,10 @@ function LocaleTime({ const tooltipContent = formatDate( Date.parse(dateTime), format || "MMMM do, yyyy h:mm a", - { locale } + { + locale, + } ); - const content = children || relative !== false ? relativeContent : tooltipContent; diff --git a/app/components/MenuIconWrapper.js b/app/components/MenuIconWrapper.ts similarity index 95% rename from app/components/MenuIconWrapper.js rename to app/components/MenuIconWrapper.ts index 03b1cca1fc..a67cfb839a 100644 --- a/app/components/MenuIconWrapper.js +++ b/app/components/MenuIconWrapper.ts @@ -1,4 +1,3 @@ -// @flow import styled from "styled-components"; const MenuIconWrapper = styled.span` diff --git a/app/components/Modal.js b/app/components/Modal.tsx similarity index 83% rename from app/components/Modal.js rename to app/components/Modal.tsx index 13efd06ca6..31cbdeb560 100644 --- a/app/components/Modal.js +++ b/app/components/Modal.tsx @@ -1,4 +1,3 @@ -// @flow import { observer } from "mobx-react"; import { CloseIcon, BackIcon } from "outline-icons"; import { transparentize } from "polished"; @@ -7,30 +6,30 @@ import { useTranslation } from "react-i18next"; import { Dialog, DialogBackdrop, useDialogState } from "reakit/Dialog"; import styled from "styled-components"; import breakpoint from "styled-components-breakpoint"; -import Flex from "components/Flex"; -import NudeButton from "components/NudeButton"; -import Scrollable from "components/Scrollable"; -import usePrevious from "hooks/usePrevious"; -import useUnmount from "hooks/useUnmount"; -import { fadeAndScaleIn } from "styles/animations"; +import Flex from "~/components/Flex"; +import NudeButton from "~/components/NudeButton"; +import Scrollable from "~/components/Scrollable"; +import usePrevious from "~/hooks/usePrevious"; +import useUnmount from "~/hooks/useUnmount"; +import { fadeAndScaleIn } from "~/styles/animations"; let openModals = 0; - -type Props = {| - children?: React.Node, - isOpen: boolean, - title?: string, - onRequestClose: () => void, -|}; +type Props = { + children?: React.ReactNode; + isOpen: boolean; + title?: React.ReactNode; + onRequestClose: () => void; +}; const Modal = ({ children, isOpen, title = "Untitled", onRequestClose, - ...rest }: Props) => { - const dialog = useDialogState({ animated: 250 }); + const dialog = useDialogState({ + animated: 250, + }); const [depth, setDepth] = React.useState(0); const wasOpen = usePrevious(isOpen); const { t } = useTranslation(); @@ -40,6 +39,7 @@ const Modal = ({ setDepth(openModals++); dialog.show(); } + if (wasOpen && !isOpen) { setDepth(openModals--); dialog.hide(); @@ -60,17 +60,13 @@ const Modal = ({ {(props) => ( - + {(props) => ( @@ -112,7 +108,7 @@ const Backdrop = styled.div` } `; -const Scene = styled.div` +const Scene = styled.div<{ $nested: boolean }>` animation: ${fadeAndScaleIn} 250ms ease; position: absolute; @@ -129,7 +125,7 @@ const Scene = styled.div` outline: none; ${breakpoint("tablet")` - ${(props) => + ${(props: any) => props.$nested && ` box-shadow: 0 -2px 10px ${props.theme.shadow}; diff --git a/app/components/NavLink.js b/app/components/NavLink.js deleted file mode 100644 index 12dbc8f922..0000000000 --- a/app/components/NavLink.js +++ /dev/null @@ -1,26 +0,0 @@ -// @flow -import * as React from "react"; -import { NavLink, Route, type Match } from "react-router-dom"; - -type Props = { - children?: (match: Match) => React.Node, - exact?: boolean, - to: string, -}; - -export default function NavLinkWithChildrenFunc({ - to, - exact = false, - children, - ...rest -}: Props) { - return ( - - {({ match }) => ( - - {children ? children(match) : null} - - )} - - ); -} diff --git a/app/components/NavLink.tsx b/app/components/NavLink.tsx new file mode 100644 index 0000000000..6d8adc274b --- /dev/null +++ b/app/components/NavLink.tsx @@ -0,0 +1,28 @@ +import * as React from "react"; +import { NavLink, Route } from "react-router-dom"; + +type Props = React.ComponentProps & { + children?: (match: any) => React.ReactNode; + exact?: boolean; + activeStyle?: React.CSSProperties; + to: string; +}; + +function NavLinkWithChildrenFunc( + { to, exact = false, children, ...rest }: Props, + ref?: React.Ref +) { + return ( + + {({ match }) => ( + + {children ? children(match) : null} + + )} + + ); +} + +export default React.forwardRef( + NavLinkWithChildrenFunc +); diff --git a/app/components/Notice.js b/app/components/Notice.ts similarity index 96% rename from app/components/Notice.js rename to app/components/Notice.ts index 7bd8048073..a0a4d1d7d1 100644 --- a/app/components/Notice.js +++ b/app/components/Notice.ts @@ -1,4 +1,3 @@ -// @flow import styled from "styled-components"; const Notice = styled.p` diff --git a/app/components/NoticeAlert.js b/app/components/NoticeAlert.tsx similarity index 78% rename from app/components/NoticeAlert.js rename to app/components/NoticeAlert.tsx index c10b768d90..16e0a2b4cf 100644 --- a/app/components/NoticeAlert.js +++ b/app/components/NoticeAlert.tsx @@ -1,17 +1,24 @@ -// @flow import * as React from "react"; -import Notice from "components/Notice"; +import Notice from "~/components/Notice"; -export default function AlertNotice({ children }: { children: React.Node }) { +export default function AlertNotice({ + children, +}: { + children: React.ReactNode; +}) { return ( - + props.width || props.size}px; - height: ${(props) => props.height || props.size}px; - background: none; - border-radius: 4px; - line-height: 0; - border: 0; - padding: 0; - cursor: pointer; - user-select: none; - color: inherit; -`; - -export default React.forwardRef( - ({ size = 24, ...props }, ref) => -); diff --git a/app/components/NudeButton.tsx b/app/components/NudeButton.tsx new file mode 100644 index 0000000000..45d3bf0893 --- /dev/null +++ b/app/components/NudeButton.tsx @@ -0,0 +1,20 @@ +import styled from "styled-components"; + +const Button = styled.button<{ + width?: number; + height?: number; + size?: number; +}>` + width: ${(props) => props.width || props.size || 24}px; + height: ${(props) => props.height || props.size || 24}px; + background: none; + border-radius: 4px; + line-height: 0; + border: 0; + padding: 0; + cursor: pointer; + user-select: none; + color: inherit; +`; + +export default Button; diff --git a/app/components/OutlineLogo.js b/app/components/OutlineLogo.tsx similarity index 96% rename from app/components/OutlineLogo.js rename to app/components/OutlineLogo.tsx index 79602477b6..81eb485518 100644 --- a/app/components/OutlineLogo.js +++ b/app/components/OutlineLogo.tsx @@ -1,10 +1,9 @@ -// @flow import * as React from "react"; type Props = { - size?: number, - fill?: string, - className?: string, + size?: number; + fill?: string; + className?: string; }; function OutlineLogo({ size = 32, fill = "#333", className }: Props) { diff --git a/app/components/PageTheme.js b/app/components/PageTheme.ts similarity index 94% rename from app/components/PageTheme.js rename to app/components/PageTheme.ts index f0f47ebfb5..a6d2235604 100644 --- a/app/components/PageTheme.js +++ b/app/components/PageTheme.ts @@ -1,7 +1,6 @@ -// @flow import * as React from "react"; import { useTheme } from "styled-components"; -import useStores from "hooks/useStores"; +import useStores from "~/hooks/useStores"; export default function PageTheme() { const { ui } = useStores(); @@ -15,12 +14,14 @@ export default function PageTheme() { // theme-color adjusts the title bar color for desktop PWA const themeElement = document.querySelector('meta[name="theme-color"]'); + if (themeElement) { themeElement.setAttribute("content", theme.background); } // user-agent controls and scrollbars const csElement = document.querySelector('meta[name="color-scheme"]'); + if (csElement) { csElement.setAttribute("content", ui.resolvedTheme); } diff --git a/app/components/PageTitle.js b/app/components/PageTitle.tsx similarity index 82% rename from app/components/PageTitle.js rename to app/components/PageTitle.tsx index b33a0f1c4b..b4aaacaa06 100644 --- a/app/components/PageTitle.js +++ b/app/components/PageTitle.tsx @@ -1,14 +1,13 @@ -// @flow import { observer } from "mobx-react"; import * as React from "react"; import { Helmet } from "react-helmet"; -import { cdnPath } from "../../shared/utils/urls"; -import useStores from "hooks/useStores"; +import { cdnPath } from "@shared/utils/urls"; +import useStores from "~/hooks/useStores"; -type Props = {| - title: string, - favicon?: string, -|}; +type Props = { + title: React.ReactNode; + favicon?: string; +}; const PageTitle = ({ title, favicon }: Props) => { const { auth } = useStores(); diff --git a/app/components/PaginatedDocumentList.js b/app/components/PaginatedDocumentList.tsx similarity index 50% rename from app/components/PaginatedDocumentList.js rename to app/components/PaginatedDocumentList.tsx index 17234a1580..06f07b976d 100644 --- a/app/components/PaginatedDocumentList.js +++ b/app/components/PaginatedDocumentList.tsx @@ -1,22 +1,21 @@ -// @flow import * as React from "react"; -import Document from "models/Document"; -import DocumentListItem from "components/DocumentListItem"; -import PaginatedList from "components/PaginatedList"; +import Document from "~/models/Document"; +import DocumentListItem from "~/components/DocumentListItem"; +import PaginatedList from "~/components/PaginatedList"; -type Props = {| - documents: Document[], - fetch: (options: ?Object) => Promise, - options?: Object, - heading?: React.Node, - empty?: React.Node, - showNestedDocuments?: boolean, - showCollection?: boolean, - showPublished?: boolean, - showPin?: boolean, - showDraft?: boolean, - showTemplate?: boolean, -|}; +type Props = { + documents: Document[]; + fetch: (options: any) => Promise; + options?: Record; + heading?: React.ReactNode; + empty?: React.ReactNode; + showNestedDocuments?: boolean; + showCollection?: boolean; + showPublished?: boolean; + showPin?: boolean; + showDraft?: boolean; + showTemplate?: boolean; +}; const PaginatedDocumentList = React.memo(function PaginatedDocumentList({ empty, diff --git a/app/components/PaginatedEventList.js b/app/components/PaginatedEventList.tsx similarity index 69% rename from app/components/PaginatedEventList.js rename to app/components/PaginatedEventList.tsx index 5414c49c56..ff7af61a1e 100644 --- a/app/components/PaginatedEventList.js +++ b/app/components/PaginatedEventList.tsx @@ -1,20 +1,18 @@ -// @flow import * as React from "react"; import styled from "styled-components"; -import Document from "models/Document"; -import Event from "models/Event"; -import PaginatedList from "components/PaginatedList"; +import Document from "~/models/Document"; +import Event from "~/models/Event"; +import PaginatedList from "~/components/PaginatedList"; import EventListItem from "./EventListItem"; -type Props = {| - events: Event[], - document: Document, - fetch: (options: ?Object) => Promise, - options?: Object, - heading?: React.Node, - empty?: React.Node, -|}; - +type Props = { + events: Event[]; + document: Document; + fetch: (options: Record | null | undefined) => Promise; + options?: Record; + heading?: React.ReactNode; + empty?: React.ReactNode; +}; const PaginatedEventList = React.memo(function PaginatedEventList({ empty, heading, diff --git a/app/components/PaginatedList.test.js b/app/components/PaginatedList.test.tsx similarity index 74% rename from app/components/PaginatedList.test.js rename to app/components/PaginatedList.test.tsx index 22608e6a1e..e7081c3e73 100644 --- a/app/components/PaginatedList.test.js +++ b/app/components/PaginatedList.test.tsx @@ -1,17 +1,27 @@ -// @flow import "../stores"; import { shallow } from "enzyme"; import * as React from "react"; -import AuthStore from "stores/AuthStore"; -import { DEFAULT_PAGINATION_LIMIT } from "stores/BaseStore"; -import RootStore from "stores/RootStore"; -import { runAllPromises } from "../test/support"; +import { getI18n } from "react-i18next"; +import { DEFAULT_PAGINATION_LIMIT } from "~/stores/BaseStore"; +import RootStore from "~/stores/RootStore"; +import { runAllPromises } from "~/test/support"; import { Component as PaginatedList } from "./PaginatedList"; describe("PaginatedList", () => { const render = () => null; - const rootStore = new RootStore(); - const props = { auth: new AuthStore(rootStore), t: (string) => "test" }; + + const i18n = getI18n(); + const { logout, ...store } = new RootStore(); + + const props = { + i18n, + tReady: true, + t: (key: string) => key, + logout: () => { + // + }, + ...store, + }; it("with no items renders nothing", () => { const list = shallow( @@ -34,8 +44,9 @@ describe("PaginatedList", () => { it("calls fetch with options + pagination on mount", () => { const fetch = jest.fn(); - const options = { id: "one" }; - + const options = { + id: "one", + }; shallow( { }); it("calls fetch when options prop changes", async () => { + // @ts-expect-error ts-migrate(2554) FIXME: Expected 1-3 arguments, but got 0. const fetchedItems = Array(DEFAULT_PAGINATION_LIMIT).fill(); const fetch = jest.fn().mockReturnValue(Promise.resolve(fetchedItems)); - const list = shallow( ); - await runAllPromises(); - expect(fetch).toHaveBeenCalledWith({ id: "one", limit: DEFAULT_PAGINATION_LIMIT, offset: 0, }); - fetch.mockReset(); - list.setProps({ fetch, items: [], - options: { id: "two" }, + options: { + id: "two", + }, }); - await runAllPromises(); - expect(fetch).toHaveBeenCalledWith({ id: "two", limit: DEFAULT_PAGINATION_LIMIT, diff --git a/app/components/PaginatedList.js b/app/components/PaginatedList.tsx similarity index 78% rename from app/components/PaginatedList.js rename to app/components/PaginatedList.tsx index 3784f606ef..73f00e1e02 100644 --- a/app/components/PaginatedList.js +++ b/app/components/PaginatedList.tsx @@ -1,43 +1,50 @@ -// @flow import ArrowKeyNavigation from "boundless-arrow-key-navigation"; import { isEqual } from "lodash"; import { observable, action } from "mobx"; -import { observer, inject } from "mobx-react"; +import { observer } from "mobx-react"; import * as React from "react"; -import { withTranslation, type TFunction } from "react-i18next"; +import { withTranslation, WithTranslation } from "react-i18next"; import { Waypoint } from "react-waypoint"; -import AuthStore from "stores/AuthStore"; -import { DEFAULT_PAGINATION_LIMIT } from "stores/BaseStore"; -import DelayedMount from "components/DelayedMount"; -import PlaceholderList from "components/List/Placeholder"; -import { dateToHeading } from "utils/dates"; +import { DEFAULT_PAGINATION_LIMIT } from "~/stores/BaseStore"; +import RootStore from "~/stores/RootStore"; +import DelayedMount from "~/components/DelayedMount"; +import PlaceholderList from "~/components/List/Placeholder"; +import withStores from "~/components/withStores"; +import { dateToHeading } from "~/utils/dates"; -type Props = { - fetch?: (options: ?Object) => Promise, - options?: Object, - heading?: React.Node, - empty?: React.Node, - items: any[], - auth: AuthStore, - renderItem: (any, index: number) => React.Node, - renderHeading?: (name: React.Element | string) => React.Node, - t: TFunction, -}; +type Props = WithTranslation & + RootStore & { + fetch?: (options: Record | null | undefined) => Promise; + options?: Record; + heading?: React.ReactNode; + empty?: React.ReactNode; + + items: any[]; + renderItem: (arg0: any, index: number) => React.ReactNode; + renderHeading?: (name: React.ReactElement | string) => React.ReactNode; + }; @observer class PaginatedList extends React.Component { - isInitiallyLoaded: boolean = false; - @observable isLoaded: boolean = false; - @observable isFetchingMore: boolean = false; - @observable isFetching: boolean = false; - @observable renderCount: number = DEFAULT_PAGINATION_LIMIT; - @observable offset: number = 0; - @observable allowLoadMore: boolean = true; + isInitiallyLoaded = this.props.items.length > 0; - constructor(props: Props) { - super(props); - this.isInitiallyLoaded = this.props.items.length > 0; - } + @observable + isLoaded = false; + + @observable + isFetchingMore = false; + + @observable + isFetching = false; + + @observable + renderCount: number = DEFAULT_PAGINATION_LIMIT; + + @observable + offset = 0; + + @observable + allowLoadMore = true; componentDidMount() { this.fetchResults(); @@ -64,9 +71,7 @@ class PaginatedList extends React.Component { fetchResults = async () => { if (!this.props.fetch) return; - this.isFetching = true; - const limit = DEFAULT_PAGINATION_LIMIT; const results = await this.props.fetch({ limit, @@ -90,10 +95,10 @@ class PaginatedList extends React.Component { loadMoreResults = async () => { // Don't paginate if there aren't more results or we’re currently fetching if (!this.allowLoadMore || this.isFetching) return; - // If there are already cached results that we haven't yet rendered because // of lazy rendering then show another page. const leftToRender = this.props.items.length - this.renderCount; + if (leftToRender > 1) { this.renderCount += DEFAULT_PAGINATION_LIMIT; } @@ -108,14 +113,12 @@ class PaginatedList extends React.Component { render() { const { items, heading, auth, empty, renderHeading } = this.props; - let previousHeading = ""; const showLoading = this.isFetching && !this.isFetchingMore && !this.isInitiallyLoaded; const showEmpty = !items.length && !showLoading; const showList = (this.isLoaded || this.isInitiallyLoaded) && !showLoading && !showEmpty; - return ( <> {showEmpty && empty} @@ -150,7 +153,6 @@ class PaginatedList extends React.Component { // heading if (!previousHeading || currentHeading !== previousHeading) { previousHeading = currentHeading; - return ( {renderHeading(currentHeading)} @@ -179,4 +181,4 @@ class PaginatedList extends React.Component { export const Component = PaginatedList; -export default withTranslation()(inject("auth")(PaginatedList)); +export default withTranslation()(withStores(PaginatedList)); diff --git a/app/components/PathToDocument.js b/app/components/PathToDocument.tsx similarity index 70% rename from app/components/PathToDocument.js rename to app/components/PathToDocument.tsx index f9995e79f9..18cbf343bd 100644 --- a/app/components/PathToDocument.js +++ b/app/components/PathToDocument.tsx @@ -1,26 +1,25 @@ -// @flow import { observer } from "mobx-react"; import { GoToIcon } from "outline-icons"; import * as React from "react"; import styled from "styled-components"; -import type { DocumentPath } from "stores/CollectionsStore"; -import Collection from "models/Collection"; -import Document from "models/Document"; -import CollectionIcon from "components/CollectionIcon"; -import Flex from "components/Flex"; +import { DocumentPath } from "~/stores/CollectionsStore"; +import Collection from "~/models/Collection"; +import Document from "~/models/Document"; +import CollectionIcon from "~/components/CollectionIcon"; +import Flex from "~/components/Flex"; type Props = { - result: DocumentPath, - document?: ?Document, - collection: ?Collection, - onSuccess?: () => void, - style?: Object, - ref?: (?React.ElementRef<"div">) => void, + result: DocumentPath; + document?: Document | null | undefined; + collection: Collection | null | undefined; + onSuccess?: () => void; + style?: React.CSSProperties; + ref?: (arg0: React.ElementRef<"div"> | null | undefined) => void; }; @observer class PathToDocument extends React.Component { - handleClick = async (ev: SyntheticEvent<>) => { + handleClick = async (ev: React.SyntheticEvent) => { ev.preventDefault(); const { document, result, onSuccess } = this.props; if (!document) return; @@ -28,7 +27,7 @@ class PathToDocument extends React.Component { if (result.type === "document") { await document.move(result.collectionId, result.id); } else { - await document.move(result.collectionId, null); + await document.move(result.collectionId); } if (onSuccess) onSuccess(); @@ -37,10 +36,10 @@ class PathToDocument extends React.Component { render() { const { result, collection, document, ref, style } = this.props; const Component = document ? ResultWrapperLink : ResultWrapper; - if (!result) return ; return ( + // @ts-expect-error ts-migrate(2604) FIXME: JSX element type 'Component' does not have any con... Remove this comment to see the full error message { {result.path .map((doc) => {doc.title}) + // @ts-expect-error ts-migrate(2739) FIXME: Type 'Element[]' is missing the following properti... Remove this comment to see the full error message .reduce((prev, curr) => [prev, , curr])} {document && ( diff --git a/app/components/PlaceholderDocument.js b/app/components/PlaceholderDocument.tsx similarity index 79% rename from app/components/PlaceholderDocument.js rename to app/components/PlaceholderDocument.tsx index a99b76d5a4..33f7f68eeb 100644 --- a/app/components/PlaceholderDocument.js +++ b/app/components/PlaceholderDocument.tsx @@ -1,17 +1,16 @@ -// @flow import * as React from "react"; import styled from "styled-components"; -import DelayedMount from "components/DelayedMount"; -import Fade from "components/Fade"; -import Flex from "components/Flex"; -import PlaceholderText from "components/PlaceholderText"; +import DelayedMount from "~/components/DelayedMount"; +import Fade from "~/components/Fade"; +import Flex from "~/components/Flex"; +import PlaceholderText from "~/components/PlaceholderText"; export default function PlaceholderDocument({ includeTitle, delay, }: { - includeTitle?: boolean, - delay?: number, + includeTitle?: boolean; + delay?: number; }) { const content = ( <> diff --git a/app/components/PlaceholderText.js b/app/components/PlaceholderText.tsx similarity index 68% rename from app/components/PlaceholderText.js rename to app/components/PlaceholderText.tsx index 042eeed4f6..bf4d6201f2 100644 --- a/app/components/PlaceholderText.js +++ b/app/components/PlaceholderText.tsx @@ -1,17 +1,16 @@ -// @flow import * as React from "react"; import styled from "styled-components"; -import { randomInteger } from "shared/random"; -import Flex from "components/Flex"; -import { pulsate } from "styles/animations"; +import { randomInteger } from "@shared/random"; +import Flex from "~/components/Flex"; +import { pulsate } from "~/styles/animations"; -type Props = {| - header?: boolean, - height?: number, - minWidth?: number, - maxWidth?: number, - delay?: number, -|}; +type Props = { + header?: boolean; + height?: number; + minWidth?: number; + maxWidth?: number; + delay?: number; +}; class PlaceholderText extends React.Component { width = randomInteger(this.props.minWidth || 75, this.props.maxWidth || 100); @@ -26,12 +25,18 @@ class PlaceholderText extends React.Component { width={this.width} height={this.props.height} delay={this.props.delay} + header={this.props.header} /> ); } } -const Mask = styled(Flex)` +const Mask = styled(Flex)<{ + width: number; + height?: number; + delay?: number; + header?: boolean; +}>` width: ${(props) => (props.header ? props.width / 2 : props.width)}%; height: ${(props) => props.height ? props.height : props.header ? 24 : 18}px; diff --git a/app/components/Popover.js b/app/components/Popover.tsx similarity index 79% rename from app/components/Popover.js rename to app/components/Popover.tsx index 1555d2d226..d23ccbfbac 100644 --- a/app/components/Popover.js +++ b/app/components/Popover.tsx @@ -1,12 +1,12 @@ -// @flow import * as React from "react"; import { Popover as ReakitPopover } from "reakit/Popover"; import styled from "styled-components"; -import { fadeAndScaleIn } from "styles/animations"; +import { fadeAndScaleIn } from "~/styles/animations"; type Props = { - children: React.Node, - width?: number, + children: React.ReactNode; + tabIndex?: number; + width?: number; }; function Popover({ children, width = 380, ...rest }: Props) { @@ -17,7 +17,7 @@ function Popover({ children, width = 380, ...rest }: Props) { ); } -const Contents = styled.div` +const Contents = styled.div<{ width: number }>` animation: ${fadeAndScaleIn} 200ms ease; transform-origin: 75% 0; background: ${(props) => props.theme.menuBackground}; diff --git a/app/components/ProfiledRoute.js b/app/components/ProfiledRoute.ts similarity index 58% rename from app/components/ProfiledRoute.js rename to app/components/ProfiledRoute.ts index 2eef5bce35..f8321dbd9e 100644 --- a/app/components/ProfiledRoute.js +++ b/app/components/ProfiledRoute.ts @@ -1,12 +1,13 @@ -// @flow import * as Sentry from "@sentry/react"; import { Route } from "react-router-dom"; -import env from "env"; +import env from "~/env"; -let Component = Route; +let Component; if (env.SENTRY_DSN) { Component = Sentry.withSentryRouting(Route); +} else { + Component = Route; } -export default Component; +export default Component as typeof Route; diff --git a/app/components/RegisterKeyDown.js b/app/components/RegisterKeyDown.ts similarity index 71% rename from app/components/RegisterKeyDown.js rename to app/components/RegisterKeyDown.ts index b5dc651397..197616e848 100644 --- a/app/components/RegisterKeyDown.js +++ b/app/components/RegisterKeyDown.ts @@ -1,9 +1,8 @@ -// @flow -import useKeyDown, { type KeyFilter } from "hooks/useKeyDown"; +import useKeyDown, { KeyFilter } from "~/hooks/useKeyDown"; type Props = { - trigger: KeyFilter, - handler: (event: KeyboardEvent) => void, + trigger: KeyFilter; + handler: (event: KeyboardEvent) => void; }; /** diff --git a/app/components/Scene.js b/app/components/Scene.tsx similarity index 67% rename from app/components/Scene.js rename to app/components/Scene.tsx index 6c2c5247de..7758279342 100644 --- a/app/components/Scene.js +++ b/app/components/Scene.tsx @@ -1,19 +1,18 @@ -// @flow import * as React from "react"; import styled from "styled-components"; -import CenteredContent from "components/CenteredContent"; -import Header from "components/Header"; -import PageTitle from "components/PageTitle"; +import CenteredContent from "~/components/CenteredContent"; +import Header from "~/components/Header"; +import PageTitle from "~/components/PageTitle"; -type Props = {| - icon?: React.Node, - title: React.Node, - textTitle?: string, - children: React.Node, - breadcrumb?: React.Node, - actions?: React.Node, - centered?: boolean, -|}; +type Props = { + icon?: React.ReactNode; + title: React.ReactNode; + textTitle?: string; + children: React.ReactNode; + breadcrumb?: React.ReactNode; + actions?: React.ReactNode; + centered?: boolean; +}; function Scene({ title, diff --git a/app/components/ScrollToTop.js b/app/components/ScrollToTop.ts similarity index 88% rename from app/components/ScrollToTop.js rename to app/components/ScrollToTop.ts index 44e654b8df..9750a8f5e3 100644 --- a/app/components/ScrollToTop.js +++ b/app/components/ScrollToTop.ts @@ -1,12 +1,11 @@ -// @flow // based on: https://reacttraining.com/react-router/web/guides/scroll-restoration import * as React from "react"; import { useLocation } from "react-router-dom"; -import usePrevious from "hooks/usePrevious"; +import usePrevious from "~/hooks/usePrevious"; -type Props = {| - children: React.Node, -|}; +type Props = { + children: JSX.Element; +}; export default function ScrollToTop({ children }: Props) { const location = useLocation(); @@ -14,14 +13,12 @@ export default function ScrollToTop({ children }: Props) { React.useEffect(() => { if (location.pathname === previousLocationPathname) return; - // exception for when entering or exiting document edit, scroll position should not reset if ( location.pathname.match(/\/edit\/?$/) || previousLocationPathname?.match(/\/edit\/?$/) ) return; - window.scrollTo(0, 0); }, [location.pathname, previousLocationPathname]); diff --git a/app/components/Scrollable.js b/app/components/Scrollable.tsx similarity index 83% rename from app/components/Scrollable.js rename to app/components/Scrollable.tsx index e406109bdd..7ea9b136d7 100644 --- a/app/components/Scrollable.js +++ b/app/components/Scrollable.tsx @@ -1,31 +1,30 @@ -// @flow import { observer } from "mobx-react"; import * as React from "react"; import styled from "styled-components"; -import useWindowSize from "hooks/useWindowSize"; +import useWindowSize from "~/hooks/useWindowSize"; -type Props = {| - shadow?: boolean, - topShadow?: boolean, - bottomShadow?: boolean, - flex?: boolean, -|}; +type Props = { + shadow?: boolean; + topShadow?: boolean; + bottomShadow?: boolean; + flex?: boolean; + children: React.ReactNode; +}; function Scrollable( { shadow, topShadow, bottomShadow, flex, ...rest }: Props, - ref: any + ref: React.RefObject ) { - const fallbackRef = React.useRef(); + const fallbackRef = React.useRef(); const [topShadowVisible, setTopShadow] = React.useState(false); const [bottomShadowVisible, setBottomShadow] = React.useState(false); const { height } = useWindowSize(); - const updateShadows = React.useCallback(() => { const c = (ref || fallbackRef).current; if (!c) return; - const scrollTop = c.scrollTop; const tsv = !!((shadow || topShadow) && scrollTop > 0); + if (tsv !== topShadowVisible) { setTopShadow(tsv); } @@ -48,7 +47,6 @@ function Scrollable( React.useEffect(() => { updateShadows(); }, [height, updateShadows]); - return ( ` display: ${(props) => (props.$flex ? "flex" : "block")}; flex-direction: column; height: 100%; @@ -73,9 +75,11 @@ const Wrapper = styled.div` if (props.$topShadowVisible && props.$bottomShadowVisible) { return "0 1px inset rgba(0,0,0,.1), 0 -1px inset rgba(0,0,0,.1)"; } + if (props.$topShadowVisible) { return "0 1px inset rgba(0,0,0,.1)"; } + if (props.$bottomShadowVisible) { return "0 -1px inset rgba(0,0,0,.1)"; } diff --git a/app/components/Sidebar/Main.js b/app/components/Sidebar/Main.tsx similarity index 86% rename from app/components/Sidebar/Main.js rename to app/components/Sidebar/Main.tsx index 850f39dc7c..78fbeeabd5 100644 --- a/app/components/Sidebar/Main.js +++ b/app/components/Sidebar/Main.tsx @@ -1,4 +1,3 @@ -// @flow import { observer } from "mobx-react"; import { EditIcon, @@ -12,9 +11,21 @@ import { DndProvider } from "react-dnd"; import { HTML5Backend } from "react-dnd-html5-backend"; import { useTranslation } from "react-i18next"; import styled from "styled-components"; -import Bubble from "components/Bubble"; -import Flex from "components/Flex"; -import Scrollable from "components/Scrollable"; +import Bubble from "~/components/Bubble"; +import Flex from "~/components/Flex"; +import Scrollable from "~/components/Scrollable"; +import { inviteUser } from "~/actions/definitions/users"; +import useCurrentTeam from "~/hooks/useCurrentTeam"; +import useCurrentUser from "~/hooks/useCurrentUser"; +import useStores from "~/hooks/useStores"; +import AccountMenu from "~/menus/AccountMenu"; +import { + homePath, + searchUrl, + draftsPath, + templatesPath, + settingsPath, +} from "~/utils/routeHelpers"; import Sidebar from "./Sidebar"; import ArchiveLink from "./components/ArchiveLink"; import Collections from "./components/Collections"; @@ -24,18 +35,6 @@ import SidebarLink from "./components/SidebarLink"; import Starred from "./components/Starred"; import TeamButton from "./components/TeamButton"; import TrashLink from "./components/TrashLink"; -import { inviteUser } from "actions/definitions/users"; -import useCurrentTeam from "hooks/useCurrentTeam"; -import useCurrentUser from "hooks/useCurrentUser"; -import useStores from "hooks/useStores"; -import AccountMenu from "menus/AccountMenu"; -import { - homePath, - searchUrl, - draftsPath, - templatesPath, - settingsPath, -} from "utils/routeHelpers"; function MainSidebar() { const { t } = useTranslation(); @@ -50,10 +49,12 @@ function MainSidebar() { const [dndArea, setDndArea] = React.useState(); const handleSidebarRef = React.useCallback((node) => setDndArea(node), []); - const html5Options = React.useMemo(() => ({ rootElement: dndArea }), [ - dndArea, - ]); - + const html5Options = React.useMemo( + () => ({ + rootElement: dndArea, + }), + [dndArea] + ); const can = policies.abilities(team.id); return ( @@ -82,7 +83,9 @@ function MainSidebar() { } label={t("Search")} @@ -128,8 +131,8 @@ function MainSidebar() { : undefined } /> - - + + > )} ( - ({ children }: Props, ref) => { +const Sidebar = React.forwardRef( + ({ children }: Props, ref: React.RefObject) => { const [isCollapsing, setCollapsing] = React.useState(false); const theme = useTheme(); const { t } = useTranslation(); const { ui } = useStores(); const location = useLocation(); const previousLocation = usePrevious(location); - const width = ui.sidebarWidth; const collapsed = ui.isEditing || ui.sidebarCollapsed; const maxWidth = theme.sidebarMaxWidth; const minWidth = theme.sidebarMinWidth + 16; // padding - const setWidth = ui.setSidebarWidth; + const setWidth = ui.setSidebarWidth; const [offset, setOffset] = React.useState(0); const [isAnimating, setAnimating] = React.useState(false); const [isResizing, setResizing] = React.useState(false); @@ -45,7 +43,6 @@ const Sidebar = React.forwardRef( (event: MouseEvent) => { // suppresses text selection event.preventDefault(); - // this is simple because the sidebar is always against the left edge const width = Math.min(event.pageX - offset, maxWidth); const isSmallerThanCollapsePoint = width < minWidth / 2; @@ -59,34 +56,32 @@ const Sidebar = React.forwardRef( [theme, offset, minWidth, maxWidth, setWidth] ); - const handleStopDrag = React.useCallback( - (event: MouseEvent) => { - setResizing(false); + const handleStopDrag = React.useCallback(() => { + setResizing(false); - if (document.activeElement) { - document.activeElement.blur(); - } + if (document.activeElement) { + // @ts-expect-error ts-migrate(2339) FIXME: Property 'blur' does not exist on type 'Element'. + document.activeElement.blur(); + } - if (isSmallerThanMinimum) { - const isSmallerThanCollapsePoint = width < minWidth / 2; + if (isSmallerThanMinimum) { + const isSmallerThanCollapsePoint = width < minWidth / 2; - if (isSmallerThanCollapsePoint) { - setAnimating(false); - setCollapsing(true); - ui.collapseSidebar(); - } else { - setWidth(minWidth); - setAnimating(true); - } + if (isSmallerThanCollapsePoint) { + setAnimating(false); + setCollapsing(true); + ui.collapseSidebar(); } else { - setWidth(width); + setWidth(minWidth); + setAnimating(true); } - }, - [ui, isSmallerThanMinimum, minWidth, width, setWidth] - ); + } else { + setWidth(width); + } + }, [ui, isSmallerThanMinimum, minWidth, width, setWidth]); const handleMouseDown = React.useCallback( - (event: MouseEvent) => { + (event) => { setOffset(event.pageX - width); setResizing(true); setAnimating(false); @@ -162,7 +157,6 @@ const Sidebar = React.forwardRef( {ui.sidebarCollapsed && !ui.isEditing && ( ( props.theme.backdrop}; `; -const Container = styled(Flex)` +const Container = styled(Flex)<{ + $mobileSidebarVisible: boolean; + $isAnimating: boolean; + $isSmallerThanMinimum: boolean; + $collapsed: boolean; +}>` position: fixed; top: 0; bottom: 0; @@ -222,7 +219,7 @@ const Container = styled(Flex)` background: ${(props) => props.theme.sidebarBackground}; transition: box-shadow 100ms ease-in-out, transform 100ms ease-out, ${(props) => props.theme.backgroundTransition} - ${(props) => + ${(props: any) => props.$isAnimating ? `,width ${ANIMATION_MS}ms ease-out` : ""}; transform: translateX( ${(props) => (props.$mobileSidebarVisible ? 0 : "-100%")} @@ -243,13 +240,13 @@ const Container = styled(Flex)` ${breakpoint("tablet")` margin: 0; min-width: 0; - transform: translateX(${(props) => + transform: translateX(${(props: any) => props.$collapsed ? "calc(-100% + 16px)" : 0}); &:hover, &:focus-within { transform: none; - box-shadow: ${(props) => + box-shadow: ${(props: any) => props.$collapsed ? "rgba(0, 0, 0, 0.2) 1px 0 4px" : props.$isSmallerThanMinimum @@ -266,7 +263,7 @@ const Container = styled(Flex)` } &:not(:hover):not(:focus-within) > div { - opacity: ${(props) => (props.$collapsed ? "0" : "1")}; + opacity: ${(props: any) => (props.$collapsed ? "0" : "1")}; transition: opacity 100ms ease-in-out; } `}; diff --git a/app/components/Sidebar/components/ArchiveLink.js b/app/components/Sidebar/components/ArchiveLink.tsx similarity index 65% rename from app/components/Sidebar/components/ArchiveLink.js rename to app/components/Sidebar/components/ArchiveLink.tsx index 0cd35d6ca7..47c96a4a84 100644 --- a/app/components/Sidebar/components/ArchiveLink.js +++ b/app/components/Sidebar/components/ArchiveLink.tsx @@ -1,27 +1,28 @@ -// @flow import { observer } from "mobx-react"; import { ArchiveIcon } from "outline-icons"; import * as React from "react"; import { useDrop } from "react-dnd"; import { useTranslation } from "react-i18next"; -import SidebarLink from "./SidebarLink"; -import useStores from "hooks/useStores"; -import useToasts from "hooks/useToasts"; -import { archivePath } from "utils/routeHelpers"; +import useStores from "~/hooks/useStores"; +import useToasts from "~/hooks/useToasts"; +import { archivePath } from "~/utils/routeHelpers"; +import SidebarLink, { DragObject } from "./SidebarLink"; -function ArchiveLink({ documents }) { - const { policies } = useStores(); +function ArchiveLink() { + const { policies, documents } = useStores(); const { t } = useTranslation(); const { showToast } = useToasts(); const [{ isDocumentDropping }, dropToArchiveDocument] = useDrop({ accept: "document", - drop: async (item, monitor) => { + drop: async (item: DragObject) => { const document = documents.get(item.id); - await document.archive(); - showToast(t("Document archived"), { type: "success" }); + await document?.archive(); + showToast(t("Document archived"), { + type: "success", + }); }, - canDrop: (item, monitor) => policies.abilities(item.id).archive, + canDrop: (item) => policies.abilities(item.id).archive, collect: (monitor) => ({ isDocumentDropping: monitor.isOver(), }), diff --git a/app/components/Sidebar/components/CollectionLink.js b/app/components/Sidebar/components/CollectionLink.tsx similarity index 79% rename from app/components/Sidebar/components/CollectionLink.js rename to app/components/Sidebar/components/CollectionLink.tsx index 6ded54b0c6..7f8062a516 100644 --- a/app/components/Sidebar/components/CollectionLink.js +++ b/app/components/Sidebar/components/CollectionLink.tsx @@ -1,4 +1,3 @@ -// @flow import fractionalIndex from "fractional-index"; import { observer } from "mobx-react"; import * as React from "react"; @@ -6,30 +5,31 @@ import { useDrop, useDrag } from "react-dnd"; import { useTranslation } from "react-i18next"; import { useLocation, useHistory } from "react-router-dom"; import styled from "styled-components"; -import Collection from "models/Collection"; -import Document from "models/Document"; -import DocumentReparent from "scenes/DocumentReparent"; -import CollectionIcon from "components/CollectionIcon"; -import Modal from "components/Modal"; +import Collection from "~/models/Collection"; +import Document from "~/models/Document"; +import DocumentReparent from "~/scenes/DocumentReparent"; +import CollectionIcon from "~/components/CollectionIcon"; +import Modal from "~/components/Modal"; +import useBoolean from "~/hooks/useBoolean"; +import useStores from "~/hooks/useStores"; +import CollectionMenu from "~/menus/CollectionMenu"; +import CollectionSortMenu from "~/menus/CollectionSortMenu"; +import { NavigationNode } from "~/types"; import DocumentLink from "./DocumentLink"; import DropCursor from "./DropCursor"; import DropToImport from "./DropToImport"; import EditableTitle from "./EditableTitle"; -import SidebarLink from "./SidebarLink"; -import useBoolean from "hooks/useBoolean"; -import useStores from "hooks/useStores"; -import CollectionMenu from "menus/CollectionMenu"; -import CollectionSortMenu from "menus/CollectionSortMenu"; +import SidebarLink, { DragObject } from "./SidebarLink"; -type Props = {| - collection: Collection, - canUpdate: boolean, - activeDocument: ?Document, - prefetchDocument: (id: string) => Promise, - belowCollection: Collection | void, - isDraggingAnyCollection: boolean, - onChangeDragging: (dragging: boolean) => void, -|}; +type Props = { + collection: Collection; + canUpdate: boolean; + activeDocument: Document | null | undefined; + prefetchDocument: (id: string) => Promise; + belowCollection: Collection | void; + isDraggingAnyCollection: boolean; + onChangeDragging: (dragging: boolean) => void; +}; function CollectionLink({ collection, @@ -49,18 +49,21 @@ function CollectionLink({ handlePermissionOpen, handlePermissionClose, ] = useBoolean(); - const itemRef = React.useRef(); + const itemRef = React.useRef< + NavigationNode & { depth: number; active: boolean; collectionId: string } + >(); const handleTitleChange = React.useCallback( async (name: string) => { - await collection.save({ name }); + await collection.save({ + name, + }); history.push(collection.url); }, [collection, history] ); const { ui, documents, policies, collections } = useStores(); - const [expanded, setExpanded] = React.useState( collection.id === ui.activeCollectionId ); @@ -71,13 +74,13 @@ function CollectionLink({ if (search === "?starred") { return; } + if (isDraggingAnyCollection) { setExpanded(false); } else { setExpanded(collection.id === ui.activeCollectionId); } }, [isDraggingAnyCollection, collection.id, ui.activeCollectionId, search]); - const manualSort = collection.sort.field === "index"; const can = policies.abilities(collection.id); const belowCollectionIndex = belowCollection ? belowCollection.index : null; @@ -85,7 +88,7 @@ function CollectionLink({ // Drop to re-parent const [{ isOver, canDrop }, drop] = useDrop({ accept: "document", - drop: (item, monitor) => { + drop: (item: DragObject, monitor) => { const { id, collectionId } = item; if (monitor.didDrop()) return; if (!collection) return; @@ -103,11 +106,13 @@ function CollectionLink({ documents.move(id, collection.id); } }, - canDrop: (item, monitor) => { + canDrop: () => { return policies.abilities(collection.id).update; }, collect: (monitor) => ({ - isOver: !!monitor.isOver({ shallow: true }), + isOver: !!monitor.isOver({ + shallow: true, + }), canDrop: monitor.canDrop(), }), }); @@ -115,7 +120,7 @@ function CollectionLink({ // Drop to reorder const [{ isOverReorder }, dropToReorder] = useDrop({ accept: "document", - drop: async (item, monitor) => { + drop: async (item: DragObject) => { if (!collection) return; documents.move(item.id, collection.id, undefined, 0); }, @@ -127,13 +132,13 @@ function CollectionLink({ // Drop to reorder Collection const [{ isCollectionDropping }, dropToReorderCollection] = useDrop({ accept: "collection", - drop: async (item, monitor) => { + drop: async (item: DragObject) => { collections.move( item.id, fractionalIndex(collection.index, belowCollectionIndex) ); }, - canDrop: (item, monitor) => { + canDrop: (item) => { return ( collection.id !== item.id && (!belowCollection || item.id !== belowCollection.id) @@ -156,17 +161,22 @@ function CollectionLink({ collect: (monitor) => ({ isCollectionDragging: monitor.isDragging(), }), - canDrag: (monitor) => { + canDrag: () => { return can.move; }, - end: (monitor) => { + end: () => { onChangeDragging(false); }, }); return ( <> - + - + {itemRef.current && ( + + )} > ); } -const Draggable = styled("div")` +const Draggable = styled("div")<{ $isDragging: boolean; $isMoving: boolean }>` opacity: ${(props) => (props.$isDragging || props.$isMoving ? 0.5 : 1)}; pointer-events: ${(props) => (props.$isMoving ? "none" : "auto")}; `; diff --git a/app/components/Sidebar/components/Collections.js b/app/components/Sidebar/components/Collections.tsx similarity index 83% rename from app/components/Sidebar/components/Collections.js rename to app/components/Sidebar/components/Collections.tsx index 7fb64afdaa..c098d41d9a 100644 --- a/app/components/Sidebar/components/Collections.js +++ b/app/components/Sidebar/components/Collections.tsx @@ -1,4 +1,3 @@ -// @flow import fractionalIndex from "fractional-index"; import { observer } from "mobx-react"; import { CollapsedIcon } from "outline-icons"; @@ -6,24 +5,25 @@ import * as React from "react"; import { useDrop } from "react-dnd"; import { useTranslation } from "react-i18next"; import styled from "styled-components"; -import Fade from "components/Fade"; -import Flex from "components/Flex"; -import useStores from "../../../hooks/useStores"; +import Collection from "~/models/Collection"; +import Fade from "~/components/Fade"; +import Flex from "~/components/Flex"; +import { createCollection } from "~/actions/definitions/collections"; +import useStores from "~/hooks/useStores"; +import useToasts from "~/hooks/useToasts"; import CollectionLink from "./CollectionLink"; import DropCursor from "./DropCursor"; import PlaceholderCollections from "./PlaceholderCollections"; import SidebarAction from "./SidebarAction"; -import SidebarLink from "./SidebarLink"; -import { createCollection } from "actions/definitions/collections"; -import useToasts from "hooks/useToasts"; +import SidebarLink, { DragObject } from "./SidebarLink"; function Collections() { const [isFetching, setFetching] = React.useState(false); const [fetchError, setFetchError] = React.useState(); - const { ui, policies, documents, collections } = useStores(); + const { policies, documents, collections } = useStores(); const { showToast } = useToasts(); const [expanded, setExpanded] = React.useState(true); - const isPreloaded: boolean = !!collections.orderedData.length; + const isPreloaded = !!collections.orderedData.length; const { t } = useTranslation(); const orderedCollections = collections.orderedData; const [isDraggingAnyCollection, setIsDraggingAnyCollection] = React.useState( @@ -35,7 +35,9 @@ function Collections() { if (!collections.isLoaded && !isFetching && !fetchError) { try { setFetching(true); - await collections.fetchPage({ limit: 100 }); + await collections.fetchPage({ + limit: 100, + }); } catch (error) { showToast( t("Collections could not be loaded, please reload the app"), @@ -49,18 +51,19 @@ function Collections() { } } } + load(); }, [collections, isFetching, showToast, fetchError, t]); const [{ isCollectionDropping }, dropToReorderCollection] = useDrop({ accept: "collection", - drop: async (item, monitor) => { + drop: async (item: DragObject) => { collections.move( item.id, fractionalIndex(null, orderedCollections[0].index) ); }, - canDrop: (item, monitor) => { + canDrop: (item) => { return item.id !== orderedCollections[0].id; }, collect: (monitor) => ({ @@ -75,14 +78,13 @@ function Collections() { innerRef={dropToReorderCollection} from="collections" /> - {orderedCollections.map((collection, index) => ( + {orderedCollections.map((collection: Collection, index: number) => ( Promise, - depth: number, - index: number, - parentId?: string, -|}; +type Props = { + node: NavigationNode; + canUpdate: boolean; + collection?: Collection; + activeDocument: Document | null | undefined; + prefetchDocument: (documentId: string) => Promise; + depth: number; + index: number; + parentId?: string; +}; function DocumentLink( { @@ -40,14 +39,12 @@ function DocumentLink( index, parentId, }: Props, - ref + ref: React.RefObject ) { const { documents, policies } = useStores(); const { t } = useTranslation(); - const isActiveDocument = activeDocument && activeDocument.id === node.id; const hasChildDocuments = !!node.children.length; - const document = documents.get(node.id); const { fetchChildDocuments } = documents; @@ -75,7 +72,6 @@ function DocumentLink( isActiveDocument) ); }, [hasChildDocuments, activeDocument, isActiveDocument, node, collection]); - const [expanded, setExpanded] = React.useState(showChildren); React.useEffect(() => { @@ -93,7 +89,7 @@ function DocumentLink( }, [expanded, hasChildDocuments]); const handleDisclosureClick = React.useCallback( - (ev: SyntheticEvent<>) => { + (ev: React.SyntheticEvent) => { ev.preventDefault(); ev.stopPropagation(); setExpanded(!expanded); @@ -101,27 +97,26 @@ function DocumentLink( [expanded] ); - const handleMouseEnter = React.useCallback( - (ev: SyntheticEvent<>) => { - prefetchDocument(node.id); - }, - [prefetchDocument, node] - ); + const handleMouseEnter = React.useCallback(() => { + prefetchDocument(node.id); + }, [prefetchDocument, node]); const handleTitleChange = React.useCallback( async (title: string) => { if (!document) return; - - await documents.update({ - id: document.id, - lastRevision: document.revision, - text: document.text, - title, - }); + await documents.update( + { + id: document.id, + text: document.text, + title, + }, + { + lastRevision: document.revision, + } + ); }, [documents, document] ); - const [menuOpen, handleMenuOpen, handleMenuClose] = useBoolean(); const isMoving = documents.movingDocumentId === node.id; const manualSort = collection?.sort.field === "index"; @@ -138,7 +133,7 @@ function DocumentLink( collect: (monitor) => ({ isDragging: !!monitor.isDragging(), }), - canDrag: (monitor) => { + canDrag: () => { return ( policies.abilities(node.id).move || policies.abilities(node.id).archive || @@ -147,50 +142,56 @@ function DocumentLink( }, }); - const hoverExpanding = React.useRef(null); + const hoverExpanding = React.useRef>(); // We set a timeout when the user first starts hovering over the document link, // to trigger expansion of children. Clear this timeout when they stop hovering. const resetHoverExpanding = React.useCallback(() => { if (hoverExpanding.current) { clearTimeout(hoverExpanding.current); - hoverExpanding.current = null; + hoverExpanding.current = undefined; } }, []); // Drop to re-parent const [{ isOverReparent, canDropToReparent }, dropToReparent] = useDrop({ accept: "document", - drop: (item, monitor) => { + drop: (item: DragObject, monitor) => { if (monitor.didDrop()) return; if (!collection) return; documents.move(item.id, collection.id, node.id); }, - - canDrop: (item, monitor) => - pathToNode && !pathToNode.includes(monitor.getItem().id), - + canDrop: (_item, monitor) => + !!pathToNode && !pathToNode.includes(monitor.getItem().id), hover: (item, monitor) => { // Enables expansion of document children when hovering over the document // for more than half a second. if ( hasChildDocuments && monitor.canDrop() && - monitor.isOver({ shallow: true }) + monitor.isOver({ + shallow: true, + }) ) { if (!hoverExpanding.current) { hoverExpanding.current = setTimeout(() => { - hoverExpanding.current = null; - if (monitor.isOver({ shallow: true })) { + hoverExpanding.current = undefined; + + if ( + monitor.isOver({ + shallow: true, + }) + ) { setExpanded(true); } }, 500); } } }, - collect: (monitor) => ({ - isOverReparent: !!monitor.isOver({ shallow: true }), + isOverReparent: !!monitor.isOver({ + shallow: true, + }), canDropToReparent: monitor.canDrop(), }), }); @@ -198,7 +199,7 @@ function DocumentLink( // Drop to reorder const [{ isOverReorder }, dropToReorder] = useDrop({ accept: "document", - drop: (item, monitor) => { + drop: (item: DragObject) => { if (!collection) return; if (item.id === node.id) return; @@ -229,7 +230,9 @@ function DocumentLink( onMouseEnter={handleMouseEnter} to={{ pathname: node.url, - state: { title: node.title }, + state: { + title: node.title, + }, }} label={ <> @@ -247,6 +250,7 @@ function DocumentLink( /> > } + // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'match' implicitly has an 'any' type. isActive={(match, location) => match && location.search !== "?starred" } @@ -300,7 +304,7 @@ const Relative = styled.div` position: relative; `; -const Draggable = styled.div` +const Draggable = styled.div<{ $isDragging?: boolean; $isMoving?: boolean }>` opacity: ${(props) => (props.$isDragging || props.$isMoving ? 0.5 : 1)}; pointer-events: ${(props) => (props.$isMoving ? "none" : "all")}; `; diff --git a/app/components/Sidebar/components/DropCursor.js b/app/components/Sidebar/components/DropCursor.tsx similarity index 72% rename from app/components/Sidebar/components/DropCursor.js rename to app/components/Sidebar/components/DropCursor.tsx index 4e280d5d88..2495595965 100644 --- a/app/components/Sidebar/components/DropCursor.js +++ b/app/components/Sidebar/components/DropCursor.tsx @@ -1,24 +1,20 @@ -// @flow import * as React from "react"; -import styled, { withTheme } from "styled-components"; -import { type Theme } from "types"; +import styled from "styled-components"; function DropCursor({ isActiveDrop, innerRef, - theme, from, }: { - isActiveDrop: boolean, - innerRef: React.Ref, - theme: Theme, - from: string, + isActiveDrop: boolean; + innerRef: React.Ref; + from?: string; }) { return ; } // transparent hover zone with a thin visible band vertically centered -const Cursor = styled("div")` +const Cursor = styled.div<{ isOver?: boolean; from?: string }>` opacity: ${(props) => (props.isOver ? 1 : 0)}; transition: opacity 150ms; @@ -41,4 +37,4 @@ const Cursor = styled("div")` } `; -export default withTheme(DropCursor); +export default DropCursor; diff --git a/app/components/Sidebar/components/DropToImport.js b/app/components/Sidebar/components/DropToImport.tsx similarity index 66% rename from app/components/Sidebar/components/DropToImport.js rename to app/components/Sidebar/components/DropToImport.tsx index 1921ccb3d5..af1ca9464c 100644 --- a/app/components/Sidebar/components/DropToImport.js +++ b/app/components/Sidebar/components/DropToImport.tsx @@ -1,20 +1,21 @@ -// @flow +import invariant from "invariant"; import { observer } from "mobx-react"; import * as React from "react"; import Dropzone from "react-dropzone"; import { useTranslation } from "react-i18next"; import styled, { css } from "styled-components"; -import LoadingIndicator from "components/LoadingIndicator"; -import useImportDocument from "hooks/useImportDocument"; -import useStores from "hooks/useStores"; -import useToasts from "hooks/useToasts"; +import LoadingIndicator from "~/components/LoadingIndicator"; +import useImportDocument from "~/hooks/useImportDocument"; +import useStores from "~/hooks/useStores"; +import useToasts from "~/hooks/useToasts"; -type Props = {| - children: React.Node, - collectionId: string, - documentId?: string, - disabled: boolean, -|}; +type Props = { + children: JSX.Element; + collectionId?: string; + documentId?: string; + disabled?: boolean; + activeClassName?: string; +}; function DropToImport({ disabled, children, collectionId, documentId }: Props) { const { t } = useTranslation(); @@ -24,13 +25,16 @@ function DropToImport({ disabled, children, collectionId, documentId }: Props) { collectionId, documentId ); + const targetId = collectionId || documentId; + invariant(targetId, "Must provide either collectionId or documentId"); - const can = policies.abilities(collectionId); - + const can = policies.abilities(targetId); const handleRejection = React.useCallback(() => { showToast( t("Document not supported – try Markdown, Plain text, HTML, or Word"), - { type: "error" } + { + type: "error", + } ); }, [t, showToast]); @@ -46,17 +50,11 @@ function DropToImport({ disabled, children, collectionId, documentId }: Props) { noClick multiple > - {({ - getRootProps, - getInputProps, - isDragActive, - isDragAccept, - isDragReject, - }) => ( + {({ getRootProps, getInputProps, isDragActive }) => ( {isImporting && } @@ -67,7 +65,7 @@ function DropToImport({ disabled, children, collectionId, documentId }: Props) { ); } -const DropzoneContainer = styled.div` +const DropzoneContainer = styled.div<{ $isDragActive: boolean }>` border-radius: 4px; ${({ $isDragActive, theme }) => diff --git a/app/components/Sidebar/components/EditableTitle.js b/app/components/Sidebar/components/EditableTitle.tsx similarity index 93% rename from app/components/Sidebar/components/EditableTitle.js rename to app/components/Sidebar/components/EditableTitle.tsx index ffd6812388..67e51097d2 100644 --- a/app/components/Sidebar/components/EditableTitle.js +++ b/app/components/Sidebar/components/EditableTitle.tsx @@ -1,14 +1,13 @@ -// @flow import * as React from "react"; import styled from "styled-components"; -import useToasts from "hooks/useToasts"; +import useToasts from "~/hooks/useToasts"; -type Props = {| - onSubmit: (title: string) => Promise, - title: string, - canUpdate: boolean, - maxLength?: number, -|}; +type Props = { + onSubmit: (title: string) => Promise; + title: string; + canUpdate: boolean; + maxLength?: number; +}; function EditableTitle({ title, onSubmit, canUpdate, ...rest }: Props) { const [isEditing, setIsEditing] = React.useState(false); @@ -43,10 +42,9 @@ function EditableTitle({ title, onSubmit, canUpdate, ...rest }: Props) { const handleSave = React.useCallback( async (ev) => { ev.preventDefault(); - setIsEditing(false); - const trimmedValue = value.trim(); + if (trimmedValue === originalValue || trimmedValue.length === 0) { setValue(originalValue); return; diff --git a/app/components/Sidebar/components/Header.js b/app/components/Sidebar/components/Header.ts similarity index 86% rename from app/components/Sidebar/components/Header.js rename to app/components/Sidebar/components/Header.ts index e7f7274402..77b0b1f7ca 100644 --- a/app/components/Sidebar/components/Header.js +++ b/app/components/Sidebar/components/Header.ts @@ -1,6 +1,5 @@ -// @flow import styled from "styled-components"; -import Flex from "components/Flex"; +import Flex from "~/components/Flex"; const Header = styled(Flex)` font-size: 11px; diff --git a/app/components/Sidebar/components/NavLink.js b/app/components/Sidebar/components/NavLink.tsx similarity index 68% rename from app/components/Sidebar/components/NavLink.js rename to app/components/Sidebar/components/NavLink.tsx index 1411fa7ad3..53accf5e69 100644 --- a/app/components/Sidebar/components/NavLink.js +++ b/app/components/Sidebar/components/NavLink.tsx @@ -1,44 +1,43 @@ -// @flow // ref: https://github.com/ReactTraining/react-router/blob/master/packages/react-router-dom/modules/NavLink.js - // This file is pulled almost 100% from react-router with the addition of one // thing, automatic scroll to the active link. It's worth the copy paste because // it avoids recalculating the link match again. -import { createLocation } from "history"; +import { Location, createLocation } from "history"; import * as React from "react"; -import { - __RouterContext as RouterContext, - matchPath, - type Location, -} from "react-router"; +import { __RouterContext as RouterContext, matchPath } from "react-router"; import { Link } from "react-router-dom"; import scrollIntoView from "smooth-scroll-into-view-if-needed"; -const resolveToLocation = (to, currentLocation) => - typeof to === "function" ? to(currentLocation) : to; +const resolveToLocation = ( + to: string | Record, + currentLocation: Location +) => (typeof to === "function" ? to(currentLocation) : to); -const normalizeToLocation = (to, currentLocation) => { +const normalizeToLocation = ( + to: string | Record, + currentLocation: Location +) => { return typeof to === "string" - ? createLocation(to, null, null, currentLocation) + ? createLocation(to, null, undefined, currentLocation) : to; }; -const joinClassnames = (...classnames) => { +const joinClassnames = (...classnames: (string | undefined)[]) => { return classnames.filter((i) => i).join(" "); }; -export type Props = {| - activeClassName?: String, - activeStyle?: Object, - className?: string, - scrollIntoViewIfNeeded?: boolean, - exact?: boolean, - isActive?: any, - location?: Location, - strict?: boolean, - style?: Object, - to: string, -|}; +export type Props = React.HTMLAttributes & { + activeClassName?: string; + activeStyle?: React.CSSProperties; + className?: string; + scrollIntoViewIfNeeded?: boolean; + exact?: boolean; + isActive?: any; + location?: Location; + strict?: boolean; + style?: React.CSSProperties; + to: string | Record; +}; /** * A wrapper that knows if it's "active" or not. @@ -57,7 +56,7 @@ const NavLink = ({ to, ...rest }: Props) => { - const linkRef = React.useRef(); + const linkRef = React.useRef(null); const context = React.useContext(RouterContext); const currentLocation = locationProp || context.location; const toLocation = normalizeToLocation( @@ -65,9 +64,9 @@ const NavLink = ({ currentLocation ); const { pathname: path } = toLocation; + // Regex taken from: https://github.com/pillarjs/path-to-regexp/blob/master/index.js#L202 const escapedPath = path && path.replace(/([.+*?=^!:${}()[\]|/\\])/g, "\\$1"); - const match = escapedPath ? matchPath(currentLocation.pathname, { path: escapedPath, @@ -78,7 +77,6 @@ const NavLink = ({ const isActive = !!(isActiveProp ? isActiveProp(match, currentLocation) : match); - const className = isActive ? joinClassnames(classNameProp, activeClassName) : classNameProp; @@ -88,13 +86,13 @@ const NavLink = ({ if (isActive && linkRef.current && scrollIntoViewIfNeeded !== false) { scrollIntoView(linkRef.current, { scrollMode: "if-needed", - behavior: "instant", + behavior: "auto", }); } }, [linkRef, scrollIntoViewIfNeeded, isActive]); const props = { - "aria-current": (isActive && ariaCurrent) || null, + "aria-current": (isActive && ariaCurrent) || undefined, className, style, to: toLocation, diff --git a/app/components/Sidebar/components/PlaceholderCollections.js b/app/components/Sidebar/components/PlaceholderCollections.tsx similarity index 85% rename from app/components/Sidebar/components/PlaceholderCollections.js rename to app/components/Sidebar/components/PlaceholderCollections.tsx index 3efadb1fb6..340319e97a 100644 --- a/app/components/Sidebar/components/PlaceholderCollections.js +++ b/app/components/Sidebar/components/PlaceholderCollections.tsx @@ -1,7 +1,6 @@ -// @flow import * as React from "react"; import styled from "styled-components"; -import PlaceholderText from "components/PlaceholderText"; +import PlaceholderText from "~/components/PlaceholderText"; function PlaceholderCollections() { return ( diff --git a/app/components/Sidebar/components/ResizeBorder.js b/app/components/Sidebar/components/ResizeBorder.ts similarity index 95% rename from app/components/Sidebar/components/ResizeBorder.js rename to app/components/Sidebar/components/ResizeBorder.ts index fe39860295..067641dd49 100644 --- a/app/components/Sidebar/components/ResizeBorder.js +++ b/app/components/Sidebar/components/ResizeBorder.ts @@ -1,4 +1,3 @@ -// @flow import styled from "styled-components"; const ResizeBorder = styled.div` diff --git a/app/components/Sidebar/components/Section.js b/app/components/Sidebar/components/Section.ts similarity index 86% rename from app/components/Sidebar/components/Section.js rename to app/components/Sidebar/components/Section.ts index 7b1877d94f..64f4d3933d 100644 --- a/app/components/Sidebar/components/Section.js +++ b/app/components/Sidebar/components/Section.ts @@ -1,6 +1,5 @@ -// @flow import styled from "styled-components"; -import Flex from "components/Flex"; +import Flex from "~/components/Flex"; const Section = styled(Flex)` position: relative; diff --git a/app/components/Sidebar/components/SidebarAction.js b/app/components/Sidebar/components/SidebarAction.tsx similarity index 76% rename from app/components/Sidebar/components/SidebarAction.js rename to app/components/Sidebar/components/SidebarAction.tsx index 18768c1af1..e8c6046617 100644 --- a/app/components/Sidebar/components/SidebarAction.js +++ b/app/components/Sidebar/components/SidebarAction.tsx @@ -1,23 +1,22 @@ -// @flow import invariant from "invariant"; import { observer } from "mobx-react"; import * as React from "react"; import { useTranslation } from "react-i18next"; import { useLocation } from "react-router"; +import { actionToMenuItem } from "~/actions"; +import useStores from "~/hooks/useStores"; +import { Action } from "~/types"; import SidebarLink from "./SidebarLink"; -import { actionToMenuItem } from "actions"; -import useStores from "hooks/useStores"; -import type { Action } from "types"; -type Props = {| - action: Action, -|}; +type Props = { + action: Action; + depth?: number; +}; function SidebarAction({ action, ...rest }: Props) { const stores = useStores(); const { t } = useTranslation(); const location = useLocation(); - const context = { isContextMenu: false, isCommandBar: false, @@ -27,9 +26,8 @@ function SidebarAction({ action, ...rest }: Props) { stores, t, }; - const menuItem = actionToMenuItem(action, context); - invariant(menuItem.onClick, "passed action must have perform"); + invariant(menuItem.type === "button", "passed action must be a button"); return ( void, - onClick?: (SyntheticEvent<>) => mixed, - onMouseEnter?: (SyntheticEvent<>) => void, - children?: React.Node, - icon?: React.Node, - label?: React.Node, - menu?: React.Node, - showActions?: boolean, - active?: boolean, - isActiveDrop?: boolean, - depth?: number, - scrollIntoViewIfNeeded?: boolean, -|}; +export type DragObject = NavigationNode & { + depth: number; + active: boolean; + collectionId: string; +}; + +type Props = Omit & { + to?: string | Record; + href?: string | Record; + innerRef?: (arg0: HTMLElement | null | undefined) => void; + onClick?: React.MouseEventHandler; + onMouseEnter?: React.MouseEventHandler; + icon?: React.ReactNode; + label?: React.ReactNode; + menu?: React.ReactNode; + showActions?: boolean; + active?: boolean; + isActiveDrop?: boolean; + depth?: number; + scrollIntoViewIfNeeded?: boolean; +}; const activeDropStyle = { fontWeight: 600, @@ -31,7 +35,6 @@ const activeDropStyle = { function SidebarLink( { icon, - children, onClick, onMouseEnter, to, @@ -44,10 +47,9 @@ function SidebarLink( href, depth, className, - scrollIntoViewIfNeeded, ...rest }: Props, - ref + ref: React.RefObject ) { const theme = useTheme(); const style = React.useMemo( @@ -71,11 +73,11 @@ function SidebarLink( <> ` display: ${(props) => (props.showActions ? "inline-flex" : "none")}; position: absolute; top: 4px; @@ -124,7 +126,7 @@ const Actions = styled(EventBoundary)` } `; -const Link = styled(NavLink)` +const Link = styled(NavLink)<{ $isActiveDrop?: boolean }>` display: flex; position: relative; text-overflow: ellipsis; @@ -182,4 +184,4 @@ const Label = styled.div` } `; -export default React.forwardRef(SidebarLink); +export default React.forwardRef(SidebarLink); diff --git a/app/components/Sidebar/components/Starred.js b/app/components/Sidebar/components/Starred.tsx similarity index 87% rename from app/components/Sidebar/components/Starred.js rename to app/components/Sidebar/components/Starred.tsx index 778f81c9c3..64f768c3a8 100644 --- a/app/components/Sidebar/components/Starred.js +++ b/app/components/Sidebar/components/Starred.tsx @@ -1,17 +1,16 @@ -// @flow import { observer } from "mobx-react"; import { CollapsedIcon } from "outline-icons"; import * as React from "react"; import { useEffect } from "react"; import { useTranslation } from "react-i18next"; import styled from "styled-components"; -import Flex from "components/Flex"; +import Flex from "~/components/Flex"; +import useStores from "~/hooks/useStores"; +import useToasts from "~/hooks/useToasts"; import PlaceholderCollections from "./PlaceholderCollections"; import Section from "./Section"; import SidebarLink from "./SidebarLink"; import StarredLink from "./StarredLink"; -import useStores from "hooks/useStores"; -import useToasts from "hooks/useToasts"; const STARRED_PAGINATION_LIMIT = 10; const STARRED = "STARRED"; @@ -63,6 +62,7 @@ function Starred() { useEffect(() => { setOffset(starred.length); + if (starred.length <= STARRED_PAGINATION_LIMIT) { setShow("Nothing"); } else if (starred.length >= upperBound) { @@ -78,17 +78,14 @@ function Starred() { } }, [fetchResults, offset]); - const handleShowMore = React.useCallback( - async (ev) => { - setUpperBound( - (previousUpperBound) => previousUpperBound + STARRED_PAGINATION_LIMIT - ); - await fetchResults(); - }, - [fetchResults] - ); + const handleShowMore = React.useCallback(async () => { + setUpperBound( + (previousUpperBound) => previousUpperBound + STARRED_PAGINATION_LIMIT + ); + await fetchResults(); + }, [fetchResults]); - const handleShowLess = React.useCallback((ev) => { + const handleShowLess = React.useCallback(() => { setUpperBound(STARRED_PAGINATION_LIMIT); setShow("More"); }, []); @@ -103,12 +100,13 @@ function Starred() { } catch (_) { // no-op Safari private mode } + setExpanded((prev) => !prev); }, [expanded] ); - const content = starred.slice(0, upperBound).map((document, index) => { + const content = starred.slice(0, upperBound).map((document) => { return ( ); @@ -151,7 +148,7 @@ function Starred() { depth={2} /> )} - {(isFetching || fetchError) && ( + {(isFetching || fetchError) && !starred.length && ( diff --git a/app/components/Sidebar/components/StarredLink.js b/app/components/Sidebar/components/StarredLink.tsx similarity index 76% rename from app/components/Sidebar/components/StarredLink.js rename to app/components/Sidebar/components/StarredLink.tsx index 88292c438b..5d9dff02ac 100644 --- a/app/components/Sidebar/components/StarredLink.js +++ b/app/components/Sidebar/components/StarredLink.tsx @@ -1,25 +1,24 @@ -// @flow import { observer } from "mobx-react"; import * as React from "react"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import styled from "styled-components"; -import { MAX_TITLE_LENGTH } from "shared/constants"; -import Fade from "components/Fade"; -import useStores from "../../../hooks/useStores"; +import { MAX_TITLE_LENGTH } from "@shared/constants"; +import Fade from "~/components/Fade"; +import useBoolean from "~/hooks/useBoolean"; +import useStores from "~/hooks/useStores"; +import DocumentMenu from "~/menus/DocumentMenu"; import Disclosure from "./Disclosure"; import EditableTitle from "./EditableTitle"; import SidebarLink from "./SidebarLink"; -import useBoolean from "hooks/useBoolean"; -import DocumentMenu from "menus/DocumentMenu"; -type Props = {| - depth: number, - title: string, - to: string, - documentId: string, - collectionId: string, -|}; +type Props = { + depth: number; + title: string; + to: string; + documentId: string; + collectionId: string; +}; function StarredLink({ depth, title, to, documentId, collectionId }: Props) { const { t } = useTranslation(); @@ -29,11 +28,9 @@ function StarredLink({ depth, title, to, documentId, collectionId }: Props) { const [expanded, setExpanded] = useState(false); const [menuOpen, handleMenuOpen, handleMenuClose] = useBoolean(); const canUpdate = policies.abilities(documentId).update; - const childDocuments = collection ? collection.getDocumentChildren(documentId) : []; - const hasChildDocuments = childDocuments.length > 0; useEffect(() => { @@ -42,25 +39,32 @@ function StarredLink({ depth, title, to, documentId, collectionId }: Props) { await documents.fetch(documentId); } } + load(); }, [collection, collectionId, collections, document, documentId, documents]); - const handleDisclosureClick = React.useCallback((ev: SyntheticEvent<>) => { - ev.preventDefault(); - ev.stopPropagation(); - setExpanded((prevExpanded) => !prevExpanded); - }, []); + const handleDisclosureClick = React.useCallback( + (ev: React.MouseEvent) => { + ev.preventDefault(); + ev.stopPropagation(); + setExpanded((prevExpanded) => !prevExpanded); + }, + [] + ); const handleTitleChange = React.useCallback( async (title: string) => { if (!document) return; - - await documents.update({ - id: document.id, - lastRevision: document.revision, - text: document.text, - title, - }); + await documents.update( + { + id: document.id, + text: document.text, + title, + }, + { + lastRevision: document.revision, + } + ); }, [documents, document] ); @@ -71,6 +75,7 @@ function StarredLink({ depth, title, to, documentId, collectionId }: Props) { match && location.search === "?starred" } diff --git a/app/components/Sidebar/components/TeamButton.js b/app/components/Sidebar/components/TeamButton.tsx similarity index 80% rename from app/components/Sidebar/components/TeamButton.js rename to app/components/Sidebar/components/TeamButton.tsx index 964187632e..b00b68147e 100644 --- a/app/components/Sidebar/components/TeamButton.js +++ b/app/components/Sidebar/components/TeamButton.tsx @@ -1,23 +1,22 @@ -// @flow import { observer } from "mobx-react"; import { ExpandedIcon } from "outline-icons"; import * as React from "react"; import styled from "styled-components"; -import Flex from "components/Flex"; -import TeamLogo from "components/TeamLogo"; +import Flex from "~/components/Flex"; +import TeamLogo from "~/components/TeamLogo"; -type Props = {| - teamName: string, - subheading: React.Node, - showDisclosure?: boolean, - onClick: (event: SyntheticEvent<>) => void, - logoUrl: string, -|}; +type Props = { + teamName: string; + subheading: React.ReactNode; + showDisclosure?: boolean; + onClick: React.MouseEventHandler; + logoUrl: string; +}; -const TeamButton = React.forwardRef( +const TeamButton = React.forwardRef( ({ showDisclosure, teamName, subheading, logoUrl, ...rest }: Props, ref) => ( - + ( height={38} /> - + {teamName} {showDisclosure && } {subheading} diff --git a/app/components/Sidebar/components/Toggle.js b/app/components/Sidebar/components/Toggle.tsx similarity index 83% rename from app/components/Sidebar/components/Toggle.js rename to app/components/Sidebar/components/Toggle.tsx index 45cf36905d..a45bc5cd35 100644 --- a/app/components/Sidebar/components/Toggle.js +++ b/app/components/Sidebar/components/Toggle.tsx @@ -1,20 +1,18 @@ -// @flow import * as React from "react"; import { useTranslation } from "react-i18next"; import styled from "styled-components"; import breakpoint from "styled-components-breakpoint"; -import Arrow from "components/Arrow"; +import Arrow from "~/components/Arrow"; type Props = { - direction: "left" | "right", - style?: Object, - onClick?: () => any, + direction: "left" | "right"; + style?: React.CSSProperties; + onClick?: () => any; }; -const Toggle = React.forwardRef( +const Toggle = React.forwardRef( ({ direction = "left", onClick, style }: Props, ref) => { const { t } = useTranslation(); - return (