diff --git a/package-lock.json b/package-lock.json index d67858977d29..b9222f63681b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "decap-cms", + "name": "decap-cms-fork", "version": "0.0.0", "lockfileVersion": 3, "requires": true, diff --git a/packages/decap-cms-core/src/components/Collection/Collection.js b/packages/decap-cms-core/src/components/Collection/Collection.js index cdc3c0c111a7..04eb3d5b45ff 100644 --- a/packages/decap-cms-core/src/components/Collection/Collection.js +++ b/packages/decap-cms-core/src/components/Collection/Collection.js @@ -24,6 +24,7 @@ import { selectEntriesGroup, selectViewStyle, } from '../../reducers/entries'; +import { selectCanCreateNewEntry } from '../../reducers'; const CollectionContainer = styled.div` margin: ${lengths.pageMargin}; @@ -53,6 +54,9 @@ export class Collection extends React.Component { sortableFields: PropTypes.array, sort: ImmutablePropTypes.orderedMap, onSortClick: PropTypes.func.isRequired, + canCreate: PropTypes.bool.isRequired, + filterTerm: PropTypes.string, + viewStyle: PropTypes.string, }; componentDidMount() { @@ -99,9 +103,10 @@ export class Collection extends React.Component { group, onChangeViewStyle, viewStyle, + canCreate, } = this.props; - let newEntryUrl = collection.get('create') ? getNewEntryUrl(collectionName) : ''; + let newEntryUrl = canCreate ? getNewEntryUrl(collectionName) : ''; if (newEntryUrl && filterTerm) { newEntryUrl = getNewEntryUrl(collectionName); if (filterTerm) { @@ -167,6 +172,7 @@ function mapStateToProps(state, ownProps) { const filter = selectEntriesFilter(state.entries, collection.get('name')); const group = selectEntriesGroup(state.entries, collection.get('name')); const viewStyle = selectViewStyle(state.entries); + const canCreate = selectCanCreateNewEntry(state, name); return { collection, @@ -183,6 +189,7 @@ function mapStateToProps(state, ownProps) { filter, group, viewStyle, + canCreate, }; } diff --git a/packages/decap-cms-core/src/components/Collection/__tests__/Collection.spec.js b/packages/decap-cms-core/src/components/Collection/__tests__/Collection.spec.js index eca5af8d4023..12b10702a115 100644 --- a/packages/decap-cms-core/src/components/Collection/__tests__/Collection.spec.js +++ b/packages/decap-cms-core/src/components/Collection/__tests__/Collection.spec.js @@ -29,22 +29,26 @@ describe('Collection', () => { view_filters: [], view_groups: [], }); - const props = { + const baseProps = { collections: fromJS([collection]).toOrderedMap(), collection, collectionName: collection.get('name'), t: jest.fn(key => key), onSortClick: jest.fn(), + viewStyle: 'list', }; it('should render with collection without create url', () => { + const props = { ...baseProps, canCreate: false }; const { asFragment } = render( , ); expect(asFragment()).toMatchSnapshot(); }); + it('should render with collection with create url', () => { + const props = { ...baseProps, canCreate: false }; const { asFragment } = render( , ); @@ -53,8 +57,9 @@ describe('Collection', () => { }); it('should render with collection with create url and path', () => { + const props = { ...baseProps, canCreate: false, filterTerm: 'dir1/dir2' }; const { asFragment } = render( - , + , ); expect(asFragment()).toMatchSnapshot(); @@ -62,8 +67,8 @@ describe('Collection', () => { it('should render connected component', () => { const store = mockStore({ - collections: props.collections, - entries: fromJS({}), + collections: baseProps.collections, + entries: fromJS({ pages: { entries: fromJS([]) } }), }); const { asFragment } = renderWithRedux(, { diff --git a/packages/decap-cms-core/src/components/Collection/__tests__/__snapshots__/Collection.spec.js.snap b/packages/decap-cms-core/src/components/Collection/__tests__/__snapshots__/Collection.spec.js.snap index 0cb6a889449c..ff2f422cb077 100644 --- a/packages/decap-cms-core/src/components/Collection/__tests__/__snapshots__/Collection.spec.js.snap +++ b/packages/decap-cms-core/src/components/Collection/__tests__/__snapshots__/Collection.spec.js.snap @@ -64,11 +64,14 @@ exports[`Collection should render with collection with create url 1`] = ` > + - @@ -98,12 +101,15 @@ exports[`Collection should render with collection with create url and path 1`] = > + - @@ -134,9 +140,12 @@ exports[`Collection should render with collection without create url 1`] = ` collection="Map { \\"name\\": \\"pages\\", \\"sortable_fields\\": List [], \\"view_filters\\": List [], \\"view_groups\\": List [], \\"create\\": false }" newentryurl="" /> - + diff --git a/packages/decap-cms-core/src/constants/configSchema.js b/packages/decap-cms-core/src/constants/configSchema.js index df7256268257..23f9380bdc85 100644 --- a/packages/decap-cms-core/src/constants/configSchema.js +++ b/packages/decap-cms-core/src/constants/configSchema.js @@ -290,6 +290,9 @@ function getConfigSchema() { minProperties: 1, }, i18n: i18nCollection, + limit: { + type: 'number', + }, }, required: ['name', 'label'], oneOf: [{ required: ['files'] }, { required: ['folder', 'fields'] }], diff --git a/packages/decap-cms-core/src/reducers/index.ts b/packages/decap-cms-core/src/reducers/index.ts index 946761e2f44f..44b5e10e1018 100644 --- a/packages/decap-cms-core/src/reducers/index.ts +++ b/packages/decap-cms-core/src/reducers/index.ts @@ -2,19 +2,20 @@ import { List } from 'immutable'; import auth from './auth'; import config from './config'; +import collections, * as fromCollections from './collections'; import integrations, * as fromIntegrations from './integrations'; import entries, * as fromEntries from './entries'; import cursors from './cursors'; import editorialWorkflow, * as fromEditorialWorkflow from './editorialWorkflow'; import entryDraft from './entryDraft'; -import collections from './collections'; -import search from './search'; import medias from './medias'; import mediaLibrary from './mediaLibrary'; import deploys, * as fromDeploys from './deploys'; import globalUI from './globalUI'; +import search from './search'; import status from './status'; import notifications from './notifications'; +import { FOLDER } from '../constants/collectionTypes'; import type { Status } from '../constants/publishModes'; import type { State, Collection } from '../types/redux'; @@ -42,8 +43,8 @@ export default reducers; /* * Selectors */ -export function selectEntry(state: State, collection: string, slug: string) { - return fromEntries.selectEntry(state.entries, collection, slug); +export function selectEntry(state: State, collectionName: string, slug: string) { + return fromEntries.selectEntry(state.entries, collectionName, slug); } export function selectEntries(state: State, collection: Collection) { @@ -77,6 +78,29 @@ export function selectUnpublishedSlugs(state: State, collection: string) { return fromEditorialWorkflow.selectUnpublishedSlugs(state.editorialWorkflow, collection); } +export function selectCanCreateNewEntry(state: State, collectionName: string) { + const collection = state.collections.get(collectionName); + + if (!collection || !collection.get('create')) { + return false; + } + + if (collection.get('type') !== FOLDER) { + return fromCollections.selectAllowNewEntries(collection); + } + + const limit = collection.get('limit') as number | undefined; + + if (limit === undefined || limit === null) { + return true; + } + + const entries = fromEntries.selectEntries(state.entries, collection); + const entryCount = entries ? entries.size : 0; + + return entryCount < limit; +} + export function selectIntegration(state: State, collection: string | null, hook: string) { return fromIntegrations.selectIntegration(state.integrations, collection, hook); } diff --git a/packages/decap-cms-core/src/types/redux.ts b/packages/decap-cms-core/src/types/redux.ts index c41bbdd18bb6..76fc3cf1e5f1 100644 --- a/packages/decap-cms-core/src/types/redux.ts +++ b/packages/decap-cms-core/src/types/redux.ts @@ -350,6 +350,7 @@ export interface CmsCollection { view_filters?: ViewFilter[]; view_groups?: ViewGroup[]; i18n?: boolean | CmsI18nConfig; + limit?: number; /** * @deprecated Use sortable_fields instead @@ -639,6 +640,7 @@ type CollectionObject = { nested?: Nested; meta?: Meta; i18n: i18n; + limit?: number; }; export type Collection = StaticallyTypedRecord;