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 #1473

Closed
Closed
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
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"javascript.validate.enable": false,
"typescript.validate.enable": false,
"editor.formatOnSave": true,
"editor.formatOnSave": false,
"typescript.format.enable": false
}
141 changes: 130 additions & 11 deletions app/scenes/Drafts.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
// @flow
import { observer, inject } from "mobx-react";
import { debounce } from "lodash";
import { observable, action } from "mobx";
import { inject, observer } from "mobx-react";
import queryString from "query-string";
import * as React from "react";

import styled from "styled-components";
import breakpoint from "styled-components-breakpoint";
import DocumentsStore from "stores/DocumentsStore";
import Document from "models/Document";
import CollectionFilter from "scenes/Search/components/CollectionFilter";
import DateFilter from "scenes/Search/components/DateFilter";
import UserFilter from "scenes/Search/components/UserFilter";

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 LoadingIndicator from "components/LoadingIndicator";
import PageTitle from "components/PageTitle";
import PaginatedDocumentList from "components/PaginatedDocumentList";
import Subheading from "components/Subheading";
Expand All @@ -19,21 +30,115 @@ type Props = {

@observer
class Drafts extends React.Component<Props> {
@observable params: URLSearchParams = new URLSearchParams();
@observable isFetching: boolean = false;
@observable drafts: Document[] = [];

componentDidMount() {
this.handleQueryChange();
}

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

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

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

this.fetchResultsDebounced();
};

@action
fetchResults = async () => {
this.isFetching = true;

try {
const result = await this.props.documents.fetchDrafts({
dateFilter: this.dateFilter,
collectionId: this.collectionId,
userId: this.userId,
});

this.drafts = result.map(
(item) => new Document(item, this.props.documents)
);
} finally {
this.isFetching = false;
}
};

fetchResultsDebounced = debounce(this.fetchResults, 350, {
leading: false,
trailing: true,
});

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

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

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

render() {
const { fetchDrafts, drafts } = this.props.documents;
const { fetchDrafts } = this.props.documents;

return (
<CenteredContent column auto>
<PageTitle title="Drafts" />
<Heading>Drafts</Heading>
<PaginatedDocumentList
heading={<Subheading>Documents</Subheading>}
empty={<Empty>You’ve not got any drafts at the moment.</Empty>}
fetch={fetchDrafts}
documents={drafts}
showDraft={false}
showCollection
/>
<Filters>
<CollectionFilter
collectionId={this.collectionId}
onSelect={(collectionId) =>
this.handleFilterChange({ collectionId })
}
/>
<UserFilter
userId={this.userId}
onSelect={(userId) => this.handleFilterChange({ userId })}
/>
<DateFilter
dateFilter={this.dateFilter}
onSelect={(dateFilter) => this.handleFilterChange({ dateFilter })}
/>
</Filters>
{this.isFetching ? (
<LoadingIndicator />
) : (
<PaginatedDocumentList
heading={<Subheading>Documents</Subheading>}
empty={<Empty>You’ve not got any drafts at the moment.</Empty>}
fetch={fetchDrafts}
documents={this.drafts}
options={{
dateFilter: this.dateFilter,
collectionId: this.collectionId,
userId: this.userId,
}}
showCollection
/>
)}

<Actions align="center" justify="flex-end">
<Action>
Expand All @@ -48,4 +153,18 @@ class Drafts extends React.Component<Props> {
}
}

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

${breakpoint("tablet")`
padding: 0;
`};

&:hover {
opacity: 1;
}
`;

export default inject("documents")(Drafts);
62 changes: 51 additions & 11 deletions server/api/documents.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,24 @@ import documentMover from "../commands/documentMover";
import { InvalidRequestError } from "../errors";
import auth from "../middlewares/authentication";
import {
Backlink,
Collection,
Document,
Event,
Revision,
Share,
Star,
View,
Revision,
Backlink,
User,
View,
} from "../models";
import policy from "../policies";
import {
presentDocument,
presentCollection,
presentDocument,
presentPolicies,
} from "../presenters";
import { sequelize } from "../sequelize";
import { subtractDate } from "../utils/date";
import pagination from "./middlewares/pagination";

const Op = Sequelize.Op;
Expand Down Expand Up @@ -341,22 +342,61 @@ router.post("documents.starred", auth(), pagination(), async (ctx) => {
});

router.post("documents.drafts", auth(), pagination(), async (ctx) => {
let { sort = "updatedAt", direction } = ctx.body;
let {
collectionId,
userId,
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);
}

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

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

const whereConditions = {
userId: 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 documents = await Document.scope(
"defaultScope",
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
24 changes: 24 additions & 0 deletions server/utils/date.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// @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;
}
}