diff --git a/app/components/Analytics.js b/app/components/Analytics.js index 55d8facd..ebcbb7aa 100644 --- a/app/components/Analytics.js +++ b/app/components/Analytics.js @@ -2,7 +2,11 @@ /* global ga */ import * as React from 'react'; -export default class Analytics extends React.Component<*> { +type Props = { + children?: React.Node, +}; + +export default class Analytics extends React.Component { componentDidMount() { if (!process.env.GOOGLE_ANALYTICS_ID) return; diff --git a/app/components/ColorPicker.js b/app/components/ColorPicker.js index 6b4f080c..65d33e8d 100644 --- a/app/components/ColorPicker.js +++ b/app/components/ColorPicker.js @@ -66,14 +66,14 @@ class ColorPicker extends React.Component { }; @action - focusOnCustomColor = (event: SyntheticEvent<*>) => { + focusOnCustomColor = (event: SyntheticEvent<>) => { this.selectedColor = ''; this.customColorSelected = true; this.fireCallback(); }; @action - setCustomColor = (event: SyntheticEvent<*>) => { + setCustomColor = (event: SyntheticEvent) => { let target = event.target; if (target instanceof HTMLInputElement) { const color = target.value; diff --git a/app/components/CopyToClipboard.js b/app/components/CopyToClipboard.js index cf762a04..88ba33f5 100644 --- a/app/components/CopyToClipboard.js +++ b/app/components/CopyToClipboard.js @@ -5,12 +5,12 @@ import copy from 'copy-to-clipboard'; type Props = { text: string, children?: React.Node, - onClick?: () => *, - onCopy: () => *, + onClick?: () => void, + onCopy: () => void, }; class CopyToClipboard extends React.PureComponent { - onClick = (ev: SyntheticEvent<*>) => { + onClick = (ev: SyntheticEvent<>) => { const { text, onCopy, children } = this.props; const elem = React.Children.only(children); copy(text, { diff --git a/app/components/DocumentHistory/DocumentHistory.js b/app/components/DocumentHistory/DocumentHistory.js index 8bb0e23a..4e447c99 100644 --- a/app/components/DocumentHistory/DocumentHistory.js +++ b/app/components/DocumentHistory/DocumentHistory.js @@ -2,6 +2,7 @@ import * as React from 'react'; import { observable, action } from 'mobx'; import { observer, inject } from 'mobx-react'; +import type { RouterHistory } from 'react-router-dom'; import styled from 'styled-components'; import Waypoint from 'react-waypoint'; import ArrowKeyNavigation from 'boundless-arrow-key-navigation'; @@ -20,7 +21,7 @@ type Props = { match: Object, documents: DocumentsStore, revisions: RevisionsStore, - history: Object, + history: RouterHistory, }; @observer diff --git a/app/components/DocumentHistory/components/Revision.js b/app/components/DocumentHistory/components/Revision.js index c1a718e6..3bca0ae1 100644 --- a/app/components/DocumentHistory/components/Revision.js +++ b/app/components/DocumentHistory/components/Revision.js @@ -9,10 +9,19 @@ import Flex from 'shared/components/Flex'; import Time from 'shared/components/Time'; import Avatar from 'components/Avatar'; import RevisionMenu from 'menus/RevisionMenu'; +import Document from 'models/Document'; +import Revision from 'models/Revision'; import { documentHistoryUrl } from 'utils/routeHelpers'; -class Revision extends React.Component<*> { +type Props = { + theme: Object, + showMenu: () => void, + document: Document, + revision: Revision, +}; + +class RevisionListItem extends React.Component { render() { const { revision, document, showMenu, theme } = this.props; @@ -74,4 +83,4 @@ const Meta = styled.p` padding: 0; `; -export default withTheme(Revision); +export default withTheme(RevisionListItem); diff --git a/app/components/DocumentPreview/DocumentPreview.js b/app/components/DocumentPreview/DocumentPreview.js index 370c51ce..84c7a9e8 100644 --- a/app/components/DocumentPreview/DocumentPreview.js +++ b/app/components/DocumentPreview/DocumentPreview.js @@ -17,7 +17,6 @@ type Props = { showCollection?: boolean, showPublished?: boolean, showPin?: boolean, - ref?: *, }; const StyledStar = withTheme(styled(({ solid, theme, ...props }) => ( @@ -107,13 +106,13 @@ const SEARCH_RESULT_REGEX = /]*>(.*?)<\/b>/gi; @observer class DocumentPreview extends React.Component { - star = (ev: SyntheticEvent<*>) => { + star = (ev: SyntheticEvent<>) => { ev.preventDefault(); ev.stopPropagation(); this.props.document.star(); }; - unstar = (ev: SyntheticEvent<*>) => { + unstar = (ev: SyntheticEvent<>) => { ev.preventDefault(); ev.stopPropagation(); this.props.document.unstar(); diff --git a/app/components/DropToImport.js b/app/components/DropToImport.js index 94e116be..10b5a30f 100644 --- a/app/components/DropToImport.js +++ b/app/components/DropToImport.js @@ -2,7 +2,7 @@ import * as React from 'react'; import { observable } from 'mobx'; import { observer, inject } from 'mobx-react'; -import { withRouter } from 'react-router-dom'; +import { withRouter, type RouterHistory } from 'react-router-dom'; import { createGlobalStyle } from 'styled-components'; import invariant from 'invariant'; import importFile from 'utils/importFile'; @@ -22,7 +22,7 @@ type Props = { disabled: boolean, location: Object, match: Object, - history: Object, + history: RouterHistory, staticContext: Object, }; diff --git a/app/components/DropdownMenu/DropdownMenu.js b/app/components/DropdownMenu/DropdownMenu.js index c49ee806..139b17dd 100644 --- a/app/components/DropdownMenu/DropdownMenu.js +++ b/app/components/DropdownMenu/DropdownMenu.js @@ -31,10 +31,10 @@ class DropdownMenu extends React.Component { @observable left: number; handleOpen = ( - openPortal: (SyntheticEvent<*>) => void, + openPortal: (SyntheticEvent<>) => void, closePortal: () => void ) => { - return (ev: SyntheticMouseEvent<*>) => { + return (ev: SyntheticMouseEvent) => { ev.preventDefault(); const currentTarget = ev.currentTarget; invariant(document.body, 'why you not here'); diff --git a/app/components/DropdownMenu/DropdownMenuItem.js b/app/components/DropdownMenu/DropdownMenuItem.js index 31036ab4..0ea5e5a5 100644 --- a/app/components/DropdownMenu/DropdownMenuItem.js +++ b/app/components/DropdownMenu/DropdownMenuItem.js @@ -3,7 +3,7 @@ import * as React from 'react'; import styled from 'styled-components'; type Props = { - onClick?: (SyntheticEvent<*>) => *, + onClick?: (SyntheticEvent<>) => void | Promise, children?: React.Node, disabled?: boolean, }; diff --git a/app/components/Editor/Editor.js b/app/components/Editor/Editor.js index 69168d85..48c474a9 100644 --- a/app/components/Editor/Editor.js +++ b/app/components/Editor/Editor.js @@ -10,6 +10,7 @@ import Placeholder from 'rich-markdown-editor/lib/components/Placeholder'; import { uploadFile } from 'utils/uploadFile'; import isInternalUrl from 'utils/isInternalUrl'; import Tooltip from 'components/Tooltip'; +import UiStore from 'stores/UiStore'; import Embed from './Embed'; import embeds from '../../embeds'; @@ -17,8 +18,8 @@ type Props = { defaultValue?: string, readOnly?: boolean, disableEmbeds?: boolean, - forwardedRef: *, - ui: *, + forwardedRef: React.Ref, + ui: UiStore, }; @observer diff --git a/app/components/InputRich.js b/app/components/InputRich.js index 861e75cc..82b91c4c 100644 --- a/app/components/InputRich.js +++ b/app/components/InputRich.js @@ -14,7 +14,7 @@ type Props = { @observer class InputRich extends React.Component { - @observable editorComponent: *; + @observable editorComponent: React.ComponentType; @observable focused: boolean = false; componentDidMount() { diff --git a/app/components/LoadingIndicator/LoadingIndicator.js b/app/components/LoadingIndicator/LoadingIndicator.js index c8d5d227..d191452c 100644 --- a/app/components/LoadingIndicator/LoadingIndicator.js +++ b/app/components/LoadingIndicator/LoadingIndicator.js @@ -1,9 +1,14 @@ // @flow import * as React from 'react'; import { inject, observer } from 'mobx-react'; +import UiStore from 'stores/UiStore'; + +type Props = { + ui: UiStore, +}; @observer -class LoadingIndicator extends React.Component<*> { +class LoadingIndicator extends React.Component { componentDidMount() { this.props.ui.enableProgressBar(); } diff --git a/app/components/Mask.js b/app/components/Mask.js index d557499b..86d5c3a6 100644 --- a/app/components/Mask.js +++ b/app/components/Mask.js @@ -5,7 +5,12 @@ import { pulsate } from 'shared/styles/animations'; import { randomInteger } from 'shared/random'; import Flex from 'shared/components/Flex'; -class Mask extends React.Component<*> { +type Props = { + header?: boolean, + height?: number, +}; + +class Mask extends React.Component { width: number; shouldComponentUpdate() { diff --git a/app/components/Modal.js b/app/components/Modal.js index 1f696d3f..28a7ec8f 100644 --- a/app/components/Modal.js +++ b/app/components/Modal.js @@ -15,7 +15,7 @@ type Props = { children?: React.Node, isOpen: boolean, title?: string, - onRequestClose: () => *, + onRequestClose: () => void, }; const GlobalStyles = createGlobalStyle` diff --git a/app/components/PaginatedDocumentList.js b/app/components/PaginatedDocumentList.js index 955d1f93..907e4e40 100644 --- a/app/components/PaginatedDocumentList.js +++ b/app/components/PaginatedDocumentList.js @@ -11,7 +11,7 @@ import { ListPlaceholder } from 'components/LoadingPlaceholder'; type Props = { documents: Document[], - fetch: (options: ?Object) => Promise<*>, + fetch: (options: ?Object) => Promise, options?: Object, heading?: React.Node, empty?: React.Node, diff --git a/app/components/PathToDocument.js b/app/components/PathToDocument.js index bf62a2a6..e839638a 100644 --- a/app/components/PathToDocument.js +++ b/app/components/PathToDocument.js @@ -14,12 +14,12 @@ type Props = { document?: ?Document, collection: ?Collection, onSuccess?: () => void, - ref?: *, + ref?: (?React.ElementRef<'div'>) => void, }; @observer class PathToDocument extends React.Component { - handleClick = async (ev: SyntheticEvent<*>) => { + handleClick = async (ev: SyntheticEvent<>) => { ev.preventDefault(); const { document, result, onSuccess } = this.props; if (!document) return; diff --git a/app/components/RouteSidebarHidden.js b/app/components/RouteSidebarHidden.js index b80c57af..d5d0f144 100644 --- a/app/components/RouteSidebarHidden.js +++ b/app/components/RouteSidebarHidden.js @@ -6,7 +6,7 @@ import UiStore from 'stores/UiStore'; type Props = { ui: UiStore, - component: *, + component: React.ComponentType, }; class RouteSidebarHidden extends React.Component { diff --git a/app/components/ScrollToTop.js b/app/components/ScrollToTop.js index a877045b..9b9cea20 100644 --- a/app/components/ScrollToTop.js +++ b/app/components/ScrollToTop.js @@ -2,8 +2,14 @@ // based on: https://reacttraining.com/react-router/web/guides/scroll-restoration import * as React from 'react'; import { withRouter } from 'react-router-dom'; +import type { Location } from 'react-router-dom'; -class ScrollToTop extends React.Component<*> { +type Props = { + location: Location, + children: React.Node, +}; + +class ScrollToTop extends React.Component { componentDidUpdate(prevProps) { if (this.props.location.pathname === prevProps.location.pathname) return; diff --git a/app/components/Scrollable.js b/app/components/Scrollable.js index 46df2e6d..7a145948 100644 --- a/app/components/Scrollable.js +++ b/app/components/Scrollable.js @@ -12,7 +12,7 @@ type Props = { class Scrollable extends React.Component { @observable shadow: boolean = false; - handleScroll = (ev: SyntheticMouseEvent<*>) => { + handleScroll = (ev: SyntheticMouseEvent) => { this.shadow = !!(this.props.shadow && ev.currentTarget.scrollTop > 0); }; diff --git a/app/components/Sidebar/Settings.js b/app/components/Sidebar/Settings.js index 03a48eba..37a8af6c 100644 --- a/app/components/Sidebar/Settings.js +++ b/app/components/Sidebar/Settings.js @@ -1,6 +1,7 @@ // @flow import * as React from 'react'; import { observer, inject } from 'mobx-react'; +import type { RouterHistory } from 'react-router-dom'; import { DocumentIcon, EmailIcon, @@ -25,7 +26,7 @@ import HeaderBlock from './components/HeaderBlock'; import AuthStore from 'stores/AuthStore'; type Props = { - history: Object, + history: RouterHistory, auth: AuthStore, }; diff --git a/app/components/Sidebar/components/CollectionLink.js b/app/components/Sidebar/components/CollectionLink.js index ec5b53bb..af689923 100644 --- a/app/components/Sidebar/components/CollectionLink.js +++ b/app/components/Sidebar/components/CollectionLink.js @@ -18,7 +18,7 @@ type Props = { ui: UiStore, documents: DocumentsStore, activeDocument: ?Document, - prefetchDocument: (id: string) => *, + prefetchDocument: (id: string) => Promise, }; @observer diff --git a/app/components/Sidebar/components/Collections.js b/app/components/Sidebar/components/Collections.js index 31630520..c54a6db4 100644 --- a/app/components/Sidebar/components/Collections.js +++ b/app/components/Sidebar/components/Collections.js @@ -1,7 +1,7 @@ // @flow import * as React from 'react'; import { observer, inject } from 'mobx-react'; -import { withRouter } from 'react-router-dom'; +import { withRouter, type RouterHistory } from 'react-router-dom'; import keydown from 'react-keydown'; import Flex from 'shared/components/Flex'; import { PlusIcon } from 'outline-icons'; @@ -17,7 +17,7 @@ import UiStore from 'stores/UiStore'; import DocumentsStore from 'stores/DocumentsStore'; type Props = { - history: Object, + history: RouterHistory, collections: CollectionsStore, documents: DocumentsStore, onCreateCollection: () => void, diff --git a/app/components/Sidebar/components/DocumentLink.js b/app/components/Sidebar/components/DocumentLink.js index a4484afe..3352ea5f 100644 --- a/app/components/Sidebar/components/DocumentLink.js +++ b/app/components/Sidebar/components/DocumentLink.js @@ -18,7 +18,7 @@ type Props = { documents: DocumentsStore, collection?: Collection, activeDocument: ?Document, - activeDocumentRef?: (?HTMLElement) => *, + activeDocumentRef?: (?HTMLElement) => void, prefetchDocument: (documentId: string) => Promise, depth: number, }; @@ -27,7 +27,7 @@ type Props = { class DocumentLink extends React.Component { @observable menuOpen = false; - handleMouseEnter = (ev: SyntheticEvent<*>) => { + handleMouseEnter = (ev: SyntheticEvent<>) => { const { node, prefetchDocument } = this.props; ev.stopPropagation(); diff --git a/app/components/Sidebar/components/SidebarLink.js b/app/components/Sidebar/components/SidebarLink.js index f10a6cbf..525282ff 100644 --- a/app/components/Sidebar/components/SidebarLink.js +++ b/app/components/Sidebar/components/SidebarLink.js @@ -9,7 +9,7 @@ import Flex from 'shared/components/Flex'; type Props = { to?: string | Object, - onClick?: (SyntheticEvent<*>) => *, + onClick?: (SyntheticEvent<>) => void, children?: React.Node, icon?: React.Node, expanded?: boolean, @@ -43,7 +43,7 @@ class SidebarLink extends React.Component { } @action - handleClick = (ev: SyntheticEvent<*>) => { + handleClick = (ev: SyntheticEvent<>) => { ev.preventDefault(); ev.stopPropagation(); this.expanded = !this.expanded; diff --git a/app/components/Sidebar/icons/Slack.js b/app/components/Sidebar/icons/Slack.js index edde2f84..be5c63e3 100644 --- a/app/components/Sidebar/icons/Slack.js +++ b/app/components/Sidebar/icons/Slack.js @@ -1,7 +1,7 @@ // @flow import * as React from 'react'; -export default function SlackIcon(props: *) { +export default function SlackIcon() { return ( diff --git a/app/components/Sidebar/icons/Zapier.js b/app/components/Sidebar/icons/Zapier.js index a772c745..0d52c3fe 100644 --- a/app/components/Sidebar/icons/Zapier.js +++ b/app/components/Sidebar/icons/Zapier.js @@ -1,7 +1,7 @@ // @flow import * as React from 'react'; -export default function ZapierIcon(props: *) { +export default function ZapierIcon() { return ( diff --git a/app/components/Tab.js b/app/components/Tab.js index d416a029..824b89ef 100644 --- a/app/components/Tab.js +++ b/app/components/Tab.js @@ -3,6 +3,10 @@ import * as React from 'react'; import styled, { withTheme } from 'styled-components'; import { NavLink } from 'react-router-dom'; +type Props = { + theme: Object, +}; + const StyledNavLink = styled(NavLink)` position: relative; top: 1px; @@ -21,7 +25,7 @@ const StyledNavLink = styled(NavLink)` } `; -function Tab(props: *) { +function Tab(props: Props) { const activeStyle = { paddingBottom: '5px', borderBottom: `3px solid ${props.theme.textSecondary}`, diff --git a/app/menus/CollectionMenu.js b/app/menus/CollectionMenu.js index c98c12c7..23c3d34a 100644 --- a/app/menus/CollectionMenu.js +++ b/app/menus/CollectionMenu.js @@ -2,7 +2,7 @@ import * as React from 'react'; import { observable } from 'mobx'; import { inject, observer } from 'mobx-react'; -import { withRouter } from 'react-router-dom'; +import { withRouter, type RouterHistory } from 'react-router-dom'; import styled from 'styled-components'; import { MoreIcon } from 'outline-icons'; import Modal from 'components/Modal'; @@ -22,7 +22,7 @@ type Props = { ui: UiStore, documents: DocumentsStore, collection: Collection, - history: Object, + history: RouterHistory, onOpen?: () => void, onClose?: () => void, }; @@ -33,20 +33,20 @@ class CollectionMenu extends React.Component { @observable permissionsModalOpen: boolean = false; @observable redirectTo: ?string; - onNewDocument = (ev: SyntheticEvent<*>) => { + onNewDocument = (ev: SyntheticEvent<>) => { ev.preventDefault(); const { collection } = this.props; this.props.history.push(newDocumentUrl(collection.id)); }; - onImportDocument = (ev: SyntheticEvent<*>) => { + onImportDocument = (ev: SyntheticEvent<>) => { ev.preventDefault(); // simulate a click on the file upload input element if (this.file) this.file.click(); }; - onFilePicked = async (ev: SyntheticEvent<*>) => { + onFilePicked = async (ev: SyntheticEvent<>) => { const files = getDataTransferFiles(ev); try { @@ -61,25 +61,25 @@ class CollectionMenu extends React.Component { } }; - onEdit = (ev: SyntheticEvent<*>) => { + onEdit = (ev: SyntheticEvent<>) => { ev.preventDefault(); const { collection } = this.props; this.props.ui.setActiveModal('collection-edit', { collection }); }; - onDelete = (ev: SyntheticEvent<*>) => { + onDelete = (ev: SyntheticEvent<>) => { ev.preventDefault(); const { collection } = this.props; this.props.ui.setActiveModal('collection-delete', { collection }); }; - onExport = (ev: SyntheticEvent<*>) => { + onExport = (ev: SyntheticEvent<>) => { ev.preventDefault(); const { collection } = this.props; this.props.ui.setActiveModal('collection-export', { collection }); }; - onPermissions = (ev: SyntheticEvent<*>) => { + onPermissions = (ev: SyntheticEvent<>) => { ev.preventDefault(); this.permissionsModalOpen = true; }; diff --git a/app/menus/DocumentMenu.js b/app/menus/DocumentMenu.js index fc5b957e..e2f7a609 100644 --- a/app/menus/DocumentMenu.js +++ b/app/menus/DocumentMenu.js @@ -40,12 +40,12 @@ class DocumentMenu extends React.Component { this.redirectTo = undefined; } - handleNewChild = (ev: SyntheticEvent<*>) => { + handleNewChild = (ev: SyntheticEvent<>) => { const { document } = this.props; this.redirectTo = newDocumentUrl(document.collectionId, document.id); }; - handleDelete = (ev: SyntheticEvent<*>) => { + handleDelete = (ev: SyntheticEvent<>) => { const { document } = this.props; this.props.ui.setActiveModal('document-delete', { document }); }; @@ -54,15 +54,15 @@ class DocumentMenu extends React.Component { this.redirectTo = documentHistoryUrl(this.props.document); }; - handleMove = (ev: SyntheticEvent<*>) => { + handleMove = (ev: SyntheticEvent<>) => { this.redirectTo = documentMoveUrl(this.props.document); }; - handleEdit = (ev: SyntheticEvent<*>) => { + handleEdit = (ev: SyntheticEvent<>) => { this.redirectTo = documentEditUrl(this.props.document); }; - handleDuplicate = async (ev: SyntheticEvent<*>) => { + handleDuplicate = async (ev: SyntheticEvent<>) => { const duped = await this.props.document.duplicate(); // when duplicating, go straight to the duplicated document content @@ -70,37 +70,37 @@ class DocumentMenu extends React.Component { this.props.ui.showToast('Document duplicated'); }; - handleArchive = async (ev: SyntheticEvent<*>) => { + handleArchive = async (ev: SyntheticEvent<>) => { await this.props.document.archive(); this.props.ui.showToast('Document archived'); }; - handleRestore = async (ev: SyntheticEvent<*>) => { + handleRestore = async (ev: SyntheticEvent<>) => { await this.props.document.restore(); this.props.ui.showToast('Document restored'); }; - handlePin = (ev: SyntheticEvent<*>) => { + handlePin = (ev: SyntheticEvent<>) => { this.props.document.pin(); }; - handleUnpin = (ev: SyntheticEvent<*>) => { + handleUnpin = (ev: SyntheticEvent<>) => { this.props.document.unpin(); }; - handleStar = (ev: SyntheticEvent<*>) => { + handleStar = (ev: SyntheticEvent<>) => { this.props.document.star(); }; - handleUnstar = (ev: SyntheticEvent<*>) => { + handleUnstar = (ev: SyntheticEvent<>) => { this.props.document.unstar(); }; - handleExport = (ev: SyntheticEvent<*>) => { + handleExport = (ev: SyntheticEvent<>) => { this.props.document.download(); }; - handleShareLink = async (ev: SyntheticEvent<*>) => { + handleShareLink = async (ev: SyntheticEvent<>) => { const { document } = this.props; if (!document.shareUrl) await document.share(); diff --git a/app/menus/RevisionMenu.js b/app/menus/RevisionMenu.js index ffbcf783..7a7ebf95 100644 --- a/app/menus/RevisionMenu.js +++ b/app/menus/RevisionMenu.js @@ -1,6 +1,6 @@ // @flow import * as React from 'react'; -import { withRouter } from 'react-router-dom'; +import { withRouter, type RouterHistory } from 'react-router-dom'; import { inject } from 'mobx-react'; import { MoreIcon } from 'outline-icons'; @@ -13,9 +13,9 @@ import UiStore from 'stores/UiStore'; type Props = { label?: React.Node, - onOpen?: () => *, - onClose: () => *, - history: Object, + onOpen?: () => void, + onClose: () => void, + history: RouterHistory, document: Document, revision: Revision, className?: string, @@ -23,7 +23,7 @@ type Props = { }; class RevisionMenu extends React.Component { - handleRestore = async (ev: SyntheticEvent<*>) => { + handleRestore = async (ev: SyntheticEvent<>) => { ev.preventDefault(); await this.props.document.restore(this.props.revision); this.props.ui.showToast('Document restored'); diff --git a/app/menus/ShareMenu.js b/app/menus/ShareMenu.js index 27fd1b6f..a86a0591 100644 --- a/app/menus/ShareMenu.js +++ b/app/menus/ShareMenu.js @@ -13,8 +13,8 @@ import Share from 'models/Share'; type Props = { label?: React.Node, - onOpen?: () => *, - onClose: () => *, + onOpen?: () => void, + onClose: () => void, shares: SharesStore, ui: UiStore, share: Share, @@ -28,12 +28,12 @@ class ShareMenu extends React.Component { this.redirectTo = undefined; } - handleGoToDocument = (ev: SyntheticEvent<*>) => { + handleGoToDocument = (ev: SyntheticEvent<>) => { ev.preventDefault(); this.redirectTo = this.props.share.documentUrl; }; - handleRevoke = (ev: SyntheticEvent<*>) => { + handleRevoke = (ev: SyntheticEvent<>) => { ev.preventDefault(); this.props.shares.revoke(this.props.share); this.props.ui.showToast('Share link revoked'); diff --git a/app/menus/UserMenu.js b/app/menus/UserMenu.js index 29ecf40b..eda16d86 100644 --- a/app/menus/UserMenu.js +++ b/app/menus/UserMenu.js @@ -14,7 +14,7 @@ type Props = { @observer class UserMenu extends React.Component { - handlePromote = (ev: SyntheticEvent<*>) => { + handlePromote = (ev: SyntheticEvent<>) => { ev.preventDefault(); const { user, users } = this.props; if ( @@ -29,7 +29,7 @@ class UserMenu extends React.Component { users.promote(user); }; - handleDemote = (ev: SyntheticEvent<*>) => { + handleDemote = (ev: SyntheticEvent<>) => { ev.preventDefault(); const { user, users } = this.props; if (!window.confirm(`Are you want to make ${user.name} a member?`)) { @@ -38,7 +38,7 @@ class UserMenu extends React.Component { users.demote(user); }; - handleSuspend = (ev: SyntheticEvent<*>) => { + handleSuspend = (ev: SyntheticEvent<>) => { ev.preventDefault(); const { user, users } = this.props; if ( @@ -51,7 +51,7 @@ class UserMenu extends React.Component { users.suspend(user); }; - handleActivate = (ev: SyntheticEvent<*>) => { + handleActivate = (ev: SyntheticEvent<>) => { ev.preventDefault(); const { user, users } = this.props; users.activate(user); diff --git a/app/models/Document.js b/app/models/Document.js index 271f6ab9..a19d4e19 100644 --- a/app/models/Document.js +++ b/app/models/Document.js @@ -7,13 +7,13 @@ import unescape from 'shared/utils/unescape'; import BaseModel from 'models/BaseModel'; import Revision from 'models/Revision'; import User from 'models/User'; +import DocumentsStore from 'stores/DocumentsStore'; type SaveOptions = { publish?: boolean, done?: boolean, autosave?: boolean }; export default class Document extends BaseModel { isSaving: boolean; - ui: *; - store: *; + store: DocumentsStore; collaborators: User[]; collectionId: string; @@ -37,7 +37,7 @@ export default class Document extends BaseModel { shareUrl: ?string; revision: number; - constructor(data?: Object = {}, store: *) { + constructor(data?: Object = {}, store: DocumentsStore) { super(data, store); this.updateTitle(); } @@ -54,7 +54,7 @@ export default class Document extends BaseModel { @computed get isStarred(): boolean { - return this.store.starredIds.get(this.id); + return !!this.store.starredIds.get(this.id); } @computed @@ -90,7 +90,7 @@ export default class Document extends BaseModel { return this.store.archive(this); }; - restore = (revision: ?Revision) => { + restore = (revision: Revision) => { return this.store.restore(this, revision); }; diff --git a/app/scenes/Collection.js b/app/scenes/Collection.js index 50761113..8da008a0 100644 --- a/app/scenes/Collection.js +++ b/app/scenes/Collection.js @@ -83,7 +83,7 @@ class CollectionScene extends React.Component { this.isFetching = false; }; - onNewDocument = (ev: SyntheticEvent<*>) => { + onNewDocument = (ev: SyntheticEvent<>) => { ev.preventDefault(); if (this.collection) { @@ -91,7 +91,7 @@ class CollectionScene extends React.Component { } }; - onPermissions = (ev: SyntheticEvent<*>) => { + onPermissions = (ev: SyntheticEvent<>) => { ev.preventDefault(); this.permissionsModalOpen = true; }; diff --git a/app/scenes/CollectionDelete.js b/app/scenes/CollectionDelete.js index 77a20dae..1ee28e82 100644 --- a/app/scenes/CollectionDelete.js +++ b/app/scenes/CollectionDelete.js @@ -1,6 +1,6 @@ // @flow import * as React from 'react'; -import { withRouter } from 'react-router-dom'; +import { withRouter, type RouterHistory } from 'react-router-dom'; import { observable } from 'mobx'; import { inject, observer } from 'mobx-react'; import { homeUrl } from 'utils/routeHelpers'; @@ -12,7 +12,7 @@ import CollectionsStore from 'stores/CollectionsStore'; import UiStore from 'stores/UiStore'; type Props = { - history: Object, + history: RouterHistory, collection: Collection, collections: CollectionsStore, ui: UiStore, @@ -23,7 +23,7 @@ type Props = { class CollectionDelete extends React.Component { @observable isDeleting: boolean; - handleSubmit = async (ev: SyntheticEvent<*>) => { + handleSubmit = async (ev: SyntheticEvent<>) => { ev.preventDefault(); this.isDeleting = true; diff --git a/app/scenes/CollectionEdit.js b/app/scenes/CollectionEdit.js index 553f6b8d..ba894121 100644 --- a/app/scenes/CollectionEdit.js +++ b/app/scenes/CollectionEdit.js @@ -1,6 +1,6 @@ // @flow import * as React from 'react'; -import { withRouter } from 'react-router-dom'; +import { withRouter, type RouterHistory } from 'react-router-dom'; import { observable } from 'mobx'; import { inject, observer } from 'mobx-react'; import Input from 'components/Input'; @@ -13,7 +13,7 @@ import Collection from 'models/Collection'; import UiStore from 'stores/UiStore'; type Props = { - history: Object, + history: RouterHistory, collection: Collection, ui: UiStore, onSubmit: () => void, @@ -31,7 +31,7 @@ class CollectionEdit extends React.Component { this.description = this.props.collection.description; } - handleSubmit = async (ev: SyntheticEvent<*>) => { + handleSubmit = async (ev: SyntheticEvent<>) => { ev.preventDefault(); this.isSaving = true; diff --git a/app/scenes/CollectionExport.js b/app/scenes/CollectionExport.js index ce6c44a3..c539cb7d 100644 --- a/app/scenes/CollectionExport.js +++ b/app/scenes/CollectionExport.js @@ -20,7 +20,7 @@ type Props = { class CollectionExport extends React.Component { @observable isLoading: boolean = false; - handleSubmit = async (ev: SyntheticEvent<*>) => { + handleSubmit = async (ev: SyntheticEvent<>) => { ev.preventDefault(); this.isLoading = true; diff --git a/app/scenes/CollectionNew.js b/app/scenes/CollectionNew.js index ed7ab755..db4ad2ae 100644 --- a/app/scenes/CollectionNew.js +++ b/app/scenes/CollectionNew.js @@ -1,6 +1,6 @@ // @flow import * as React from 'react'; -import { withRouter } from 'react-router-dom'; +import { withRouter, type RouterHistory } from 'react-router-dom'; import { observable } from 'mobx'; import { inject, observer } from 'mobx-react'; import Button from 'components/Button'; @@ -15,7 +15,7 @@ import CollectionsStore from 'stores/CollectionsStore'; import UiStore from 'stores/UiStore'; type Props = { - history: Object, + history: RouterHistory, ui: UiStore, collections: CollectionsStore, onSubmit: () => void, @@ -29,7 +29,7 @@ class CollectionNew extends React.Component { @observable private: boolean = false; @observable isSaving: boolean; - handleSubmit = async (ev: SyntheticEvent<*>) => { + handleSubmit = async (ev: SyntheticEvent<>) => { ev.preventDefault(); this.isSaving = true; const collection = new Collection( diff --git a/app/scenes/CollectionPermissions/components/MemberListItem.js b/app/scenes/CollectionPermissions/components/MemberListItem.js index 4a844762..eed20acf 100644 --- a/app/scenes/CollectionPermissions/components/MemberListItem.js +++ b/app/scenes/CollectionPermissions/components/MemberListItem.js @@ -12,7 +12,7 @@ import { DropdownMenu, DropdownMenuItem } from 'components/DropdownMenu'; type Props = { user: User, showRemove: boolean, - onRemove: () => *, + onRemove: () => void, }; const MemberListItem = ({ user, onRemove, showRemove }: Props) => { diff --git a/app/scenes/CollectionPermissions/components/UserListItem.js b/app/scenes/CollectionPermissions/components/UserListItem.js index 51e54a78..4deba753 100644 --- a/app/scenes/CollectionPermissions/components/UserListItem.js +++ b/app/scenes/CollectionPermissions/components/UserListItem.js @@ -8,7 +8,7 @@ import User from 'models/User'; type Props = { user: User, showAdd: boolean, - onAdd: () => *, + onAdd: () => void, }; const UserListItem = ({ user, onAdd, showAdd }: Props) => { diff --git a/app/scenes/Document/Document.js b/app/scenes/Document/Document.js index 5e1ab14f..fbc62882 100644 --- a/app/scenes/Document/Document.js +++ b/app/scenes/Document/Document.js @@ -6,7 +6,7 @@ import breakpoint from 'styled-components-breakpoint'; import { observable } from 'mobx'; import { observer, inject } from 'mobx-react'; import { Prompt, Route, withRouter } from 'react-router-dom'; -import type { Location } from 'react-router-dom'; +import type { Location, RouterHistory } from 'react-router-dom'; import keydown from 'react-keydown'; import Flex from 'shared/components/Flex'; import { @@ -59,7 +59,7 @@ Are you sure you want to discard them? type Props = { match: Object, - history: Object, + history: RouterHistory, location: Location, documents: DocumentsStore, revisions: RevisionsStore, diff --git a/app/scenes/Document/components/Backlink.js b/app/scenes/Document/components/Backlink.js index 483e2646..f07c506b 100644 --- a/app/scenes/Document/components/Backlink.js +++ b/app/scenes/Document/components/Backlink.js @@ -10,7 +10,6 @@ type Props = { document: Document, anchor: string, showCollection?: boolean, - ref?: *, }; const DocumentLink = styled(Link)` diff --git a/app/scenes/Document/components/DocumentMove.js b/app/scenes/Document/components/DocumentMove.js index 47386d5a..7c10c2c1 100644 --- a/app/scenes/Document/components/DocumentMove.js +++ b/app/scenes/Document/components/DocumentMove.js @@ -26,12 +26,12 @@ type Props = { documents: DocumentsStore, collections: CollectionsStore, ui: UiStore, - onRequestClose: *, + onRequestClose: () => void, }; @observer class DocumentMove extends React.Component { - firstDocument: *; + firstDocument: ?PathToDocument; @observable searchTerm: ?string; @observable isSaving: boolean; diff --git a/app/scenes/Document/components/Editor.js b/app/scenes/Document/components/Editor.js index ab9f1a5c..a2268884 100644 --- a/app/scenes/Document/components/Editor.js +++ b/app/scenes/Document/components/Editor.js @@ -10,7 +10,7 @@ type Props = { }; class DocumentEditor extends React.Component { - editor: *; + editor: ?Editor; componentDidMount() { if (!this.props.defaultValue) { diff --git a/app/scenes/Document/components/Header.js b/app/scenes/Document/components/Header.js index f8cc390b..4f1d0e9e 100644 --- a/app/scenes/Document/components/Header.js +++ b/app/scenes/Document/components/Header.js @@ -32,12 +32,12 @@ type Props = { isPublishing: boolean, publishingIsDisabled: boolean, savingIsDisabled: boolean, - onDiscard: () => *, + onDiscard: () => void, onSave: ({ done?: boolean, publish?: boolean, autosave?: boolean, - }) => *, + }) => void, auth: AuthStore, }; @@ -73,7 +73,7 @@ class Header extends React.Component { this.props.onSave({ done: true, publish: true }); }; - handleShareLink = async (ev: SyntheticEvent<*>) => { + handleShareLink = async (ev: SyntheticEvent<>) => { const { document } = this.props; if (!document.shareUrl) await document.share(); this.showShareModal = true; diff --git a/app/scenes/DocumentDelete.js b/app/scenes/DocumentDelete.js index 3007eb20..259f3402 100644 --- a/app/scenes/DocumentDelete.js +++ b/app/scenes/DocumentDelete.js @@ -1,6 +1,6 @@ // @flow import * as React from 'react'; -import { withRouter } from 'react-router-dom'; +import { withRouter, type RouterHistory } from 'react-router-dom'; import { observable } from 'mobx'; import { inject, observer } from 'mobx-react'; import Button from 'components/Button'; @@ -12,7 +12,7 @@ import UiStore from 'stores/UiStore'; import { collectionUrl } from 'utils/routeHelpers'; type Props = { - history: Object, + history: RouterHistory, document: Document, documents: DocumentsStore, ui: UiStore, @@ -23,7 +23,7 @@ type Props = { class DocumentDelete extends React.Component { @observable isDeleting: boolean; - handleSubmit = async (ev: SyntheticEvent<*>) => { + handleSubmit = async (ev: SyntheticEvent<>) => { ev.preventDefault(); this.isDeleting = true; diff --git a/app/scenes/DocumentShare.js b/app/scenes/DocumentShare.js index 025e4258..a9887456 100644 --- a/app/scenes/DocumentShare.js +++ b/app/scenes/DocumentShare.js @@ -11,7 +11,7 @@ import Document from 'models/Document'; type Props = { document?: Document, - onSubmit: () => *, + onSubmit: () => void, }; @observer diff --git a/app/scenes/Invite.js b/app/scenes/Invite.js index 69011e1d..b04a578f 100644 --- a/app/scenes/Invite.js +++ b/app/scenes/Invite.js @@ -1,6 +1,6 @@ // @flow import * as React from 'react'; -import { withRouter } from 'react-router-dom'; +import { withRouter, type RouterHistory } from 'react-router-dom'; import { observable } from 'mobx'; import { inject, observer } from 'mobx-react'; import { CloseIcon } from 'outline-icons'; @@ -21,7 +21,7 @@ const MAX_INVITES = 20; type Props = { auth: AuthStore, users: UsersStore, - history: Object, + history: RouterHistory, ui: UiStore, onSubmit: () => void, }; @@ -37,7 +37,7 @@ class Invite extends React.Component { { email: '', name: '' }, ]; - handleSubmit = async (ev: SyntheticEvent<*>) => { + handleSubmit = async (ev: SyntheticEvent<>) => { ev.preventDefault(); this.isSaving = true; diff --git a/app/scenes/Search/Search.js b/app/scenes/Search/Search.js index 6c7b47dd..55113aa0 100644 --- a/app/scenes/Search/Search.js +++ b/app/scenes/Search/Search.js @@ -4,6 +4,7 @@ import ReactDOM from 'react-dom'; import keydown from 'react-keydown'; import Waypoint from 'react-waypoint'; import { withRouter, Link } from 'react-router-dom'; +import type { Location, RouterHistory } from 'react-router-dom'; import { observable, action } from 'mobx'; import { observer, inject } from 'mobx-react'; import { debounce } from 'lodash'; @@ -32,9 +33,9 @@ import UserFilter from './components/UserFilter'; import DateFilter from './components/DateFilter'; type Props = { - history: Object, + history: RouterHistory, match: Object, - location: Object, + location: Location, documents: DocumentsStore, users: UsersStore, notFound: ?boolean, diff --git a/app/scenes/Search/components/SearchField.js b/app/scenes/Search/components/SearchField.js index d6584208..cd36e6fa 100644 --- a/app/scenes/Search/components/SearchField.js +++ b/app/scenes/Search/components/SearchField.js @@ -5,18 +5,18 @@ import { SearchIcon } from 'outline-icons'; import Flex from 'shared/components/Flex'; type Props = { - onChange: string => *, + onChange: string => void, theme: Object, }; class SearchField extends React.Component { input: ?HTMLInputElement; - handleChange = (ev: SyntheticEvent<*>) => { + handleChange = (ev: SyntheticEvent) => { this.props.onChange(ev.currentTarget.value ? ev.currentTarget.value : ''); }; - focusInput = (ev: SyntheticEvent<*>) => { + focusInput = (ev: SyntheticEvent<>) => { if (this.input) this.input.focus(); }; diff --git a/app/scenes/Settings/Details.js b/app/scenes/Settings/Details.js index d0de9f37..324df2b2 100644 --- a/app/scenes/Settings/Details.js +++ b/app/scenes/Settings/Details.js @@ -40,7 +40,7 @@ class Details extends React.Component { clearTimeout(this.timeout); } - handleSubmit = async (ev: SyntheticEvent<*>) => { + handleSubmit = async (ev: SyntheticEvent<>) => { ev.preventDefault(); try { diff --git a/app/scenes/Settings/Export.js b/app/scenes/Settings/Export.js index d4ccc420..417a32a1 100644 --- a/app/scenes/Settings/Export.js +++ b/app/scenes/Settings/Export.js @@ -22,7 +22,7 @@ class Export extends React.Component { @observable isLoading: boolean = false; @observable isExporting: boolean = false; - handleSubmit = async (ev: SyntheticEvent<*>) => { + handleSubmit = async (ev: SyntheticEvent<>) => { ev.preventDefault(); this.isLoading = true; diff --git a/app/scenes/Settings/Notifications.js b/app/scenes/Settings/Notifications.js index 06c0682e..25d6986d 100644 --- a/app/scenes/Settings/Notifications.js +++ b/app/scenes/Settings/Notifications.js @@ -59,7 +59,7 @@ class Notifications extends React.Component { this.props.notificationSettings.fetchPage(); } - handleChange = async (ev: SyntheticInputEvent<*>) => { + handleChange = async (ev: SyntheticInputEvent<>) => { const { notificationSettings } = this.props; const setting = notificationSettings.getByEvent(ev.target.name); diff --git a/app/scenes/Settings/Profile.js b/app/scenes/Settings/Profile.js index 60e31490..6a93bed2 100644 --- a/app/scenes/Settings/Profile.js +++ b/app/scenes/Settings/Profile.js @@ -38,7 +38,7 @@ class Profile extends React.Component { clearTimeout(this.timeout); } - handleSubmit = async (ev: SyntheticEvent<*>) => { + handleSubmit = async (ev: SyntheticEvent<>) => { ev.preventDefault(); await this.props.auth.updateUser({ diff --git a/app/scenes/Settings/Tokens.js b/app/scenes/Settings/Tokens.js index 37fc4692..a36b8941 100644 --- a/app/scenes/Settings/Tokens.js +++ b/app/scenes/Settings/Tokens.js @@ -28,7 +28,7 @@ class Tokens extends React.Component { this.name = ev.target.value; }; - handleSubmit = async (ev: SyntheticEvent<*>) => { + handleSubmit = async (ev: SyntheticEvent<>) => { ev.preventDefault(); await this.props.apiKeys.create({ name: this.name }); this.name = ''; diff --git a/app/scenes/Settings/components/ImageUpload.js b/app/scenes/Settings/components/ImageUpload.js index 116fdd0a..feb1bed3 100644 --- a/app/scenes/Settings/components/ImageUpload.js +++ b/app/scenes/Settings/components/ImageUpload.js @@ -4,7 +4,6 @@ import { observable } from 'mobx'; import { observer } from 'mobx-react'; import styled from 'styled-components'; import Dropzone from 'react-dropzone'; - import LoadingIndicator from 'components/LoadingIndicator'; import Flex from 'shared/components/Flex'; import Modal from 'components/Modal'; @@ -14,8 +13,8 @@ import { uploadFile, dataUrlToBlob } from 'utils/uploadFile'; type Props = { children?: React.Node, - onSuccess: string => *, - onError: string => *, + onSuccess: string => void | Promise, + onError: string => void, submitText: string, borderRadius: number, }; diff --git a/app/scenes/Settings/components/NotificationListItem.js b/app/scenes/Settings/components/NotificationListItem.js index e2eb8734..60e41fac 100644 --- a/app/scenes/Settings/components/NotificationListItem.js +++ b/app/scenes/Settings/components/NotificationListItem.js @@ -9,7 +9,7 @@ type Props = { event: string, description: string, disabled: boolean, - onChange: *, + onChange: (ev: SyntheticInputEvent<>) => void | Promise, }; const NotificationListItem = ({ diff --git a/app/scenes/Settings/components/TokenListItem.js b/app/scenes/Settings/components/TokenListItem.js index 6eea21cd..d89e58b1 100644 --- a/app/scenes/Settings/components/TokenListItem.js +++ b/app/scenes/Settings/components/TokenListItem.js @@ -6,7 +6,7 @@ import ApiKey from 'models/ApiKey'; type Props = { token: ApiKey, - onDelete: (tokenId: string) => *, + onDelete: (tokenId: string) => void, }; const TokenListItem = ({ token, onDelete }: Props) => { diff --git a/app/scenes/UserDelete.js b/app/scenes/UserDelete.js index 036ffe10..41ac9e95 100644 --- a/app/scenes/UserDelete.js +++ b/app/scenes/UserDelete.js @@ -10,14 +10,14 @@ import AuthStore from 'stores/AuthStore'; type Props = { auth: AuthStore, - onRequestClose: () => *, + onRequestClose: () => void, }; @observer class UserDelete extends React.Component { @observable isDeleting: boolean; - handleSubmit = async (ev: SyntheticEvent<*>) => { + handleSubmit = async (ev: SyntheticEvent<>) => { ev.preventDefault(); this.isDeleting = true; diff --git a/app/scenes/UserProfile.js b/app/scenes/UserProfile.js index 8e1229a4..e861b6e2 100644 --- a/app/scenes/UserProfile.js +++ b/app/scenes/UserProfile.js @@ -3,7 +3,7 @@ import * as React from 'react'; import styled from 'styled-components'; import distanceInWordsToNow from 'date-fns/distance_in_words_to_now'; import { inject, observer } from 'mobx-react'; -import { withRouter } from 'react-router-dom'; +import { withRouter, type RouterHistory } from 'react-router-dom'; import { EditIcon } from 'outline-icons'; import Flex from 'shared/components/Flex'; import HelpText from 'components/HelpText'; @@ -22,8 +22,8 @@ type Props = { user: User, auth: AuthStore, documents: DocumentsStore, - history: Object, - onRequestClose: () => *, + history: RouterHistory, + onRequestClose: () => void, }; @observer diff --git a/app/stores/DocumentsStore.js b/app/stores/DocumentsStore.js index 8950787c..2e242925 100644 --- a/app/stores/DocumentsStore.js +++ b/app/stores/DocumentsStore.js @@ -49,7 +49,7 @@ export default class DocumentsStore extends BaseStore { return orderBy(this.all, 'updatedAt', 'desc'); } - createdByUser(userId: string): * { + createdByUser(userId: string): Document[] { return orderBy( filter(this.all, d => d.createdBy.id === userId), 'updatedAt', @@ -387,7 +387,12 @@ export default class DocumentsStore extends BaseStore { } @action - async update(params: *) { + async update(params: { + id: string, + title: string, + text: string, + lastRevision: number, + }) { const document = await super.update(params); // Because the collection object contains the url and title diff --git a/app/utils/getDataTransferFiles.js b/app/utils/getDataTransferFiles.js index b8bef197..9822e0dd 100644 --- a/app/utils/getDataTransferFiles.js +++ b/app/utils/getDataTransferFiles.js @@ -1,5 +1,5 @@ // @flow -export default function getDataTransferFiles(event: SyntheticEvent<*>): File[] { +export default function getDataTransferFiles(event: SyntheticEvent<>): File[] { let dataTransferItemsList = []; // $FlowFixMe diff --git a/app/utils/importFile.js b/app/utils/importFile.js index aa851eeb..5ace59ce 100644 --- a/app/utils/importFile.js +++ b/app/utils/importFile.js @@ -1,9 +1,10 @@ // @flow -import Document from '../models/Document'; +import Document from 'models/Document'; +import DocumentsStore from 'stores/DocumentsStore'; type Options = { file: File, - documents: *, + documents: DocumentsStore, collectionId: string, documentId?: string, }; diff --git a/shared/utils/naturalSort.js b/shared/utils/naturalSort.js index 0ff35ab6..3d7a3868 100644 --- a/shared/utils/naturalSort.js +++ b/shared/utils/naturalSort.js @@ -1,11 +1,36 @@ // @flow -import { sortBy } from 'lodash'; +import { deburr } from 'lodash'; import naturalSort from 'natural-sort'; -export default (sortableArray: *, key: string): * => { - if (!sortableArray) return []; - - let keys = sortableArray.map(object => object[key]); - keys.sort(naturalSort()); - return sortBy(sortableArray, object => keys.indexOf(object[key])); +type NaturalSortOptions = { + caseSensitive?: boolean, + direction?: 'asc' | 'desc', }; + +const sorter = naturalSort(); + +function getSortByField( + item: T, + keyOrCallback: string | (T => string) +) { + if (typeof keyOrCallback === 'string') { + return deburr(item[keyOrCallback]); + } + + return keyOrCallback(item); +} + +function naturalSortBy( + items: T[], + key: string | (T => string), + sortOptions?: NaturalSortOptions +): T[] { + if (!items) return []; + + const sort = sortOptions ? naturalSort(sortOptions) : sorter; + return items.sort((a: any, b: any): -1 | 0 | 1 => + sort(getSortByField(a, key), getSortByField(b, key)) + ); +} + +export default naturalSortBy;