feat: Update default collection tab (#1821)
* feat: Allow listing root level documents only via documents.list * feat: New tab on collection home * update tab layout * fix: Correctly sort index sorted documents.list * revert: Tab layout changes * fix: Missing route for recently published fix: Redirect unknown tabs
This commit is contained in:
@@ -278,9 +278,12 @@ class CollectionScene extends React.Component<Props> {
|
||||
|
||||
<Tabs>
|
||||
<Tab to={collectionUrl(collection.id)} exact>
|
||||
{t("Documents")}
|
||||
</Tab>
|
||||
<Tab to={collectionUrl(collection.id, "updated")} exact>
|
||||
{t("Recently updated")}
|
||||
</Tab>
|
||||
<Tab to={collectionUrl(collection.id, "recent")} exact>
|
||||
<Tab to={collectionUrl(collection.id, "published")} exact>
|
||||
{t("Recently published")}
|
||||
</Tab>
|
||||
<Tab to={collectionUrl(collection.id, "old")} exact>
|
||||
@@ -313,9 +316,9 @@ class CollectionScene extends React.Component<Props> {
|
||||
showPin
|
||||
/>
|
||||
</Route>
|
||||
<Route path={collectionUrl(collection.id, "recent")}>
|
||||
<Route path={collectionUrl(collection.id, "published")}>
|
||||
<PaginatedDocumentList
|
||||
key="recent"
|
||||
key="published"
|
||||
documents={documents.recentlyPublishedInCollection(
|
||||
collection.id
|
||||
)}
|
||||
@@ -325,8 +328,9 @@ class CollectionScene extends React.Component<Props> {
|
||||
showPin
|
||||
/>
|
||||
</Route>
|
||||
<Route path={collectionUrl(collection.id)}>
|
||||
<Route path={collectionUrl(collection.id, "updated")}>
|
||||
<PaginatedDocumentList
|
||||
key="updated"
|
||||
documents={documents.recentlyUpdatedInCollection(
|
||||
collection.id
|
||||
)}
|
||||
@@ -335,6 +339,22 @@ class CollectionScene extends React.Component<Props> {
|
||||
showPin
|
||||
/>
|
||||
</Route>
|
||||
<Route path={collectionUrl(collection.id)} exact>
|
||||
<PaginatedDocumentList
|
||||
documents={documents.rootInCollection(collection.id)}
|
||||
fetch={documents.fetchPage}
|
||||
options={{
|
||||
collectionId: collection.id,
|
||||
parentDocumentId: null,
|
||||
sort: collection.sort.field,
|
||||
direction: "ASC",
|
||||
}}
|
||||
showPin
|
||||
/>
|
||||
</Route>
|
||||
<Route>
|
||||
<Redirect to={collectionUrl(collection.id)} />
|
||||
</Route>
|
||||
</Switch>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -111,6 +111,15 @@ export default class DocumentsStore extends BaseStore<Document> {
|
||||
);
|
||||
}
|
||||
|
||||
rootInCollection(collectionId: string): Document[] {
|
||||
const collection = this.rootStore.collections.get(collectionId);
|
||||
if (!collection) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return compact(collection.documents.map((node) => this.get(node.id)));
|
||||
}
|
||||
|
||||
leastRecentlyUpdatedInCollection(collectionId: string): Document[] {
|
||||
return orderBy(this.inCollection(collectionId), "updatedAt", "asc");
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ const { authorize, cannot } = policy;
|
||||
const router = new Router();
|
||||
|
||||
router.post("documents.list", auth(), pagination(), async (ctx) => {
|
||||
const {
|
||||
let {
|
||||
sort = "updatedAt",
|
||||
template,
|
||||
backlinkDocumentId,
|
||||
@@ -70,6 +70,7 @@ router.post("documents.list", auth(), pagination(), async (ctx) => {
|
||||
where = { ...where, createdById };
|
||||
}
|
||||
|
||||
let documentIds = [];
|
||||
// if a specific collection is passed then we need to check auth to view it
|
||||
if (collectionId) {
|
||||
ctx.assertUuid(collectionId, "collection must be a UUID");
|
||||
@@ -80,6 +81,15 @@ router.post("documents.list", auth(), pagination(), async (ctx) => {
|
||||
}).findByPk(collectionId);
|
||||
authorize(user, "read", collection);
|
||||
|
||||
// index sort is special because it uses the order of the documents in the
|
||||
// collection.documentStructure rather than a database column
|
||||
if (sort === "index") {
|
||||
documentIds = collection.documentStructure
|
||||
.map((node) => node.id)
|
||||
.slice(ctx.state.pagination.offset, ctx.state.pagination.limit);
|
||||
where = { ...where, id: documentIds };
|
||||
}
|
||||
|
||||
// otherwise, filter by all collections the user has access to
|
||||
} else {
|
||||
const collectionIds = await user.collectionIds();
|
||||
@@ -91,6 +101,12 @@ router.post("documents.list", auth(), pagination(), async (ctx) => {
|
||||
where = { ...where, parentDocumentId };
|
||||
}
|
||||
|
||||
// Explicitly passing 'null' as the parentDocumentId allows listing documents
|
||||
// that have no parent document (aka they are at the root of the collection)
|
||||
if (parentDocumentId === null) {
|
||||
where = { ...where, parentDocumentId: { [Op.eq]: null } };
|
||||
}
|
||||
|
||||
if (backlinkDocumentId) {
|
||||
ctx.assertUuid(backlinkDocumentId, "backlinkDocumentId must be a UUID");
|
||||
|
||||
@@ -107,6 +123,10 @@ router.post("documents.list", auth(), pagination(), async (ctx) => {
|
||||
};
|
||||
}
|
||||
|
||||
if (sort === "index") {
|
||||
sort = "updatedAt";
|
||||
}
|
||||
|
||||
// add the users starred state to the response by default
|
||||
const starredScope = { method: ["withStarred", user.id] };
|
||||
const collectionScope = { method: ["withCollection", user.id] };
|
||||
@@ -123,6 +143,14 @@ router.post("documents.list", auth(), pagination(), async (ctx) => {
|
||||
limit: ctx.state.pagination.limit,
|
||||
});
|
||||
|
||||
// index sort is special because it uses the order of the documents in the
|
||||
// collection.documentStructure rather than a database column
|
||||
if (documentIds.length) {
|
||||
documents.sort(
|
||||
(a, b) => documentIds.indexOf(a.id) - documentIds.indexOf(b.id)
|
||||
);
|
||||
}
|
||||
|
||||
const data = await Promise.all(
|
||||
documents.map((document) => presentDocument(document))
|
||||
);
|
||||
|
||||
@@ -433,7 +433,27 @@ describe("#documents.list", () => {
|
||||
expect(body.data[0].id).toEqual(document.id);
|
||||
});
|
||||
|
||||
it("should not return unpublished documents", async () => {
|
||||
it("should allow filtering documents with no parent", async () => {
|
||||
const { user, document } = await seed();
|
||||
await buildDocument({
|
||||
title: "child document",
|
||||
text: "random text",
|
||||
parentDocumentId: document.id,
|
||||
userId: user.id,
|
||||
teamId: user.teamId,
|
||||
});
|
||||
|
||||
const res = await server.post("/api/documents.list", {
|
||||
body: { token: user.getJwtToken(), parentDocumentId: null },
|
||||
});
|
||||
const body = await res.json();
|
||||
|
||||
expect(res.status).toEqual(200);
|
||||
expect(body.data.length).toEqual(1);
|
||||
expect(body.data[0].id).toEqual(document.id);
|
||||
});
|
||||
|
||||
it("should not return draft documents", async () => {
|
||||
const { user, document } = await seed();
|
||||
document.publishedAt = null;
|
||||
await document.save();
|
||||
@@ -493,6 +513,32 @@ describe("#documents.list", () => {
|
||||
expect(body.data[1].id).toEqual(anotherDoc.id);
|
||||
});
|
||||
|
||||
it("should allow sorting by collection index", async () => {
|
||||
const { user, document, collection } = await seed();
|
||||
const anotherDoc = await buildDocument({
|
||||
title: "another document",
|
||||
text: "random text",
|
||||
userId: user.id,
|
||||
teamId: user.teamId,
|
||||
collectionId: collection.id,
|
||||
});
|
||||
await collection.addDocumentToStructure(anotherDoc, 0);
|
||||
|
||||
const res = await server.post("/api/documents.list", {
|
||||
body: {
|
||||
token: user.getJwtToken(),
|
||||
collectionId: collection.id,
|
||||
sort: "index",
|
||||
direction: "ASC",
|
||||
},
|
||||
});
|
||||
const body = await res.json();
|
||||
|
||||
expect(res.status).toEqual(200);
|
||||
expect(body.data[0].id).toEqual(anotherDoc.id);
|
||||
expect(body.data[1].id).toEqual(document.id);
|
||||
});
|
||||
|
||||
it("should allow filtering by collection", async () => {
|
||||
const { user, document } = await seed();
|
||||
const res = await server.post("/api/documents.list", {
|
||||
|
||||
@@ -249,7 +249,6 @@
|
||||
"Edit {{noun}}": "Edit {{noun}}",
|
||||
"New from template": "New from template",
|
||||
"Publish": "Publish",
|
||||
"Publish document": "Publish document",
|
||||
"Publishing": "Publishing",
|
||||
"Are you sure you want to delete the <2>{{documentTitle}}</2> template?": "Are you sure you want to delete the <2>{{documentTitle}}</2> template?",
|
||||
"Are you sure about that? Deleting the <2>{{documentTitle}}</2> document will delete all of its history and any nested documents.": "Are you sure about that? Deleting the <2>{{documentTitle}}</2> document will delete all of its history and any nested documents.",
|
||||
|
||||
Reference in New Issue
Block a user