diff --git a/app/scenes/CollectionDelete.js b/app/scenes/CollectionDelete.js index 13e93ea33..f9c23f345 100644 --- a/app/scenes/CollectionDelete.js +++ b/app/scenes/CollectionDelete.js @@ -1,62 +1,60 @@ // @flow -import { observable } from "mobx"; -import { inject, observer } from "mobx-react"; +import { observer } from "mobx-react"; import * as React from "react"; -import { withRouter, type RouterHistory } from "react-router-dom"; -import CollectionsStore from "stores/CollectionsStore"; -import UiStore from "stores/UiStore"; +import { useTranslation, Trans } from "react-i18next"; +import { useHistory } from "react-router-dom"; import Collection from "models/Collection"; import Button from "components/Button"; import Flex from "components/Flex"; import HelpText from "components/HelpText"; +import useStores from "hooks/useStores"; import { homeUrl } from "utils/routeHelpers"; type Props = { - history: RouterHistory, collection: Collection, - collections: CollectionsStore, - ui: UiStore, onSubmit: () => void, }; -@observer -class CollectionDelete extends React.Component { - @observable isDeleting: boolean; +function CollectionDelete({ collection, onSubmit }: Props) { + const [isDeleting, setIsDeleting] = React.useState(); + const { ui } = useStores(); + const history = useHistory(); + const { t } = useTranslation(); - handleSubmit = async (ev: SyntheticEvent<>) => { - ev.preventDefault(); - this.isDeleting = true; + const handleSubmit = React.useCallback( + async (ev: SyntheticEvent<>) => { + ev.preventDefault(); + setIsDeleting(true); - try { - await this.props.collection.delete(); - this.props.history.push(homeUrl()); - this.props.onSubmit(); - } catch (err) { - this.props.ui.showToast(err.message, { type: "error" }); - } finally { - this.isDeleting = false; - } - }; + try { + await collection.delete(); + history.push(homeUrl()); + onSubmit(); + } catch (err) { + ui.showToast(err.message, { type: "error" }); + } finally { + setIsDeleting(false); + } + }, + [ui, onSubmit, collection, history] + ); - render() { - const { collection } = this.props; - - return ( - -
- - Are you sure about that? Deleting the{" "} - {collection.name} collection is permanent and - cannot be restored, however documents within will be moved to the - trash. - - -
-
- ); - } + return ( + +
+ + }} + /> + + +
+
+ ); } -export default inject("collections", "ui")(withRouter(CollectionDelete)); +export default observer(CollectionDelete); diff --git a/app/scenes/CollectionExport.js b/app/scenes/CollectionExport.js index e2b14abff..7fdd44a98 100644 --- a/app/scenes/CollectionExport.js +++ b/app/scenes/CollectionExport.js @@ -1,9 +1,7 @@ // @flow -import { observable } from "mobx"; -import { inject, observer } from "mobx-react"; +import { observer } from "mobx-react"; import * as React from "react"; -import AuthStore from "stores/AuthStore"; -import UiStore from "stores/UiStore"; +import { useTranslation, Trans } from "react-i18next"; import Collection from "models/Collection"; import Button from "components/Button"; import Flex from "components/Flex"; @@ -11,43 +9,41 @@ import HelpText from "components/HelpText"; type Props = { collection: Collection, - auth: AuthStore, - ui: UiStore, onSubmit: () => void, }; -@observer -class CollectionExport extends React.Component { - @observable isLoading: boolean = false; +function CollectionExport({ collection, onSubmit }: Props) { + const [isLoading, setIsLoading] = React.useState(); + const { t } = useTranslation(); - handleSubmit = async (ev: SyntheticEvent<>) => { - ev.preventDefault(); + const handleSubmit = React.useCallback( + async (ev: SyntheticEvent<>) => { + ev.preventDefault(); - this.isLoading = true; - await this.props.collection.export(); - this.isLoading = false; - this.props.onSubmit(); - }; + setIsLoading(true); + await collection.export(); + setIsLoading(false); + onSubmit(); + }, + [collection, onSubmit] + ); - render() { - const { collection, auth } = this.props; - if (!auth.user) return null; - - return ( - -
- - Exporting the collection {collection.name} may take - a few seconds. Your documents will be downloaded as a zip of folders - with files in Markdown format. - - -
-
- ); - } + return ( + +
+ + }} + /> + + +
+
+ ); } -export default inject("ui", "auth")(CollectionExport); +export default observer(CollectionExport); diff --git a/app/scenes/DocumentMove.js b/app/scenes/DocumentMove.js index a6dd5ec9b..08b1ae90b 100644 --- a/app/scenes/DocumentMove.js +++ b/app/scenes/DocumentMove.js @@ -1,37 +1,32 @@ // @flow import { Search } from "js-search"; import { last } from "lodash"; -import { observable, computed } from "mobx"; -import { observer, inject } from "mobx-react"; +import { observer } from "mobx-react"; import * as React from "react"; +import { useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; import AutoSizer from "react-virtualized-auto-sizer"; import { FixedSizeList as List } from "react-window"; import styled from "styled-components"; -import CollectionsStore, { type DocumentPath } from "stores/CollectionsStore"; -import DocumentsStore from "stores/DocumentsStore"; -import UiStore from "stores/UiStore"; +import { type DocumentPath } from "stores/CollectionsStore"; import Document from "models/Document"; import Flex from "components/Flex"; import { Outline } from "components/Input"; import Labeled from "components/Labeled"; import PathToDocument from "components/PathToDocument"; +import useStores from "hooks/useStores"; type Props = {| document: Document, - documents: DocumentsStore, - collections: CollectionsStore, - ui: UiStore, onRequestClose: () => void, |}; -@observer -class DocumentMove extends React.Component { - @observable searchTerm: ?string; - @observable isSaving: boolean; +function DocumentMove({ document, onRequestClose }: Props) { + const [searchTerm, setSearchTerm] = useState(); + const { ui, collections, documents } = useStores(); + const { t } = useTranslation(); - @computed - get searchIndex() { - const { collections, documents } = this.props; + const searchIndex = useMemo(() => { const paths = collections.pathsToDocuments; const index = new Search("id"); index.addIndex("title"); @@ -47,19 +42,16 @@ class DocumentMove extends React.Component { index.addDocuments(indexeableDocuments); return index; - } + }, [documents, collections.pathsToDocuments]); - @computed - get results(): DocumentPath[] { - const { document, collections } = this.props; + const results: DocumentPath[] = useMemo(() => { const onlyShowCollections = document.isTemplate; - let results = []; if (collections.isLoaded) { - if (this.searchTerm) { - results = this.searchIndex.search(this.searchTerm); + if (searchTerm) { + results = searchIndex.search(searchTerm); } else { - results = this.searchIndex._documents; + results = searchIndex._documents; } } @@ -82,19 +74,18 @@ class DocumentMove extends React.Component { } return results; - } + }, [document, collections, searchTerm, searchIndex]); - handleSuccess = () => { - this.props.ui.showToast("Document moved", { type: "info" }); - this.props.onRequestClose(); + const handleSuccess = () => { + ui.showToast(t("Document moved"), { type: "info" }); + onRequestClose(); }; - handleFilter = (ev: SyntheticInputEvent<*>) => { - this.searchTerm = ev.target.value; + const handleFilter = (ev: SyntheticInputEvent<*>) => { + setSearchTerm(ev.target.value); }; - renderPathToCurrentDocument() { - const { collections, document } = this.props; + const renderPathToCurrentDocument = () => { const result = collections.getPathForDocument(document.id); if (result) { @@ -105,75 +96,71 @@ class DocumentMove extends React.Component { /> ); } - } + }; - row = ({ index, data, style }) => { + const row = ({ index, data, style }) => { const result = data[index]; - const { document, collections } = this.props; return ( ); }; - render() { - const { document, collections } = this.props; - const data = this.results; + const data = results; - if (!document || !collections.isLoaded) { - return null; - } - - return ( - -
- - {this.renderPathToCurrentDocument()} - -
- -
- - - - - - - - {({ width, height }) => ( - - data[index].id} - > - {this.row} - - - )} - - - -
-
- ); + if (!document || !collections.isLoaded) { + return null; } + + return ( + +
+ + {renderPathToCurrentDocument()} + +
+ +
+ + + + + + + + {({ width, height }) => ( + + data[index].id} + > + {row} + + + )} + + + +
+
+ ); } const InputWrapper = styled("div")` @@ -210,4 +197,4 @@ const Section = styled(Flex)` margin-bottom: 24px; `; -export default inject("documents", "collections", "ui")(DocumentMove); +export default observer(DocumentMove); diff --git a/app/scenes/DocumentTemplatize.js b/app/scenes/DocumentTemplatize.js index 8a6f44f20..814220fb7 100644 --- a/app/scenes/DocumentTemplatize.js +++ b/app/scenes/DocumentTemplatize.js @@ -1,63 +1,64 @@ // @flow -import { observable } from "mobx"; -import { inject, observer } from "mobx-react"; +import { observer } from "mobx-react"; import * as React from "react"; -import { withRouter, type RouterHistory } from "react-router-dom"; -import UiStore from "stores/UiStore"; +import { useState } from "react"; +import { useTranslation, Trans } from "react-i18next"; +import { useHistory } from "react-router-dom"; import Document from "models/Document"; import Button from "components/Button"; import Flex from "components/Flex"; import HelpText from "components/HelpText"; +import useStores from "hooks/useStores"; import { documentUrl } from "utils/routeHelpers"; type Props = { - ui: UiStore, document: Document, - history: RouterHistory, onSubmit: () => void, }; -@observer -class DocumentTemplatize extends React.Component { - @observable isSaving: boolean; +function DocumentTemplatize({ document, onSubmit }: Props) { + const [isSaving, setIsSaving] = useState(); + const history = useHistory(); + const { ui } = useStores(); + const { t } = useTranslation(); - handleSubmit = async (ev: SyntheticEvent<>) => { - ev.preventDefault(); - this.isSaving = true; + const handleSubmit = React.useCallback( + async (ev: SyntheticEvent<>) => { + ev.preventDefault(); + setIsSaving(true); - try { - const template = await this.props.document.templatize(); - this.props.history.push(documentUrl(template)); - this.props.ui.showToast("Template created, go ahead and customize it", { - type: "info", - }); - this.props.onSubmit(); - } catch (err) { - this.props.ui.showToast(err.message, { type: "error" }); - } finally { - this.isSaving = false; - } - }; + try { + const template = await document.templatize(); + history.push(documentUrl(template)); + ui.showToast(t("Template created, go ahead and customize it"), { + type: "info", + }); + onSubmit(); + } catch (err) { + ui.showToast(err.message, { type: "error" }); + } finally { + setIsSaving(false); + } + }, + [document, ui, history, onSubmit, t] + ); - render() { - const { document } = this.props; - - return ( - -
- - Creating a template from{" "} - {document.titleWithDefault} is a non-destructive - action – we'll make a copy of the document and turn it into a - template that can be used as a starting point for new documents. - - -
-
- ); - } + return ( + +
+ + }} + /> + + +
+
+ ); } -export default inject("ui")(withRouter(DocumentTemplatize)); +export default observer(DocumentTemplatize); diff --git a/shared/i18n/locales/en_US/translation.json b/shared/i18n/locales/en_US/translation.json index d474c8274..09937fe98 100644 --- a/shared/i18n/locales/en_US/translation.json +++ b/shared/i18n/locales/en_US/translation.json @@ -219,6 +219,7 @@ "Contents": "Contents", "Headings you add to the document will appear here": "Headings you add to the document will appear here", "Table of contents": "Table of contents", + "Contents": "Contents", "By {{ author }}": "By {{ author }}", "Are you sure you want to make {{ userName }} an admin? Admins can modify team and billing information.": "Are you sure you want to make {{ userName }} an admin? Admins can modify team and billing information.", "Are you sure you want to make {{ userName }} a member?": "Are you sure you want to make {{ userName }} a member?", @@ -248,6 +249,9 @@ "Least recently updated": "Least recently updated", "A–Z": "A–Z", "Drop documents to import": "Drop documents to import", + "Are you sure about that? Deleting the {{collectionName}} collection is permanent and cannot be restored, however documents within will be moved to the trash.": "Are you sure about that? Deleting the {{collectionName}} collection is permanent and cannot be restored, however documents within will be moved to the trash.", + "Deleting": "Deleting", + "I’m sure – Delete": "I’m sure – Delete", "The collection was updated": "The collection was updated", "You can edit the name and other details at any time, however doing so often might confuse your team mates.": "You can edit the name and other details at any time, however doing so often might confuse your team mates.", "Name": "Name", @@ -257,6 +261,9 @@ "Public sharing is currently disabled in the team security settings.": "Public sharing is currently disabled in the team security settings.", "Saving": "Saving", "Save": "Save", + "Exporting the collection {{collectionName}} may take a few seconds. Your documents will be downloaded as a zip of folders with files in Markdown format.": "Exporting the collection {{collectionName}} may take a few seconds. Your documents will be downloaded as a zip of folders with files in Markdown format.", + "Exporting": "Exporting", + "Export Collection": "Export Collection", "Collections are for grouping your documents. They work best when organized around a topic or internal team — Product or Engineering for example.": "Collections are for grouping your documents. They work best when organized around a topic or internal team — Product or Engineering for example.", "This is the default level of access given to team members, you can give specific users or groups more access once the collection is created.": "This is the default level of access given to team members, you can give specific users or groups more access once the collection is created.", "Creating": "Creating", @@ -323,12 +330,16 @@ "Are you sure you want to delete the {{ documentTitle }} template?": "Are you sure you want to delete the {{ documentTitle }} template?", "Are you sure about that? Deleting the {{ documentTitle }} document will delete all of its history and any nested documents.": "Are you sure about that? Deleting the {{ documentTitle }} document will delete all of its history and any nested documents.", "If you’d like the option of referencing or restoring the {{noun}} in the future, consider archiving it instead.": "If you’d like the option of referencing or restoring the {{noun}} in the future, consider archiving it instead.", - "Deleting": "Deleting", - "I’m sure – Delete": "I’m sure – Delete", "Archiving": "Archiving", + "Document moved": "Document moved", + "Current location": "Current location", + "Choose a new location": "Choose a new location", + "Search collections & documents": "Search collections & documents", "Couldn’t create the document, try again?": "Couldn’t create the document, try again?", "Document permanently deleted": "Document permanently deleted", "Are you sure you want to permanently delete the {{ documentTitle }} document? This action is immediate and cannot be undone.": "Are you sure you want to permanently delete the {{ documentTitle }} document? This action is immediate and cannot be undone.", + "Template created, go ahead and customize it": "Template created, go ahead and customize it", + "Creating a template from {{titleWithDefault}} is a non-destructive action – we'll make a copy of the document and turn it into a template that can be used as a starting point for new documents.": "Creating a template from {{titleWithDefault}} is a non-destructive action – we'll make a copy of the document and turn it into a template that can be used as a starting point for new documents.", "Search documents": "Search documents", "No documents found for your filters.": "No documents found for your filters.", "You’ve not got any drafts at the moment.": "You’ve not got any drafts at the moment.",