DocumentPreview Improves (#141)

* Show collection name in search results
Highlight documents modified since last edited
Move views to scope

* Allow ESC key to work on Search page when input not focused

* Update document title with search query
Show loading indicator for search results

* WIP

* 💚 ?

* 💚

* Address PR feedback
This commit is contained in:
Tom Moor
2017-07-16 09:24:45 -07:00
committed by GitHub
parent b53f28fee8
commit 3b2ad193d5
9 changed files with 66 additions and 36 deletions

View File

@@ -50,10 +50,7 @@ class DocumentPreview extends Component {
<DocumentLink to={document.url} innerRef={innerRef} {...rest}>
<h3>{document.title}</h3>
<PublishingInfo
createdAt={document.createdAt}
createdBy={document.createdBy}
updatedAt={document.updatedAt}
updatedBy={document.updatedBy}
document={document}
collection={showCollection ? document.collection : undefined}
/>
</DocumentLink>

View File

@@ -2,12 +2,14 @@
import React, { Component } from 'react';
import moment from 'moment';
import styled from 'styled-components';
import { color } from 'styles/constants';
import Collection from 'models/Collection';
import Document from 'models/Document';
import type { User } from 'types';
import Flex from 'components/Flex';
const Container = styled(Flex)`
color: #bbb;
color: ${color.slate};
font-size: 13px;
`;
@@ -21,7 +23,7 @@ const Avatar = styled.img`
height: 26px;
flex-shrink: 0;
border-radius: 50%;
border: 2px solid #FFFFFF;
border: 2px solid ${color.white};
margin-right: -13px;
&:first-child {
@@ -29,26 +31,28 @@ const Avatar = styled.img`
}
`;
const Modified = styled.span`
color: ${props => (props.highlight ? color.slateDark : color.slate)};
font-weight: ${props => (props.highlight ? '600' : '400')};
`;
class PublishingInfo extends Component {
props: {
collaborators?: Array<User>,
collection?: Collection,
createdAt: string,
createdBy: User,
updatedAt: string,
updatedBy: User,
document: Document,
views?: number,
};
render() {
const { collaborators, collection, document } = this.props;
const {
collaborators,
collection,
modifiedSinceViewed,
createdAt,
updatedAt,
createdBy,
updatedBy,
} = this.props;
} = document;
return (
<Container align="center">
@@ -68,10 +72,12 @@ class PublishingInfo extends Component {
</span>
: <span>
{updatedBy.name}
{' '}
modified
{' '}
{moment(updatedAt).fromNow()}
<Modified highlight={modifiedSinceViewed}>
{' '}
modified
{' '}
{moment(updatedAt).fromNow()}
</Modified>
</span>}
{collection && <span>&nbsp;in <strong>{collection.name}</strong></span>}
</Container>

View File

@@ -21,6 +21,9 @@ class Document {
collaborators: Array<User>;
collection: $Shape<Collection>;
firstViewedAt: ?string;
lastViewedAt: ?string;
modifiedSinceViewed: ?boolean;
createdAt: string;
createdBy: User;
html: string;
@@ -38,6 +41,10 @@ class Document {
/* Computed */
@computed get modifiedSinceViewed(): boolean {
return !!this.lastViewedAt && this.lastViewedAt < this.updatedAt;
}
@computed get pathToDocument(): Array<string> {
let path;
const traveler = (nodes, previousPath) => {

View File

@@ -157,10 +157,7 @@ type Props = {
<InfoWrapper visible={!isEditing}>
<PublishingInfo
collaborators={this.document.collaborators}
createdAt={this.document.createdAt}
createdBy={this.document.createdBy}
updatedAt={this.document.updatedAt}
updatedBy={this.document.updatedBy}
document={this.document}
/>
</InfoWrapper>
);

View File

@@ -1,6 +1,7 @@
// @flow
import React from 'react';
import ReactDOM from 'react-dom';
import keydown from 'react-keydown';
import { observer } from 'mobx-react';
import _ from 'lodash';
import Flex from 'components/Flex';
@@ -10,6 +11,7 @@ import styled from 'styled-components';
import ArrowKeyNavigation from 'boundless-arrow-key-navigation';
import CenteredContent from 'components/CenteredContent';
import LoadingIndicator from 'components/LoadingIndicator';
import SearchField from './components/SearchField';
import SearchStore from './SearchStore';
@@ -65,12 +67,18 @@ const StyledArrowKeyNavigation = styled(ArrowKeyNavigation)`
}
}
@keydown('esc')
goBack() {
this.props.history.goBack();
}
handleKeyDown = ev => {
// ESC
// Escape
if (ev.which === 27) {
ev.preventDefault();
this.props.history.goBack();
this.goBack();
}
// Down
if (ev.which === 40) {
ev.preventDefault();
@@ -94,13 +102,21 @@ const StyledArrowKeyNavigation = styled(ArrowKeyNavigation)`
this.firstDocument = ref;
};
get title() {
const query = this.store.searchTerm;
const title = 'Search';
if (query) return `${query} - ${title}`;
return title;
}
render() {
const query = this.props.match.params.query;
const hasResults = this.store.documents.length > 0;
return (
<Container auto>
<PageTitle title="Search" />
<PageTitle title={this.title} />
{this.store.isFetching && <LoadingIndicator />}
{this.props.notFound &&
<div>
<h1>Not Found</h1>

View File

@@ -7,7 +7,6 @@ import Document from 'models/Document';
class SearchStore {
@observable documents: Array<Document> = [];
@observable searchTerm: ?string = null;
@observable isFetching = false;
/* Actions */

View File

@@ -101,9 +101,7 @@ router.post('documents.info', auth(), async ctx => {
}
ctx.body = {
data: await presentDocument(ctx, document, {
includeViews: true,
}),
data: await presentDocument(ctx, document),
};
});

View File

@@ -119,6 +119,9 @@ Document.associate = models => {
Document.hasMany(models.Star, {
as: 'starred',
});
Document.hasMany(models.View, {
as: 'views',
});
Document.addScope(
'defaultScope',
{
@@ -130,6 +133,11 @@ Document.associate = models => {
},
{ override: true }
);
Document.addScope('withViews', userId => ({
include: [
{ model: models.View, as: 'views', where: { userId }, required: false },
],
}));
Document.addScope('withStarred', userId => ({
include: [
{ model: models.Star, as: 'starred', where: { userId }, required: false },
@@ -174,7 +182,9 @@ Document.searchForUser = async (user, query, options = {}) => {
model: Document,
})
.map(document => document.id);
return Document.findAll({
const withViewsScope = { method: ['withViews', user.id] };
return Document.scope('defaultScope', withViewsScope).findAll({
where: { id: ids },
});
};

View File

@@ -1,18 +1,16 @@
// @flow
import _ from 'lodash';
import { User, Document, View } from '../models';
import { User, Document } from '../models';
import presentUser from './user';
import presentCollection from './collection';
type Options = {
includeCollaborators?: boolean,
includeViews?: boolean,
};
async function present(ctx: Object, document: Document, options: ?Options) {
options = {
includeCollaborators: true,
includeViews: false,
...options,
};
ctx.cache.set(document.id, document);
@@ -28,6 +26,8 @@ async function present(ctx: Object, document: Document, options: ?Options) {
createdBy: presentUser(ctx, document.createdBy),
updatedAt: document.updatedAt,
updatedBy: presentUser(ctx, document.updatedBy),
firstViewedAt: undefined,
lastViewedAt: undefined,
team: document.teamId,
collaborators: [],
starred: !!document.starred,
@@ -40,10 +40,10 @@ async function present(ctx: Object, document: Document, options: ?Options) {
data.collection = await presentCollection(ctx, document.collection);
}
if (options.includeViews) {
data.views = await View.sum('count', {
where: { documentId: document.id },
});
if (document.views && document.views.length === 1) {
data.views = document.views[0].count;
data.firstViewedAt = document.views[0].createdAt;
data.lastViewedAt = document.views[0].updatedAt;
}
if (options.includeCollaborators) {