decap-cms-core
Version:
Decap CMS core application, see decap-cms package for the main distribution.
259 lines (223 loc) • 7.63 kB
JavaScript
import PropTypes from 'prop-types';
import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import styled from '@emotion/styled';
import { Waypoint } from 'react-waypoint';
import { Map, List, fromJS } from 'immutable';
import { translate } from 'react-polyglot';
import orderBy from 'lodash/orderBy';
import { colors, lengths } from 'decap-cms-ui-default';
import {
selectFields,
selectInferredField,
selectSortDataPath,
} from '../../../reducers/collections';
import { filterNestedEntries } from './EntriesCollection';
import { SortDirection } from '../../../types/redux';
import EntryCard from './EntryCard';
const CardsGrid = styled.ul`
display: flex;
flex-flow: row wrap;
list-style-type: none;
margin-left: -12px;
margin-top: 16px;
margin-bottom: 16px;
`;
const SectionSeparator = styled.div`
width: ${lengths.topCardWidth};
margin: 24px 0 16px 12px;
padding-top: 16px;
border-top: 2px solid ${colors.textFieldBorder};
`;
const SectionHeading = styled.p`
font-size: 16px;
font-weight: 600;
color: ${colors.textLead};
margin: 0 0 8px;
`;
class EntryListing extends React.Component {
static propTypes = {
collections: ImmutablePropTypes.iterable.isRequired,
entries: ImmutablePropTypes.list,
viewStyle: PropTypes.string,
cursor: PropTypes.any.isRequired,
handleCursorActions: PropTypes.func.isRequired,
page: PropTypes.number,
getUnpublishedEntries: PropTypes.func.isRequired,
getWorkflowStatus: PropTypes.func.isRequired,
filterTerm: PropTypes.string,
sortFields: PropTypes.array,
showPublishedEntries: PropTypes.bool,
showUnpublishedEntries: PropTypes.bool,
t: PropTypes.func.isRequired,
};
static defaultProps = {
showPublishedEntries: true,
showUnpublishedEntries: true,
};
componentDidMount() {
// Manually validate PropTypes - React 19 breaking change
PropTypes.checkPropTypes(EntryListing.propTypes, this.props, 'prop', 'EntryListing');
}
hasMore = () => {
const hasMore = this.props.cursor?.actions?.has('append_next');
return hasMore;
};
handleLoadMore = () => {
if (this.hasMore()) {
this.props.handleCursorActions('append_next');
}
};
inferFields = collection => {
const titleField = selectInferredField(collection, 'title');
const descriptionField = selectInferredField(collection, 'description');
const imageField = selectInferredField(collection, 'image');
const fields = selectFields(collection);
const inferredFields = [titleField, descriptionField, imageField];
const remainingFields =
fields && fields.filter(f => inferredFields.indexOf(f.get('name')) === -1);
return { titleField, descriptionField, imageField, remainingFields };
};
sortEntries = (entries, sortFields, collections) => {
if (!sortFields || sortFields.length === 0) {
return entries;
}
const keys = sortFields.map(v => selectSortDataPath(collections, v.get('key')));
const orders = sortFields.map(v =>
v.get('direction') === SortDirection.Ascending ? 'asc' : 'desc',
);
return fromJS(orderBy(entries.toJS(), keys, orders));
};
getUnpublishedEntriesList = () => {
const { entries, collections, filterTerm, sortFields } = this.props;
const collectionName = Map.isMap(collections) ? collections.get('name') : null;
if (!collectionName) {
return List();
}
const unpublishedEntries = this.props.getUnpublishedEntries(collectionName);
if (!unpublishedEntries || unpublishedEntries.length === 0) {
return List();
}
let unpublishedList = List(unpublishedEntries.map(entry => entry));
if (collections.has('nested') && filterTerm) {
const collectionFolder = collections.get('folder');
const subfolders = collections.get('nested').get('subfolders') !== false;
unpublishedList = filterNestedEntries(
filterTerm,
collectionFolder,
unpublishedList,
subfolders,
);
}
const publishedSlugs = entries.map(entry => entry.get('slug')).toSet();
const uniqueUnpublished = unpublishedList.filterNot(entry =>
publishedSlugs.has(entry.get('slug')),
);
return this.sortEntries(uniqueUnpublished, sortFields, collections);
};
renderCardsForSingleCollection = () => {
const {
collections,
viewStyle,
entries,
page,
t,
showPublishedEntries,
showUnpublishedEntries,
} = this.props;
const inferredFields = this.inferFields(collections);
const entryCardProps = { collection: collections, inferredFields, viewStyle };
const publishedCards = showPublishedEntries
? entries.map((entry, idx) => {
const workflowStatus = this.props.getWorkflowStatus(
collections.get('name'),
entry.get('slug'),
);
return (
<EntryCard
{...entryCardProps}
entry={entry}
workflowStatus={workflowStatus}
key={`published-${idx}`}
/>
);
})
: List();
const unpublishedEntries = showUnpublishedEntries ? this.getUnpublishedEntriesList() : List();
if (unpublishedEntries.size === 0) {
if (!showPublishedEntries) {
return null;
}
return (
<CardsGrid>
{publishedCards}
{this.hasMore() && <Waypoint key={page} onEnter={this.handleLoadMore} />}
</CardsGrid>
);
}
const unpublishedCards = unpublishedEntries.map((entry, idx) => {
const workflowStatus = this.props.getWorkflowStatus(
collections.get('name'),
entry.get('slug'),
);
return (
<EntryCard
{...entryCardProps}
entry={entry}
workflowStatus={workflowStatus}
key={`unpublished-${idx}`}
/>
);
});
return (
<React.Fragment>
{showPublishedEntries && (
<CardsGrid>
{publishedCards}
{this.hasMore() && <Waypoint key={page} onEnter={this.handleLoadMore} />}
</CardsGrid>
)}
<SectionSeparator>
<SectionHeading>{t('collection.entries.unpublishedHeader')}</SectionHeading>
</SectionSeparator>
<CardsGrid>{unpublishedCards}</CardsGrid>
</React.Fragment>
);
};
renderCardsForMultipleCollections = () => {
const { collections, entries, page } = this.props;
const isSingleCollectionInList = collections.size === 1;
const entryCards = entries.map((entry, idx) => {
const collectionName = entry.get('collection');
const collection = collections.find(coll => coll.get('name') === collectionName);
const collectionLabel = !isSingleCollectionInList && collection.get('label');
const inferredFields = this.inferFields(collection);
const workflowStatus = this.props.getWorkflowStatus(collectionName, entry.get('slug'));
const entryCardProps = {
collection,
entry,
inferredFields,
collectionLabel,
workflowStatus,
};
return <EntryCard {...entryCardProps} key={idx} />;
});
return (
<CardsGrid>
{entryCards}
{this.hasMore() && <Waypoint key={page} onEnter={this.handleLoadMore} />}
</CardsGrid>
);
};
render() {
const { collections } = this.props;
return (
<div>
{Map.isMap(collections)
? this.renderCardsForSingleCollection()
: this.renderCardsForMultipleCollections()}
</div>
);
}
}
export default translate()(EntryListing);