fix: Use friendly urls for collections (#2162)
Co-authored-by: Tom Moor <tom.moor@gmail.com>
This commit is contained in:
@@ -4,7 +4,15 @@ import { NewDocumentIcon, PlusIcon, PinIcon, MoreIcon } from "outline-icons";
|
||||
import * as React from "react";
|
||||
import Dropzone from "react-dropzone";
|
||||
import { useTranslation, Trans } from "react-i18next";
|
||||
import { useParams, Redirect, Link, Switch, Route } from "react-router-dom";
|
||||
import {
|
||||
useParams,
|
||||
Redirect,
|
||||
Link,
|
||||
Switch,
|
||||
Route,
|
||||
useHistory,
|
||||
useRouteMatch,
|
||||
} from "react-router-dom";
|
||||
import styled, { css } from "styled-components";
|
||||
import CollectionPermissions from "scenes/CollectionPermissions";
|
||||
import Search from "scenes/Search";
|
||||
@@ -29,6 +37,8 @@ import Subheading from "components/Subheading";
|
||||
import Tab from "components/Tab";
|
||||
import Tabs from "components/Tabs";
|
||||
import Tooltip from "components/Tooltip";
|
||||
import Collection from "../models/Collection";
|
||||
import { updateCollectionUrl } from "../utils/routeHelpers";
|
||||
import useCurrentTeam from "hooks/useCurrentTeam";
|
||||
import useImportDocument from "hooks/useImportDocument";
|
||||
import useStores from "hooks/useStores";
|
||||
@@ -38,6 +48,8 @@ import { newDocumentUrl, collectionUrl } from "utils/routeHelpers";
|
||||
|
||||
function CollectionScene() {
|
||||
const params = useParams();
|
||||
const history = useHistory();
|
||||
const match = useRouteMatch();
|
||||
const { t } = useTranslation();
|
||||
const { documents, policies, collections, ui } = useStores();
|
||||
const team = useCurrentTeam();
|
||||
@@ -45,11 +57,21 @@ function CollectionScene() {
|
||||
const [error, setError] = React.useState();
|
||||
const [permissionsModalOpen, setPermissionsModalOpen] = React.useState(false);
|
||||
|
||||
const collectionId = params.id || "";
|
||||
const collection = collections.get(collectionId);
|
||||
const can = policies.abilities(collectionId || "");
|
||||
const id = params.id || "";
|
||||
const collection: ?Collection =
|
||||
collections.getByUrl(id) || collections.get(id);
|
||||
const can = policies.abilities(collection?.id || "");
|
||||
const canUser = policies.abilities(team.id);
|
||||
const { handleFiles, isImporting } = useImportDocument(collectionId);
|
||||
const { handleFiles, isImporting } = useImportDocument(collection?.id || "");
|
||||
|
||||
React.useEffect(() => {
|
||||
if (collection) {
|
||||
const canonicalUrl = updateCollectionUrl(match.url, collection);
|
||||
if (match.url !== canonicalUrl) {
|
||||
history.replace(canonicalUrl);
|
||||
}
|
||||
}
|
||||
}, [collection, history, id, match.url]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (collection) {
|
||||
@@ -59,8 +81,10 @@ function CollectionScene() {
|
||||
|
||||
React.useEffect(() => {
|
||||
setError(null);
|
||||
documents.fetchPinned({ collectionId });
|
||||
}, [documents, collectionId]);
|
||||
if (collection) {
|
||||
documents.fetchPinned({ collectionId: collection.id });
|
||||
}
|
||||
}, [documents, collection]);
|
||||
|
||||
React.useEffect(() => {
|
||||
async function load() {
|
||||
@@ -68,7 +92,7 @@ function CollectionScene() {
|
||||
try {
|
||||
setError(null);
|
||||
setFetching(true);
|
||||
await collections.fetch(collectionId);
|
||||
await collections.fetch(id);
|
||||
} catch (err) {
|
||||
setError(err);
|
||||
} finally {
|
||||
@@ -77,7 +101,7 @@ function CollectionScene() {
|
||||
}
|
||||
}
|
||||
load();
|
||||
}, [collections, isFetching, collection, error, collectionId, can]);
|
||||
}, [collections, isFetching, collection, error, id, can]);
|
||||
|
||||
useUnmount(ui.clearActiveCollection);
|
||||
|
||||
@@ -124,7 +148,7 @@ function CollectionScene() {
|
||||
source="collection"
|
||||
placeholder={`${t("Search in collection")}…`}
|
||||
label={`${t("Search in collection")}…`}
|
||||
collectionId={collectionId}
|
||||
collectionId={collection.id}
|
||||
/>
|
||||
</Action>
|
||||
{can.update && (
|
||||
@@ -257,27 +281,27 @@ function CollectionScene() {
|
||||
)}
|
||||
|
||||
<Tabs>
|
||||
<Tab to={collectionUrl(collection.id)} exact>
|
||||
<Tab to={collectionUrl(collection.url)} exact>
|
||||
{t("Documents")}
|
||||
</Tab>
|
||||
<Tab to={collectionUrl(collection.id, "updated")} exact>
|
||||
<Tab to={collectionUrl(collection.url, "updated")} exact>
|
||||
{t("Recently updated")}
|
||||
</Tab>
|
||||
<Tab to={collectionUrl(collection.id, "published")} exact>
|
||||
<Tab to={collectionUrl(collection.url, "published")} exact>
|
||||
{t("Recently published")}
|
||||
</Tab>
|
||||
<Tab to={collectionUrl(collection.id, "old")} exact>
|
||||
<Tab to={collectionUrl(collection.url, "old")} exact>
|
||||
{t("Least recently updated")}
|
||||
</Tab>
|
||||
<Tab
|
||||
to={collectionUrl(collection.id, "alphabetical")}
|
||||
to={collectionUrl(collection.url, "alphabetical")}
|
||||
exact
|
||||
>
|
||||
{t("A–Z")}
|
||||
</Tab>
|
||||
</Tabs>
|
||||
<Switch>
|
||||
<Route path={collectionUrl(collection.id, "alphabetical")}>
|
||||
<Route path={collectionUrl(collection.url, "alphabetical")}>
|
||||
<PaginatedDocumentList
|
||||
key="alphabetical"
|
||||
documents={documents.alphabeticalInCollection(
|
||||
@@ -288,7 +312,7 @@ function CollectionScene() {
|
||||
showPin
|
||||
/>
|
||||
</Route>
|
||||
<Route path={collectionUrl(collection.id, "old")}>
|
||||
<Route path={collectionUrl(collection.url, "old")}>
|
||||
<PaginatedDocumentList
|
||||
key="old"
|
||||
documents={documents.leastRecentlyUpdatedInCollection(
|
||||
@@ -299,12 +323,12 @@ function CollectionScene() {
|
||||
showPin
|
||||
/>
|
||||
</Route>
|
||||
<Route path={collectionUrl(collection.id, "recent")}>
|
||||
<Route path={collectionUrl(collection.url, "recent")}>
|
||||
<Redirect
|
||||
to={collectionUrl(collection.id, "published")}
|
||||
to={collectionUrl(collection.url, "published")}
|
||||
/>
|
||||
</Route>
|
||||
<Route path={collectionUrl(collection.id, "published")}>
|
||||
<Route path={collectionUrl(collection.url, "published")}>
|
||||
<PaginatedDocumentList
|
||||
key="published"
|
||||
documents={documents.recentlyPublishedInCollection(
|
||||
@@ -316,7 +340,7 @@ function CollectionScene() {
|
||||
showPin
|
||||
/>
|
||||
</Route>
|
||||
<Route path={collectionUrl(collection.id, "updated")}>
|
||||
<Route path={collectionUrl(collection.url, "updated")}>
|
||||
<PaginatedDocumentList
|
||||
key="updated"
|
||||
documents={documents.recentlyUpdatedInCollection(
|
||||
@@ -327,7 +351,7 @@ function CollectionScene() {
|
||||
showPin
|
||||
/>
|
||||
</Route>
|
||||
<Route path={collectionUrl(collection.id)} exact>
|
||||
<Route path={collectionUrl(collection.url)} exact>
|
||||
<PaginatedDocumentList
|
||||
documents={documents.rootInCollection(collection.id)}
|
||||
fetch={documents.fetchPage}
|
||||
|
||||
@@ -214,10 +214,7 @@ class DataLoader extends React.Component<Props> {
|
||||
const isMove = this.props.location.pathname.match(/move$/);
|
||||
const canRedirect = !revisionId && !isMove && !shareId;
|
||||
if (canRedirect) {
|
||||
const canonicalUrl = updateDocumentUrl(
|
||||
this.props.match.url,
|
||||
document.url
|
||||
);
|
||||
const canonicalUrl = updateDocumentUrl(this.props.match.url, document);
|
||||
if (this.props.location.pathname !== canonicalUrl) {
|
||||
this.props.history.replace(canonicalUrl);
|
||||
}
|
||||
|
||||
@@ -36,7 +36,6 @@ import { isCustomDomain } from "utils/domains";
|
||||
import { emojiToUrl } from "utils/emoji";
|
||||
import { meta } from "utils/keyboard";
|
||||
import {
|
||||
collectionUrl,
|
||||
documentMoveUrl,
|
||||
documentHistoryUrl,
|
||||
editDocumentUrl,
|
||||
@@ -291,15 +290,7 @@ class DocumentScene extends React.Component<Props> {
|
||||
};
|
||||
|
||||
goBack = () => {
|
||||
let url;
|
||||
if (this.props.document.url) {
|
||||
url = this.props.document.url;
|
||||
} else if (this.props.match.params.id) {
|
||||
url = collectionUrl(this.props.match.params.id);
|
||||
}
|
||||
if (url) {
|
||||
this.props.history.push(url);
|
||||
}
|
||||
this.props.history.push(this.props.document.url);
|
||||
};
|
||||
|
||||
render() {
|
||||
|
||||
@@ -17,12 +17,13 @@ type Props = {
|
||||
|
||||
function DocumentDelete({ document, onSubmit }: Props) {
|
||||
const { t } = useTranslation();
|
||||
const { ui, documents } = useStores();
|
||||
const { ui, documents, collections } = useStores();
|
||||
const history = useHistory();
|
||||
const [isDeleting, setDeleting] = React.useState(false);
|
||||
const [isArchiving, setArchiving] = React.useState(false);
|
||||
const { showToast } = ui;
|
||||
const canArchive = !document.isDraft && !document.isArchived;
|
||||
const collection = collections.get(document.collectionId);
|
||||
|
||||
const handleSubmit = React.useCallback(
|
||||
async (ev: SyntheticEvent<>) => {
|
||||
@@ -45,7 +46,7 @@ function DocumentDelete({ document, onSubmit }: Props) {
|
||||
}
|
||||
|
||||
// otherwise, redirect to the collection home
|
||||
history.push(collectionUrl(document.collectionId));
|
||||
history.push(collectionUrl(collection?.url || "/"));
|
||||
}
|
||||
onSubmit();
|
||||
} catch (err) {
|
||||
@@ -54,7 +55,7 @@ function DocumentDelete({ document, onSubmit }: Props) {
|
||||
setDeleting(false);
|
||||
}
|
||||
},
|
||||
[showToast, onSubmit, ui, document, documents, history]
|
||||
[showToast, onSubmit, ui, document, documents, history, collection]
|
||||
);
|
||||
|
||||
const handleArchive = React.useCallback(
|
||||
|
||||
@@ -1,58 +1,57 @@
|
||||
// @flow
|
||||
import { inject } from "mobx-react";
|
||||
import { observer } from "mobx-react";
|
||||
import queryString from "query-string";
|
||||
import * as React from "react";
|
||||
import {
|
||||
type RouterHistory,
|
||||
type Location,
|
||||
type Match,
|
||||
} from "react-router-dom";
|
||||
import DocumentsStore from "stores/DocumentsStore";
|
||||
import UiStore from "stores/UiStore";
|
||||
import { useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useHistory, useLocation, useRouteMatch } from "react-router-dom";
|
||||
import CenteredContent from "components/CenteredContent";
|
||||
import Flex from "components/Flex";
|
||||
import LoadingPlaceholder from "components/LoadingPlaceholder";
|
||||
import useStores from "hooks/useStores";
|
||||
import { editDocumentUrl } from "utils/routeHelpers";
|
||||
|
||||
type Props = {
|
||||
history: RouterHistory,
|
||||
location: Location,
|
||||
documents: DocumentsStore,
|
||||
ui: UiStore,
|
||||
match: Match,
|
||||
};
|
||||
function DocumentNew() {
|
||||
const history = useHistory();
|
||||
const location = useLocation();
|
||||
const match = useRouteMatch();
|
||||
const { t } = useTranslation();
|
||||
const { documents, ui, collections } = useStores();
|
||||
const id = match.params.id || "";
|
||||
|
||||
class DocumentNew extends React.Component<Props> {
|
||||
async componentDidMount() {
|
||||
const params = queryString.parse(this.props.location.search);
|
||||
useEffect(() => {
|
||||
async function createDocument() {
|
||||
const params = queryString.parse(location.search);
|
||||
try {
|
||||
const collection = await collections.fetch(id);
|
||||
|
||||
try {
|
||||
const document = await this.props.documents.create({
|
||||
collectionId: this.props.match.params.id,
|
||||
parentDocumentId: params.parentDocumentId,
|
||||
templateId: params.templateId,
|
||||
template: params.template,
|
||||
title: "",
|
||||
text: "",
|
||||
});
|
||||
this.props.history.replace(editDocumentUrl(document));
|
||||
} catch (err) {
|
||||
this.props.ui.showToast("Couldn’t create the document, try again?", {
|
||||
type: "error",
|
||||
});
|
||||
this.props.history.goBack();
|
||||
const document = await documents.create({
|
||||
collectionId: collection.id,
|
||||
parentDocumentId: params.parentDocumentId,
|
||||
templateId: params.templateId,
|
||||
template: params.template,
|
||||
title: "",
|
||||
text: "",
|
||||
});
|
||||
|
||||
history.replace(editDocumentUrl(document));
|
||||
} catch (err) {
|
||||
ui.showToast(t("Couldn’t create the document, try again?"), {
|
||||
type: "error",
|
||||
});
|
||||
history.goBack();
|
||||
}
|
||||
}
|
||||
}
|
||||
createDocument();
|
||||
});
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Flex column auto>
|
||||
<CenteredContent>
|
||||
<LoadingPlaceholder />
|
||||
</CenteredContent>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Flex column auto>
|
||||
<CenteredContent>
|
||||
<LoadingPlaceholder />
|
||||
</CenteredContent>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
export default inject("documents", "ui")(DocumentNew);
|
||||
export default observer(DocumentNew);
|
||||
|
||||
Reference in New Issue
Block a user