Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add filters to drafts #1631

Merged
merged 10 commits into from Nov 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions app/components/PaginatedList.js
@@ -1,5 +1,6 @@
// @flow
import ArrowKeyNavigation from "boundless-arrow-key-navigation";
import { isEqual } from "lodash";
import { observable, action } from "mobx";
import { observer } from "mobx-react";
import * as React from "react";
Expand Down Expand Up @@ -40,6 +41,9 @@ class PaginatedList extends React.Component<Props> {
if (prevProps.fetch !== this.props.fetch) {
this.fetchResults();
}
if (!isEqual(prevProps.options, this.props.options)) {
this.fetchResults();
}
}

fetchResults = async () => {
Expand Down
5 changes: 2 additions & 3 deletions app/components/Sidebar/Main.js
Expand Up @@ -69,7 +69,6 @@ class MainSidebar extends React.Component<Props> {
const { user, team } = auth;
if (!user || !team) return null;

const draftDocumentsCount = documents.drafts.length;
const can = policies.abilities(team.id);

return (
Expand Down Expand Up @@ -123,8 +122,8 @@ class MainSidebar extends React.Component<Props> {
label={
<Drafts align="center">
Drafts
{draftDocumentsCount > 0 && (
<Bubble count={draftDocumentsCount} />
{documents.totalDrafts > 0 && (
<Bubble count={documents.totalDrafts} />
)}
</Drafts>
}
Expand Down
5 changes: 2 additions & 3 deletions app/components/Subheading.js
Expand Up @@ -11,12 +11,11 @@ const H3 = styled.h3`
margin-top: 22px;
margin-bottom: 12px;
line-height: 1;
position: relative;
`;

const Underline = styled("span")`
position: relative;
top: 1px;

margin-top: -1px;
display: inline-block;
font-weight: 500;
font-size: 14px;
Expand Down
101 changes: 92 additions & 9 deletions app/scenes/Drafts.js
@@ -1,37 +1,107 @@
// @flow
import { observer, inject } from "mobx-react";
import { observable } from "mobx";
import { inject, observer } from "mobx-react";
import queryString from "query-string";
import * as React from "react";

import { type RouterHistory } from "react-router-dom";
import styled from "styled-components";
import DocumentsStore from "stores/DocumentsStore";
import CollectionFilter from "scenes/Search/components/CollectionFilter";
import DateFilter from "scenes/Search/components/DateFilter";

import Actions, { Action } from "components/Actions";
import CenteredContent from "components/CenteredContent";
import Empty from "components/Empty";
import Flex from "components/Flex";
import Heading from "components/Heading";
import InputSearch from "components/InputSearch";
import PageTitle from "components/PageTitle";
import PaginatedDocumentList from "components/PaginatedDocumentList";
import Subheading from "components/Subheading";
import NewDocumentMenu from "menus/NewDocumentMenu";
import { type LocationWithState } from "types";

type Props = {
type Props = {|
documents: DocumentsStore,
};
history: RouterHistory,
location: LocationWithState,
|};

@observer
class Drafts extends React.Component<Props> {
@observable params: URLSearchParams = new URLSearchParams(
this.props.location.search
);

componentDidUpdate(prevProps) {
if (prevProps.location.search !== this.props.location.search) {
this.handleQueryChange();
}
}

handleQueryChange = () => {
this.params = new URLSearchParams(this.props.location.search);
};

handleFilterChange = (search) => {
this.props.history.replace({
pathname: this.props.location.pathname,
search: queryString.stringify({
...queryString.parse(this.props.location.search),
...search,
}),
});
};

get collectionId() {
const id = this.params.get("collectionId");
return id ? id : undefined;
}

get dateFilter() {
const id = this.params.get("dateFilter");
return id ? id : undefined;
}

render() {
const { fetchDrafts, drafts } = this.props.documents;
const { drafts, fetchDrafts } = this.props.documents;
const isFiltered = this.collectionId || this.dateFilter;
const options = {
dateFilter: this.dateFilter,
collectionId: this.collectionId,
};

return (
<CenteredContent column auto>
<PageTitle title="Drafts" />
<Heading>Drafts</Heading>
<Subheading>
Documents
<Filters>
<CollectionFilter
collectionId={this.collectionId}
onSelect={(collectionId) =>
this.handleFilterChange({ collectionId })
}
/>
<DateFilter
dateFilter={this.dateFilter}
onSelect={(dateFilter) => this.handleFilterChange({ dateFilter })}
/>
</Filters>
</Subheading>

<PaginatedDocumentList
heading={<Subheading>Documents</Subheading>}
empty={<Empty>You’ve not got any drafts at the moment.</Empty>}
empty={
<Empty>
{isFiltered
? "No documents found for your filters."
: "You’ve not got any drafts at the moment."}
</Empty>
}
fetch={fetchDrafts}
documents={drafts}
showDraft={false}
documents={drafts(options)}
options={options}
showCollection
/>

Expand All @@ -48,4 +118,17 @@ class Drafts extends React.Component<Props> {
}
}

const Filters = styled(Flex)`
opacity: 0.85;
transition: opacity 100ms ease-in-out;
position: absolute;
right: -8px;
bottom: 0;
padding: 0 0 6px;

&:hover {
opacity: 1;
}
`;

export default inject("documents")(Drafts);
26 changes: 23 additions & 3 deletions app/stores/DocumentsStore.js
Expand Up @@ -3,6 +3,7 @@ import invariant from "invariant";
import { find, orderBy, filter, compact, omitBy } from "lodash";
import { observable, action, computed, runInAction } from "mobx";
import { MAX_TITLE_LENGTH } from "shared/constants";
import { subtractDate } from "shared/utils/date";
import naturalSort from "shared/utils/naturalSort";
import BaseStore from "stores/BaseStore";
import RootStore from "stores/RootStore";
Expand Down Expand Up @@ -168,12 +169,31 @@ export default class DocumentsStore extends BaseStore<Document> {
}

@computed
get drafts(): Document[] {
return filter(
get totalDrafts(): number {
return this.drafts().length;
}

drafts = (options = {}): Document[] => {
let drafts = filter(
orderBy(this.all, "updatedAt", "desc"),
(doc) => !doc.publishedAt
);
}

if (options.dateFilter) {
drafts = filter(
drafts,
(draft) =>
new Date(draft.updatedAt) >=
subtractDate(new Date(), options.dateFilter)
);
}

if (options.collectionId) {
drafts = filter(drafts, { collectionId: options.collectionId });
}

return drafts;
};

@computed
get active(): ?Document {
Expand Down
48 changes: 38 additions & 10 deletions server/api/documents.js
@@ -1,6 +1,7 @@
// @flow
import Router from "koa-router";
import Sequelize from "sequelize";
import { subtractDate } from "../../shared/utils/date";
import documentImporter from "../commands/documentImporter";
import documentMover from "../commands/documentMover";
import { NotFoundError, InvalidRequestError } from "../errors";
Expand Down Expand Up @@ -359,24 +360,51 @@ router.post("documents.starred", auth(), pagination(), async (ctx) => {
});

router.post("documents.drafts", auth(), pagination(), async (ctx) => {
let { sort = "updatedAt", direction } = ctx.body;
let { collectionId, dateFilter, sort = "updatedAt", direction } = ctx.body;
if (direction !== "ASC") direction = "DESC";

const user = ctx.state.user;
const collectionIds = await user.collectionIds();

if (collectionId) {
ctx.assertUuid(collectionId, "collectionId must be a UUID");

const collection = await Collection.scope({
method: ["withMembership", user.id],
}).findByPk(collectionId);
authorize(user, "read", collection);
}

const collectionIds = !!collectionId
? [collectionId]
: await user.collectionIds();

const whereConditions = {
userId: user.id,
collectionId: collectionIds,
publishedAt: { [Op.eq]: null },
updatedAt: undefined,
};

if (dateFilter) {
ctx.assertIn(
dateFilter,
["day", "week", "month", "year"],
"dateFilter must be one of day,week,month,year"
);

whereConditions.updatedAt = {
[Op.gte]: subtractDate(new Date(), dateFilter),
};
} else {
delete whereConditions.updatedAt;
}

const collectionScope = { method: ["withCollection", user.id] };
const viewScope = { method: ["withViews", user.id] };
const documents = await Document.scope(
"defaultScope",
collectionScope,
viewScope
collectionScope
).findAll({
where: {
userId: user.id,
collectionId: collectionIds,
publishedAt: { [Op.eq]: null },
},
where: whereConditions,
order: [[sort, direction]],
offset: ctx.state.pagination.offset,
limit: ctx.state.pagination.limit,
Expand Down
23 changes: 23 additions & 0 deletions shared/utils/date.js
@@ -0,0 +1,23 @@
// @flow
import subDays from "date-fns/sub_days";
import subMonth from "date-fns/sub_months";
import subWeek from "date-fns/sub_weeks";
import subYear from "date-fns/sub_years";

export function subtractDate(
date: Date,
period: "day" | "week" | "month" | "year"
) {
switch (period) {
case "day":
return subDays(date, 1);
case "week":
return subWeek(date, 1);
case "month":
return subMonth(date, 1);
case "year":
return subYear(date, 1);
default:
return date;
}
}