Added: Recently published view to collection
Added: Infinite scroll to collection
This commit is contained in:
@ -7,12 +7,14 @@ import ArrowKeyNavigation from 'boundless-arrow-key-navigation';
|
|||||||
type Props = {
|
type Props = {
|
||||||
documents: Document[],
|
documents: Document[],
|
||||||
showCollection?: boolean,
|
showCollection?: boolean,
|
||||||
|
showPublished?: boolean,
|
||||||
limit?: number,
|
limit?: number,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function DocumentList({
|
export default function DocumentList({
|
||||||
limit,
|
limit,
|
||||||
showCollection,
|
showCollection,
|
||||||
|
showPublished,
|
||||||
documents,
|
documents,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const items = limit ? documents.splice(0, limit) : documents;
|
const items = limit ? documents.splice(0, limit) : documents;
|
||||||
@ -27,6 +29,7 @@ export default function DocumentList({
|
|||||||
key={document.id}
|
key={document.id}
|
||||||
document={document}
|
document={document}
|
||||||
showCollection={showCollection}
|
showCollection={showCollection}
|
||||||
|
showPublished={showPublished}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</ArrowKeyNavigation>
|
</ArrowKeyNavigation>
|
||||||
|
@ -15,6 +15,7 @@ type Props = {
|
|||||||
highlight?: ?string,
|
highlight?: ?string,
|
||||||
context?: ?string,
|
context?: ?string,
|
||||||
showCollection?: boolean,
|
showCollection?: boolean,
|
||||||
|
showPublished?: boolean,
|
||||||
ref?: *,
|
ref?: *,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -133,6 +134,7 @@ class DocumentPreview extends React.Component<Props> {
|
|||||||
const {
|
const {
|
||||||
document,
|
document,
|
||||||
showCollection,
|
showCollection,
|
||||||
|
showPublished,
|
||||||
highlight,
|
highlight,
|
||||||
context,
|
context,
|
||||||
...rest
|
...rest
|
||||||
@ -173,6 +175,7 @@ class DocumentPreview extends React.Component<Props> {
|
|||||||
<PublishingInfo
|
<PublishingInfo
|
||||||
document={document}
|
document={document}
|
||||||
collection={showCollection ? document.collection : undefined}
|
collection={showCollection ? document.collection : undefined}
|
||||||
|
showPublished={showPublished}
|
||||||
/>
|
/>
|
||||||
</DocumentLink>
|
</DocumentLink>
|
||||||
);
|
);
|
||||||
|
@ -19,11 +19,12 @@ const Modified = styled.span`
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
collection?: Collection,
|
collection?: Collection,
|
||||||
|
showPublished?: boolean,
|
||||||
document: Document,
|
document: Document,
|
||||||
views?: number,
|
views?: number,
|
||||||
};
|
};
|
||||||
|
|
||||||
function PublishingInfo({ collection, document }: Props) {
|
function PublishingInfo({ collection, showPublished, document }: Props) {
|
||||||
const {
|
const {
|
||||||
modifiedSinceViewed,
|
modifiedSinceViewed,
|
||||||
updatedAt,
|
updatedAt,
|
||||||
@ -35,7 +36,7 @@ function PublishingInfo({ collection, document }: Props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Container align="center">
|
<Container align="center">
|
||||||
{publishedAt && neverUpdated ? (
|
{publishedAt && (neverUpdated || showPublished) ? (
|
||||||
<span>
|
<span>
|
||||||
{updatedBy.name} published <Time dateTime={publishedAt} /> ago
|
{updatedBy.name} published <Time dateTime={publishedAt} /> ago
|
||||||
</span>
|
</span>
|
||||||
@ -48,7 +49,7 @@ function PublishingInfo({ collection, document }: Props) {
|
|||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<Modified highlight={modifiedSinceViewed}>
|
<Modified highlight={modifiedSinceViewed}>
|
||||||
modified <Time dateTime={updatedAt} /> ago
|
updated <Time dateTime={updatedAt} /> ago
|
||||||
</Modified>
|
</Modified>
|
||||||
)}
|
)}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
|
@ -11,6 +11,7 @@ import { ListPlaceholder } from 'components/LoadingPlaceholder';
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
showCollection?: boolean,
|
showCollection?: boolean,
|
||||||
|
showPublished?: boolean,
|
||||||
documents: Document[],
|
documents: Document[],
|
||||||
fetch: (options: ?Object) => Promise<*>,
|
fetch: (options: ?Object) => Promise<*>,
|
||||||
options?: Object,
|
options?: Object,
|
||||||
@ -64,11 +65,15 @@ class PaginatedDocumentList extends React.Component<Props> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { showCollection, documents } = this.props;
|
const { showCollection, showPublished, documents } = this.props;
|
||||||
|
|
||||||
return this.isLoaded || documents.length ? (
|
return this.isLoaded || documents.length ? (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<DocumentList documents={documents} showCollection={showCollection} />
|
<DocumentList
|
||||||
|
documents={documents}
|
||||||
|
showCollection={showCollection}
|
||||||
|
showPublished={showPublished}
|
||||||
|
/>
|
||||||
{this.allowLoadMore && (
|
{this.allowLoadMore && (
|
||||||
<Waypoint key={this.offset} onEnter={this.loadMoreResults} />
|
<Waypoint key={this.offset} onEnter={this.loadMoreResults} />
|
||||||
)}
|
)}
|
||||||
|
@ -59,6 +59,7 @@ class CollectionLink extends React.Component<Props> {
|
|||||||
hideDisclosure
|
hideDisclosure
|
||||||
menuOpen={this.menuOpen}
|
menuOpen={this.menuOpen}
|
||||||
label={collection.name}
|
label={collection.name}
|
||||||
|
exact={false}
|
||||||
menu={
|
menu={
|
||||||
<CollectionMenu
|
<CollectionMenu
|
||||||
history={history}
|
history={history}
|
||||||
|
@ -68,6 +68,7 @@ export default function Routes() {
|
|||||||
component={Zapier}
|
component={Zapier}
|
||||||
/>
|
/>
|
||||||
<Route exact path="/settings/export" component={Export} />
|
<Route exact path="/settings/export" component={Export} />
|
||||||
|
<Route exact path="/collections/:id/:tab" component={Collection} />
|
||||||
<Route exact path="/collections/:id" component={Collection} />
|
<Route exact path="/collections/:id" component={Collection} />
|
||||||
<Route exact path={`/d/${slug}`} component={RedirectDocument} />
|
<Route exact path={`/d/${slug}`} component={RedirectDocument} />
|
||||||
<Route
|
<Route
|
||||||
|
@ -2,7 +2,8 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { observable } from 'mobx';
|
import { observable } from 'mobx';
|
||||||
import { observer, inject } from 'mobx-react';
|
import { observer, inject } from 'mobx-react';
|
||||||
import { withRouter, Link } from 'react-router-dom';
|
import { withRouter, Link, Switch, Route } from 'react-router-dom';
|
||||||
|
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import {
|
import {
|
||||||
CollectionIcon,
|
CollectionIcon,
|
||||||
@ -12,7 +13,7 @@ import {
|
|||||||
} from 'outline-icons';
|
} from 'outline-icons';
|
||||||
import RichMarkdownEditor from 'rich-markdown-editor';
|
import RichMarkdownEditor from 'rich-markdown-editor';
|
||||||
|
|
||||||
import { newDocumentUrl } from 'utils/routeHelpers';
|
import { newDocumentUrl, collectionUrl } from 'utils/routeHelpers';
|
||||||
import CollectionsStore from 'stores/CollectionsStore';
|
import CollectionsStore from 'stores/CollectionsStore';
|
||||||
import DocumentsStore from 'stores/DocumentsStore';
|
import DocumentsStore from 'stores/DocumentsStore';
|
||||||
import UiStore from 'stores/UiStore';
|
import UiStore from 'stores/UiStore';
|
||||||
@ -33,6 +34,9 @@ import PageTitle from 'components/PageTitle';
|
|||||||
import Flex from 'shared/components/Flex';
|
import Flex from 'shared/components/Flex';
|
||||||
import Modal from 'components/Modal';
|
import Modal from 'components/Modal';
|
||||||
import CollectionPermissions from 'scenes/CollectionPermissions';
|
import CollectionPermissions from 'scenes/CollectionPermissions';
|
||||||
|
import Tabs from 'components/Tabs';
|
||||||
|
import Tab from 'components/Tab';
|
||||||
|
import PaginatedDocumentList from 'components/PaginatedDocumentList';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
ui: UiStore,
|
ui: UiStore,
|
||||||
@ -69,15 +73,9 @@ class CollectionScene extends React.Component<Props> {
|
|||||||
this.props.ui.setActiveCollection(collection);
|
this.props.ui.setActiveCollection(collection);
|
||||||
this.collection = collection;
|
this.collection = collection;
|
||||||
|
|
||||||
await Promise.all([
|
await this.props.documents.fetchPinned({
|
||||||
this.props.documents.fetchRecentlyUpdated({
|
|
||||||
limit: 10,
|
|
||||||
collection: id,
|
collection: id,
|
||||||
}),
|
});
|
||||||
this.props.documents.fetchPinned({
|
|
||||||
collection: id,
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.isFetching = false;
|
this.isFetching = false;
|
||||||
@ -124,15 +122,14 @@ class CollectionScene extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { documents } = this.props;
|
||||||
|
|
||||||
if (!this.isFetching && !this.collection) {
|
if (!this.isFetching && !this.collection) {
|
||||||
return this.renderNotFound();
|
return this.renderNotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
const pinnedDocuments = this.collection
|
const pinnedDocuments = this.collection
|
||||||
? this.props.documents.pinnedInCollection(this.collection.id)
|
? documents.pinnedInCollection(this.collection.id)
|
||||||
: [];
|
|
||||||
const recentDocuments = this.collection
|
|
||||||
? this.props.documents.recentlyUpdatedInCollection(this.collection.id)
|
|
||||||
: [];
|
: [];
|
||||||
const hasPinnedDocuments = !!pinnedDocuments.length;
|
const hasPinnedDocuments = !!pinnedDocuments.length;
|
||||||
const collection = this.collection;
|
const collection = this.collection;
|
||||||
@ -207,8 +204,36 @@ class CollectionScene extends React.Component<Props> {
|
|||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Subheading>Recently edited</Subheading>
|
<Tabs>
|
||||||
<DocumentList documents={recentDocuments} limit={10} />
|
<Tab to={collectionUrl(collection.id)} exact>
|
||||||
|
Recently updated
|
||||||
|
</Tab>
|
||||||
|
<Tab to={collectionUrl(collection.id, 'recent')} exact>
|
||||||
|
Recently published
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
|
<Switch>
|
||||||
|
<Route path={collectionUrl(collection.id, 'recent')}>
|
||||||
|
<PaginatedDocumentList
|
||||||
|
key="recent"
|
||||||
|
documents={documents.recentlyPublishedInCollection(
|
||||||
|
collection.id
|
||||||
|
)}
|
||||||
|
fetch={documents.fetchRecentlyPublished}
|
||||||
|
options={{ collection: collection.id }}
|
||||||
|
showPublished
|
||||||
|
/>
|
||||||
|
</Route>
|
||||||
|
<Route path={collectionUrl(collection.id)}>
|
||||||
|
<PaginatedDocumentList
|
||||||
|
documents={documents.recentlyUpdatedInCollection(
|
||||||
|
collection.id
|
||||||
|
)}
|
||||||
|
fetch={documents.fetchRecentlyUpdated}
|
||||||
|
options={{ collection: collection.id }}
|
||||||
|
/>
|
||||||
|
</Route>
|
||||||
|
</Switch>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
@ -12,8 +12,8 @@ import CenteredContent from 'components/CenteredContent';
|
|||||||
import PageTitle from 'components/PageTitle';
|
import PageTitle from 'components/PageTitle';
|
||||||
import Tabs from 'components/Tabs';
|
import Tabs from 'components/Tabs';
|
||||||
import Tab from 'components/Tab';
|
import Tab from 'components/Tab';
|
||||||
import TipInvite from 'components/TipInvite';
|
|
||||||
import PaginatedDocumentList from '../components/PaginatedDocumentList';
|
import PaginatedDocumentList from '../components/PaginatedDocumentList';
|
||||||
|
import TipInvite from 'components/TipInvite';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
documents: DocumentsStore,
|
documents: DocumentsStore,
|
||||||
|
@ -67,6 +67,18 @@ export default class DocumentsStore extends BaseStore<Document> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
recentlyPublishedInCollection(collectionId: string): Document[] {
|
||||||
|
return orderBy(
|
||||||
|
filter(
|
||||||
|
Array.from(this.data.values()),
|
||||||
|
document =>
|
||||||
|
document.collectionId === collectionId && !!document.publishedAt
|
||||||
|
),
|
||||||
|
'publishedAt',
|
||||||
|
'desc'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
get starred(): Document[] {
|
get starred(): Document[] {
|
||||||
return filter(this.orderedData, d => d.starred);
|
return filter(this.orderedData, d => d.starred);
|
||||||
@ -126,6 +138,15 @@ export default class DocumentsStore extends BaseStore<Document> {
|
|||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@action
|
||||||
|
fetchRecentlyPublished = async (options: ?PaginationParams): Promise<*> => {
|
||||||
|
return this.fetchNamedPage('list', {
|
||||||
|
sort: 'publishedAt',
|
||||||
|
direction: 'DESC',
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
@action
|
@action
|
||||||
fetchRecentlyViewed = async (options: ?PaginationParams): Promise<*> => {
|
fetchRecentlyViewed = async (options: ?PaginationParams): Promise<*> => {
|
||||||
const data = await this.fetchNamedPage('viewed', options);
|
const data = await this.fetchNamedPage('viewed', options);
|
||||||
|
@ -14,8 +14,10 @@ export function newCollectionUrl(): string {
|
|||||||
return '/collections/new';
|
return '/collections/new';
|
||||||
}
|
}
|
||||||
|
|
||||||
export function collectionUrl(collectionId: string): string {
|
export function collectionUrl(collectionId: string, section: ?string): string {
|
||||||
return `/collections/${collectionId}`;
|
const path = `/collections/${collectionId}`;
|
||||||
|
if (section) return `${path}/${section}`;
|
||||||
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function documentUrl(doc: Document): string {
|
export function documentUrl(doc: Document): string {
|
||||||
|
Reference in New Issue
Block a user