decap-cms-core 3.7.1 → 3.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/decap-cms-core.js +17 -17
- package/dist/decap-cms-core.js.map +1 -1
- package/dist/esm/bootstrap.js +2 -2
- package/dist/esm/components/App/App.js +6 -3
- package/dist/esm/components/App/Header.js +38 -14
- package/dist/esm/components/Collection/Entries/Entries.js +14 -5
- package/dist/esm/components/Collection/Entries/EntriesCollection.js +65 -9
- package/dist/esm/components/Collection/Entries/EntryCard.js +84 -28
- package/dist/esm/components/Collection/Entries/EntryListing.js +46 -11
- package/dist/esm/components/UI/ErrorBoundary.js +2 -2
- package/dist/esm/constants/configSchema.js +14 -0
- package/dist/esm/lib/i18n.js +16 -0
- package/index.d.ts +5 -1
- package/package.json +2 -2
- package/src/components/App/App.js +2 -0
- package/src/components/App/Header.js +27 -0
- package/src/components/Collection/Entries/Entries.js +9 -0
- package/src/components/Collection/Entries/EntriesCollection.js +100 -4
- package/src/components/Collection/Entries/EntryCard.js +82 -3
- package/src/components/Collection/Entries/EntryListing.js +65 -5
- package/src/components/Collection/Entries/__tests__/EntriesCollection.spec.js +26 -18
- package/src/components/Collection/Entries/__tests__/__snapshots__/EntriesCollection.spec.js.snap +1 -0
- package/src/constants/configSchema.js +9 -1
- package/src/lib/i18n.ts +21 -0
- package/src/types/redux.ts +5 -1
|
@@ -3,9 +3,10 @@ import React from 'react';
|
|
|
3
3
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
4
4
|
import styled from '@emotion/styled';
|
|
5
5
|
import { Waypoint } from 'react-waypoint';
|
|
6
|
-
import { Map } from 'immutable';
|
|
6
|
+
import { Map, List } from 'immutable';
|
|
7
7
|
|
|
8
8
|
import { selectFields, selectInferredField } from '../../../reducers/collections';
|
|
9
|
+
import { filterNestedEntries } from './EntriesCollection';
|
|
9
10
|
import EntryCard from './EntryCard';
|
|
10
11
|
|
|
11
12
|
const CardsGrid = styled.ul`
|
|
@@ -17,7 +18,7 @@ const CardsGrid = styled.ul`
|
|
|
17
18
|
margin-bottom: 16px;
|
|
18
19
|
`;
|
|
19
20
|
|
|
20
|
-
|
|
21
|
+
class EntryListing extends React.Component {
|
|
21
22
|
static propTypes = {
|
|
22
23
|
collections: ImmutablePropTypes.iterable.isRequired,
|
|
23
24
|
entries: ImmutablePropTypes.list,
|
|
@@ -25,6 +26,9 @@ export default class EntryListing extends React.Component {
|
|
|
25
26
|
cursor: PropTypes.any.isRequired,
|
|
26
27
|
handleCursorActions: PropTypes.func.isRequired,
|
|
27
28
|
page: PropTypes.number,
|
|
29
|
+
getUnpublishedEntries: PropTypes.func.isRequired,
|
|
30
|
+
getWorkflowStatus: PropTypes.func.isRequired,
|
|
31
|
+
filterTerm: PropTypes.string,
|
|
28
32
|
};
|
|
29
33
|
|
|
30
34
|
componentDidMount() {
|
|
@@ -54,11 +58,58 @@ export default class EntryListing extends React.Component {
|
|
|
54
58
|
return { titleField, descriptionField, imageField, remainingFields };
|
|
55
59
|
};
|
|
56
60
|
|
|
61
|
+
getAllEntries = () => {
|
|
62
|
+
const { entries, collections, filterTerm } = this.props;
|
|
63
|
+
const collectionName = Map.isMap(collections) ? collections.get('name') : null;
|
|
64
|
+
|
|
65
|
+
if (!collectionName) {
|
|
66
|
+
return entries;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const unpublishedEntries = this.props.getUnpublishedEntries(collectionName);
|
|
70
|
+
|
|
71
|
+
if (!unpublishedEntries || unpublishedEntries.length === 0) {
|
|
72
|
+
return entries;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
let unpublishedList = List(unpublishedEntries.map(entry => entry));
|
|
76
|
+
|
|
77
|
+
if (collections.has('nested') && filterTerm) {
|
|
78
|
+
const collectionFolder = collections.get('folder');
|
|
79
|
+
const subfolders = collections.get('nested').get('subfolders') !== false;
|
|
80
|
+
|
|
81
|
+
unpublishedList = filterNestedEntries(
|
|
82
|
+
filterTerm,
|
|
83
|
+
collectionFolder,
|
|
84
|
+
unpublishedList,
|
|
85
|
+
subfolders,
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const publishedSlugs = entries.map(entry => entry.get('slug')).toSet();
|
|
90
|
+
const uniqueUnpublished = unpublishedList.filterNot(entry =>
|
|
91
|
+
publishedSlugs.has(entry.get('slug')),
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
return entries.concat(uniqueUnpublished);
|
|
95
|
+
};
|
|
96
|
+
|
|
57
97
|
renderCardsForSingleCollection = () => {
|
|
58
|
-
const { collections,
|
|
98
|
+
const { collections, viewStyle } = this.props;
|
|
99
|
+
const allEntries = this.getAllEntries();
|
|
59
100
|
const inferredFields = this.inferFields(collections);
|
|
60
101
|
const entryCardProps = { collection: collections, inferredFields, viewStyle };
|
|
61
|
-
|
|
102
|
+
|
|
103
|
+
return allEntries.map((entry, idx) => {
|
|
104
|
+
const workflowStatus = this.props.getWorkflowStatus(
|
|
105
|
+
collections.get('name'),
|
|
106
|
+
entry.get('slug'),
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<EntryCard {...entryCardProps} entry={entry} workflowStatus={workflowStatus} key={idx} />
|
|
111
|
+
);
|
|
112
|
+
});
|
|
62
113
|
};
|
|
63
114
|
|
|
64
115
|
renderCardsForMultipleCollections = () => {
|
|
@@ -69,7 +120,14 @@ export default class EntryListing extends React.Component {
|
|
|
69
120
|
const collection = collections.find(coll => coll.get('name') === collectionName);
|
|
70
121
|
const collectionLabel = !isSingleCollectionInList && collection.get('label');
|
|
71
122
|
const inferredFields = this.inferFields(collection);
|
|
72
|
-
const
|
|
123
|
+
const workflowStatus = this.props.getWorkflowStatus(collectionName, entry.get('slug'));
|
|
124
|
+
const entryCardProps = {
|
|
125
|
+
collection,
|
|
126
|
+
entry,
|
|
127
|
+
inferredFields,
|
|
128
|
+
collectionLabel,
|
|
129
|
+
workflowStatus,
|
|
130
|
+
};
|
|
73
131
|
return <EntryCard {...entryCardProps} key={idx} />;
|
|
74
132
|
});
|
|
75
133
|
};
|
|
@@ -89,3 +147,5 @@ export default class EntryListing extends React.Component {
|
|
|
89
147
|
);
|
|
90
148
|
}
|
|
91
149
|
}
|
|
150
|
+
|
|
151
|
+
export default EntryListing;
|
|
@@ -14,6 +14,19 @@ jest.mock('../Entries', () => 'mock-entries');
|
|
|
14
14
|
const middlewares = [];
|
|
15
15
|
const mockStore = configureStore(middlewares);
|
|
16
16
|
|
|
17
|
+
function createMockStore(collection, entriesArray, additionalState = {}) {
|
|
18
|
+
return mockStore({
|
|
19
|
+
entries: toEntriesState(collection, entriesArray),
|
|
20
|
+
cursors: fromJS({}),
|
|
21
|
+
config: fromJS({ publish_mode: 'simple' }),
|
|
22
|
+
collections: fromJS({ [collection.get('name')]: collection }),
|
|
23
|
+
editorialWorkflow: fromJS({
|
|
24
|
+
pages: { ids: [] },
|
|
25
|
+
}),
|
|
26
|
+
...additionalState,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
17
30
|
function renderWithRedux(component, { store } = {}) {
|
|
18
31
|
function Wrapper({ children }) {
|
|
19
32
|
return <Provider store={store}>{children}</Provider>;
|
|
@@ -70,10 +83,18 @@ describe('EntriesCollection', () => {
|
|
|
70
83
|
t: jest.fn(),
|
|
71
84
|
loadEntries: jest.fn(),
|
|
72
85
|
traverseCollectionCursor: jest.fn(),
|
|
86
|
+
loadUnpublishedEntries: jest.fn(),
|
|
73
87
|
isFetching: false,
|
|
74
88
|
cursor: {},
|
|
75
89
|
collection,
|
|
90
|
+
collections: fromJS({ pages: collection }),
|
|
91
|
+
entriesLoaded: true,
|
|
92
|
+
unpublishedEntriesLoaded: true,
|
|
93
|
+
isEditorialWorkflowEnabled: false,
|
|
94
|
+
getWorkflowStatus: jest.fn(),
|
|
95
|
+
getUnpublishedEntries: jest.fn(() => []),
|
|
76
96
|
};
|
|
97
|
+
|
|
77
98
|
it('should render with entries', () => {
|
|
78
99
|
const entries = fromJS([{ slug: 'index' }]);
|
|
79
100
|
const { asFragment } = render(<EntriesCollection {...props} entries={entries} />);
|
|
@@ -88,10 +109,7 @@ describe('EntriesCollection', () => {
|
|
|
88
109
|
{ slug: 'dir2/index', path: 'src/pages/dir2/index.md', data: { title: 'File 2' } },
|
|
89
110
|
];
|
|
90
111
|
|
|
91
|
-
const store =
|
|
92
|
-
entries: toEntriesState(collection, entriesArray),
|
|
93
|
-
cursors: fromJS({}),
|
|
94
|
-
});
|
|
112
|
+
const store = createMockStore(collection, entriesArray);
|
|
95
113
|
|
|
96
114
|
const { asFragment } = renderWithRedux(<ConnectedEntriesCollection collection={collection} />, {
|
|
97
115
|
store,
|
|
@@ -109,18 +127,13 @@ describe('EntriesCollection', () => {
|
|
|
109
127
|
{ slug: 'dir3/dir4/index', path: 'src/pages/dir3/dir4/index.md', data: { title: 'File 4' } },
|
|
110
128
|
];
|
|
111
129
|
|
|
112
|
-
const store =
|
|
113
|
-
entries: toEntriesState(collection, entriesArray),
|
|
114
|
-
cursors: fromJS({}),
|
|
115
|
-
});
|
|
130
|
+
const store = createMockStore(collection, entriesArray);
|
|
116
131
|
|
|
117
132
|
const { asFragment } = renderWithRedux(
|
|
118
133
|
<ConnectedEntriesCollection
|
|
119
134
|
collection={collection.set('nested', fromJS({ depth: 10, subfolders: false }))}
|
|
120
135
|
/>,
|
|
121
|
-
{
|
|
122
|
-
store,
|
|
123
|
-
},
|
|
136
|
+
{ store },
|
|
124
137
|
);
|
|
125
138
|
|
|
126
139
|
expect(asFragment()).toMatchSnapshot();
|
|
@@ -135,19 +148,14 @@ describe('EntriesCollection', () => {
|
|
|
135
148
|
{ slug: 'dir3/dir4/index', path: 'src/pages/dir3/dir4/index.md', data: { title: 'File 4' } },
|
|
136
149
|
];
|
|
137
150
|
|
|
138
|
-
const store =
|
|
139
|
-
entries: toEntriesState(collection, entriesArray),
|
|
140
|
-
cursors: fromJS({}),
|
|
141
|
-
});
|
|
151
|
+
const store = createMockStore(collection, entriesArray);
|
|
142
152
|
|
|
143
153
|
const { asFragment } = renderWithRedux(
|
|
144
154
|
<ConnectedEntriesCollection
|
|
145
155
|
collection={collection.set('nested', fromJS({ depth: 10, subfolders: false }))}
|
|
146
156
|
filterTerm="dir3/dir4"
|
|
147
157
|
/>,
|
|
148
|
-
{
|
|
149
|
-
store,
|
|
150
|
-
},
|
|
158
|
+
{ store },
|
|
151
159
|
);
|
|
152
160
|
|
|
153
161
|
expect(asFragment()).toMatchSnapshot();
|
package/src/components/Collection/Entries/__tests__/__snapshots__/EntriesCollection.spec.js.snap
CHANGED
|
@@ -29,6 +29,7 @@ exports[`EntriesCollection should render with applied filter term for nested col
|
|
|
29
29
|
collections="Map { \\"name\\": \\"pages\\", \\"label\\": \\"Pages\\", \\"folder\\": \\"src/pages\\", \\"nested\\": Map { \\"depth\\": 10, \\"subfolders\\": false } }"
|
|
30
30
|
cursor="[object Object]"
|
|
31
31
|
entries="List [ Map { \\"slug\\": \\"dir3/dir4/index\\", \\"path\\": \\"src/pages/dir3/dir4/index.md\\", \\"data\\": Map { \\"title\\": \\"File 4\\" } } ]"
|
|
32
|
+
filterterm="dir3/dir4"
|
|
32
33
|
/>
|
|
33
34
|
</DocumentFragment>
|
|
34
35
|
`;
|
|
@@ -160,7 +160,15 @@ function getConfigSchema() {
|
|
|
160
160
|
i18n: i18nRoot,
|
|
161
161
|
site_url: { type: 'string', examples: ['https://example.com'] },
|
|
162
162
|
display_url: { type: 'string', examples: ['https://example.com'] },
|
|
163
|
-
logo_url: { type: 'string', examples: ['https://example.com/images/logo.svg'] },
|
|
163
|
+
logo_url: { type: 'string', examples: ['https://example.com/images/logo.svg'] }, // Deprecated, replaced by `logo.src`
|
|
164
|
+
logo: {
|
|
165
|
+
type: 'object',
|
|
166
|
+
properties: {
|
|
167
|
+
src: { type: 'string', examples: ['https://example.com/images/logo.svg'] },
|
|
168
|
+
show_in_header: { type: 'boolean' },
|
|
169
|
+
},
|
|
170
|
+
required: ['src'],
|
|
171
|
+
},
|
|
164
172
|
show_preview_links: { type: 'boolean' },
|
|
165
173
|
media_folder: { type: 'string', examples: ['assets/uploads'] },
|
|
166
174
|
public_folder: { type: 'string', examples: ['/uploads'] },
|
package/src/lib/i18n.ts
CHANGED
|
@@ -221,6 +221,24 @@ export function formatI18nBackup(
|
|
|
221
221
|
return i18n;
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
+
function applyDefaultI18nValues(
|
|
225
|
+
collection: Collection,
|
|
226
|
+
value: EntryValue,
|
|
227
|
+
defaultLocaleValue: EntryValue,
|
|
228
|
+
) {
|
|
229
|
+
if (collection.get('fields') === undefined) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
collection.get('fields').forEach(field => {
|
|
233
|
+
if (field && field.get(I18N) === I18N_FIELD.DUPLICATE) {
|
|
234
|
+
const data = value.data[field.get('name')];
|
|
235
|
+
if (!data) {
|
|
236
|
+
value.data[field.get('name')] = defaultLocaleValue.data[field.get('name')];
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
224
242
|
function mergeValues(
|
|
225
243
|
collection: Collection,
|
|
226
244
|
structure: I18N_STRUCTURE,
|
|
@@ -236,6 +254,9 @@ function mergeValues(
|
|
|
236
254
|
.filter(e => e.locale !== defaultEntry!.locale)
|
|
237
255
|
.reduce((acc, { locale, value }) => {
|
|
238
256
|
const dataPath = getLocaleDataPath(locale);
|
|
257
|
+
if (defaultEntry) {
|
|
258
|
+
applyDefaultI18nValues(collection, value, defaultEntry.value);
|
|
259
|
+
}
|
|
239
260
|
return set(acc, dataPath, value.data);
|
|
240
261
|
}, {});
|
|
241
262
|
|
package/src/types/redux.ts
CHANGED
|
@@ -397,7 +397,11 @@ export interface CmsConfig {
|
|
|
397
397
|
locale?: string;
|
|
398
398
|
site_url?: string;
|
|
399
399
|
display_url?: string;
|
|
400
|
-
logo_url?: string;
|
|
400
|
+
logo_url?: string; // Deprecated, replaced by `logo.src`
|
|
401
|
+
logo?: {
|
|
402
|
+
src: string;
|
|
403
|
+
show_in_header?: boolean;
|
|
404
|
+
};
|
|
401
405
|
show_preview_links?: boolean;
|
|
402
406
|
media_folder?: string;
|
|
403
407
|
public_folder?: string;
|