diff --git a/.gitignore b/.gitignore
index 01ce5e521df5..5d55c107daf0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,3 +20,4 @@ coverage/
.temp/
storybook-static/
.nx
+.claude/settings.local.json
diff --git a/packages/decap-cms-core/src/components/Collection/Entries/Entries.js b/packages/decap-cms-core/src/components/Collection/Entries/Entries.js
index 0ef11da57274..12edd6d0d1fd 100644
--- a/packages/decap-cms-core/src/components/Collection/Entries/Entries.js
+++ b/packages/decap-cms-core/src/components/Collection/Entries/Entries.js
@@ -29,6 +29,7 @@ function Entries({
getWorkflowStatus,
getUnpublishedEntries,
filterTerm,
+ sortFields,
}) {
const loadingMessages = [
t('collection.entries.loadingEntries'),
@@ -54,6 +55,7 @@ function Entries({
getWorkflowStatus={getWorkflowStatus}
getUnpublishedEntries={getUnpublishedEntries}
filterTerm={filterTerm}
+ sortFields={sortFields}
/>
{isFetching && page !== undefined && entries.size > 0 ? (
{t('collection.entries.loadingEntries')}
@@ -77,6 +79,7 @@ Entries.propTypes = {
getWorkflowStatus: PropTypes.func,
getUnpublishedEntries: PropTypes.func,
filterTerm: PropTypes.string,
+ sortFields: ImmutablePropTypes.list,
};
export default translate()(Entries);
diff --git a/packages/decap-cms-core/src/components/Collection/Entries/EntriesCollection.js b/packages/decap-cms-core/src/components/Collection/Entries/EntriesCollection.js
index 8847f3b7c242..b87188dd3759 100644
--- a/packages/decap-cms-core/src/components/Collection/Entries/EntriesCollection.js
+++ b/packages/decap-cms-core/src/components/Collection/Entries/EntriesCollection.js
@@ -18,6 +18,7 @@ import {
selectEntriesLoaded,
selectIsFetching,
selectGroups,
+ selectEntriesSortFields,
} from '../../../reducers/entries';
import { selectUnpublishedEntry, selectUnpublishedEntriesByStatus } from '../../../reducers';
import { selectCollectionEntriesCursor } from '../../../reducers/cursors';
@@ -144,6 +145,7 @@ export class EntriesCollection extends React.Component {
getWorkflowStatus,
getUnpublishedEntries,
filterTerm,
+ sortFields,
} = this.props;
const EntriesToRender = ({ entries }) => {
@@ -160,6 +162,7 @@ export class EntriesCollection extends React.Component {
getWorkflowStatus={getWorkflowStatus}
getUnpublishedEntries={getUnpublishedEntries}
filterTerm={filterTerm}
+ sortFields={sortFields}
/>
);
};
@@ -206,6 +209,7 @@ function mapStateToProps(state, ownProps) {
let entries = selectEntries(state.entries, collection);
const groups = selectGroups(state.entries, collection);
+ const sortFields = selectEntriesSortFields(state.entries, collection.get('name'));
if (collection.has('nested')) {
const collectionFolder = collection.get('folder');
@@ -233,6 +237,7 @@ function mapStateToProps(state, ownProps) {
page,
entries,
groups,
+ sortFields,
entriesLoaded,
isFetching,
viewStyle,
diff --git a/packages/decap-cms-core/src/components/Collection/Entries/EntryListing.js b/packages/decap-cms-core/src/components/Collection/Entries/EntryListing.js
index c6d0e391457d..2ca6e277bc57 100644
--- a/packages/decap-cms-core/src/components/Collection/Entries/EntryListing.js
+++ b/packages/decap-cms-core/src/components/Collection/Entries/EntryListing.js
@@ -3,10 +3,18 @@ import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import styled from '@emotion/styled';
import { Waypoint } from 'react-waypoint';
-import { Map, List } from 'immutable';
-
-import { selectFields, selectInferredField } from '../../../reducers/collections';
+import { Map, List, fromJS } from 'immutable';
+import { translate } from 'react-polyglot';
+import orderBy from 'lodash/orderBy';
+import { colors } 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`
@@ -18,6 +26,20 @@ const CardsGrid = styled.ul`
margin-bottom: 16px;
`;
+const SectionSeparator = styled.div`
+ width: 100%;
+ margin: 24px 0 16px 12px;
+ padding-top: 16px;
+ border-top: 2px solid ${colors.textFieldBorder};
+`;
+
+const SectionHeading = styled.h3`
+ font-size: 16px;
+ font-weight: 600;
+ color: ${colors.textLead};
+ margin: 0 0 8px;
+`;
+
class EntryListing extends React.Component {
static propTypes = {
collections: ImmutablePropTypes.iterable.isRequired,
@@ -29,6 +51,8 @@ class EntryListing extends React.Component {
getUnpublishedEntries: PropTypes.func.isRequired,
getWorkflowStatus: PropTypes.func.isRequired,
filterTerm: PropTypes.string,
+ sortFields: ImmutablePropTypes.list,
+ t: PropTypes.func.isRequired,
};
componentDidMount() {
@@ -58,18 +82,30 @@ class EntryListing extends React.Component {
return { titleField, descriptionField, imageField, remainingFields };
};
- getAllEntries = () => {
- const { entries, collections, filterTerm } = this.props;
+ sortEntries = (entries, sortFields, collections) => {
+ if (!sortFields || sortFields.size === 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.toArray(), orders.toArray()));
+ };
+
+ getUnpublishedEntriesList = () => {
+ const { entries, collections, filterTerm, sortFields } = this.props;
const collectionName = Map.isMap(collections) ? collections.get('name') : null;
if (!collectionName) {
- return entries;
+ return List();
}
const unpublishedEntries = this.props.getUnpublishedEntries(collectionName);
if (!unpublishedEntries || unpublishedEntries.length === 0) {
- return entries;
+ return List();
}
let unpublishedList = List(unpublishedEntries.map(entry => entry));
@@ -91,25 +127,61 @@ class EntryListing extends React.Component {
publishedSlugs.has(entry.get('slug')),
);
- return entries.concat(uniqueUnpublished);
+ return this.sortEntries(uniqueUnpublished, sortFields, collections);
};
renderCardsForSingleCollection = () => {
- const { collections, viewStyle } = this.props;
- const allEntries = this.getAllEntries();
+ const { collections, viewStyle, entries, t } = this.props;
const inferredFields = this.inferFields(collections);
const entryCardProps = { collection: collections, inferredFields, viewStyle };
- return allEntries.map((entry, idx) => {
+ const publishedCards = entries.map((entry, idx) => {
+ const workflowStatus = this.props.getWorkflowStatus(
+ collections.get('name'),
+ entry.get('slug'),
+ );
+
+ return (
+
+ );
+ });
+
+ const unpublishedEntries = this.getUnpublishedEntriesList();
+
+ if (unpublishedEntries.size === 0) {
+ return publishedCards;
+ }
+
+ const unpublishedCards = unpublishedEntries.map((entry, idx) => {
const workflowStatus = this.props.getWorkflowStatus(
collections.get('name'),
entry.get('slug'),
);
return (
-
+
);
});
+
+ return (
+
+ {publishedCards}
+
+ {t('collection.entries.unpublishedHeader')}
+
+ {unpublishedCards}
+
+ );
};
renderCardsForMultipleCollections = () => {
@@ -148,4 +220,4 @@ class EntryListing extends React.Component {
}
}
-export default EntryListing;
+export default translate()(EntryListing);
diff --git a/packages/decap-cms-core/src/components/Collection/Entries/__tests__/__snapshots__/EntriesCollection.spec.js.snap b/packages/decap-cms-core/src/components/Collection/Entries/__tests__/__snapshots__/EntriesCollection.spec.js.snap
index 71e9ec1b5572..00b9bdb26969 100644
--- a/packages/decap-cms-core/src/components/Collection/Entries/__tests__/__snapshots__/EntriesCollection.spec.js.snap
+++ b/packages/decap-cms-core/src/components/Collection/Entries/__tests__/__snapshots__/EntriesCollection.spec.js.snap
@@ -7,6 +7,7 @@ exports[`EntriesCollection should render connected component 1`] = `
collections="Map { \\"name\\": \\"pages\\", \\"label\\": \\"Pages\\", \\"folder\\": \\"src/pages\\" }"
cursor="[object Object]"
entries="List [ Map { \\"slug\\": \\"index\\", \\"path\\": \\"src/pages/index.md\\", \\"data\\": Map { \\"title\\": \\"Root\\" } }, Map { \\"slug\\": \\"dir1/index\\", \\"path\\": \\"src/pages/dir1/index.md\\", \\"data\\": Map { \\"title\\": \\"File 1\\" } }, Map { \\"slug\\": \\"dir2/index\\", \\"path\\": \\"src/pages/dir2/index.md\\", \\"data\\": Map { \\"title\\": \\"File 2\\" } } ]"
+ sortfields=""
/>
`;
@@ -18,6 +19,7 @@ exports[`EntriesCollection should render show only immediate children for nested
collections="Map { \\"name\\": \\"pages\\", \\"label\\": \\"Pages\\", \\"folder\\": \\"src/pages\\", \\"nested\\": Map { \\"depth\\": 10, \\"subfolders\\": false } }"
cursor="[object Object]"
entries="List [ Map { \\"slug\\": \\"index\\", \\"path\\": \\"src/pages/index.md\\", \\"data\\": Map { \\"title\\": \\"Root\\" } } ]"
+ sortfields=""
/>
`;
@@ -30,6 +32,7 @@ exports[`EntriesCollection should render with applied filter term for nested col
cursor="[object Object]"
entries="List [ Map { \\"slug\\": \\"dir3/dir4/index\\", \\"path\\": \\"src/pages/dir3/dir4/index.md\\", \\"data\\": Map { \\"title\\": \\"File 4\\" } } ]"
filterterm="dir3/dir4"
+ sortfields=""
/>
`;
diff --git a/packages/decap-cms-locales/src/en/index.js b/packages/decap-cms-locales/src/en/index.js
index b41cd36de2fd..00148d7eb1d2 100644
--- a/packages/decap-cms-locales/src/en/index.js
+++ b/packages/decap-cms-locales/src/en/index.js
@@ -313,6 +313,17 @@ const en = {
readyHeader: 'Ready',
currentEntries: '%{smart_count} entry |||| %{smart_count} entries',
},
+ collection: {
+ sidebar: {
+ collections: 'Collections',
+ allCollections: 'All Collections',
+ searchAll: 'Search all',
+ searchIn: 'Search in',
+ },
+ entries: {
+ unpublishedHeader: 'Unpublished Entries',
+ },
+ },
},
};