decap-cms-core 3.9.0 → 3.10.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 +23 -23
- package/dist/decap-cms-core.js.LICENSE.txt +10 -0
- package/dist/decap-cms-core.js.map +1 -1
- package/dist/esm/actions/config.js +19 -0
- package/dist/esm/actions/entries.js +10 -1
- package/dist/esm/bootstrap.js +2 -2
- package/dist/esm/components/App/StatusBar.js +41 -0
- package/dist/esm/components/Collection/Entries/Pagination.js +132 -0
- package/dist/esm/components/Editor/EditorNotesPane/AddNoteForm.js +17 -14
- package/dist/esm/components/UI/ErrorBoundary.js +6 -9
- package/dist/esm/constants/configSchema.js +41 -23
- package/dist/esm/lib/entryCache.js +145 -0
- package/dist/esm/lib/entryHelpers.js +102 -0
- package/dist/esm/lib/formatters.js +2 -1
- package/dist/esm/lib/immutableHelpers.js +21 -0
- package/dist/esm/lib/indexFileHelper.js +36 -0
- package/dist/esm/lib/pagination.js +68 -0
- package/dist/esm/reducers/collections.js +54 -5
- package/dist/esm/reducers/entries.js +8 -2
- package/index.d.ts +8 -2
- package/package.json +2 -3
- package/src/actions/__tests__/config.spec.js +4 -4
- package/src/actions/config.ts +22 -0
- package/src/actions/entries.ts +11 -1
- package/src/components/UI/ErrorBoundary.js +1 -2
- package/src/constants/__tests__/configSchema.spec.js +84 -0
- package/src/constants/configSchema.js +34 -1
- package/src/lib/__tests__/formatters.spec.js +30 -2
- package/src/lib/formatters.ts +6 -1
- package/src/reducers/__tests__/collections.spec.js +39 -0
- package/src/reducers/collections.ts +52 -5
- package/src/reducers/entries.ts +12 -2
- package/src/types/redux.ts +9 -3
package/src/actions/config.ts
CHANGED
|
@@ -171,6 +171,20 @@ function hasIntegration(config: CmsConfig, collection: CmsCollection) {
|
|
|
171
171
|
return !!integration;
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
+
function normalizeSortableFields(
|
|
175
|
+
sortableFields: (
|
|
176
|
+
| string
|
|
177
|
+
| { field: string; label?: string; default_sort?: boolean | 'asc' | 'desc' }
|
|
178
|
+
)[],
|
|
179
|
+
) {
|
|
180
|
+
return sortableFields.map(field => {
|
|
181
|
+
if (typeof field === 'string') {
|
|
182
|
+
return { field, default_sort: undefined };
|
|
183
|
+
}
|
|
184
|
+
return field;
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
174
188
|
export function normalizeConfig(config: CmsConfig) {
|
|
175
189
|
const { collections = [] } = config;
|
|
176
190
|
|
|
@@ -200,6 +214,14 @@ export function normalizeConfig(config: CmsConfig) {
|
|
|
200
214
|
);
|
|
201
215
|
}
|
|
202
216
|
|
|
217
|
+
// Normalize sortable_fields to consistent object format
|
|
218
|
+
if (normalizedCollection.sortable_fields) {
|
|
219
|
+
normalizedCollection = {
|
|
220
|
+
...normalizedCollection,
|
|
221
|
+
sortable_fields: normalizeSortableFields(normalizedCollection.sortable_fields),
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
203
225
|
return normalizedCollection;
|
|
204
226
|
});
|
|
205
227
|
|
package/src/actions/entries.ts
CHANGED
|
@@ -3,7 +3,7 @@ import isEqual from 'lodash/isEqual';
|
|
|
3
3
|
import { Cursor } from 'decap-cms-lib-util';
|
|
4
4
|
|
|
5
5
|
import { selectCollectionEntriesCursor } from '../reducers/cursors';
|
|
6
|
-
import { selectFields, updateFieldByKey } from '../reducers/collections';
|
|
6
|
+
import { selectFields, updateFieldByKey, selectDefaultSortField } from '../reducers/collections';
|
|
7
7
|
import { selectIntegration, selectPublishedSlugs } from '../reducers';
|
|
8
8
|
import { getIntegrationProvider } from '../integrations';
|
|
9
9
|
import { currentBackend } from '../backend';
|
|
@@ -579,11 +579,21 @@ export function loadEntries(collection: Collection, page = 0) {
|
|
|
579
579
|
}
|
|
580
580
|
const state = getState();
|
|
581
581
|
const sortFields = selectEntriesSortFields(state.entries, collection.get('name'));
|
|
582
|
+
|
|
583
|
+
// If user has already set a sort, use it
|
|
582
584
|
if (sortFields && sortFields.length > 0) {
|
|
583
585
|
const field = sortFields[0];
|
|
584
586
|
return dispatch(sortByField(collection, field.get('key'), field.get('direction')));
|
|
585
587
|
}
|
|
586
588
|
|
|
589
|
+
// Otherwise, check for a default sort field in the collection configuration
|
|
590
|
+
const defaultSort = selectDefaultSortField(collection);
|
|
591
|
+
if (defaultSort) {
|
|
592
|
+
const direction =
|
|
593
|
+
defaultSort.direction === 'desc' ? SortDirection.Descending : SortDirection.Ascending;
|
|
594
|
+
return dispatch(sortByField(collection, defaultSort.field, direction));
|
|
595
|
+
}
|
|
596
|
+
|
|
587
597
|
const backend = currentBackend(state.config);
|
|
588
598
|
const integration = selectIntegration(state, collection.get('name'), 'listEntries');
|
|
589
599
|
const provider = integration
|
|
@@ -7,7 +7,6 @@ import truncate from 'lodash/truncate';
|
|
|
7
7
|
import copyToClipboard from 'copy-text-to-clipboard';
|
|
8
8
|
import { localForage } from 'decap-cms-lib-util';
|
|
9
9
|
import { buttons, colors } from 'decap-cms-ui-default';
|
|
10
|
-
import cleanStack from 'clean-stack';
|
|
11
10
|
|
|
12
11
|
const ISSUE_URL = 'https://github.com/decaporg/decap-cms/issues/new?';
|
|
13
12
|
|
|
@@ -145,7 +144,7 @@ export class ErrorBoundary extends React.Component {
|
|
|
145
144
|
console.error(error);
|
|
146
145
|
return {
|
|
147
146
|
hasError: true,
|
|
148
|
-
errorMessage:
|
|
147
|
+
errorMessage: error.stack || error.toString(),
|
|
149
148
|
errorTitle: error.toString(),
|
|
150
149
|
};
|
|
151
150
|
}
|
|
@@ -216,6 +216,90 @@ describe('config', () => {
|
|
|
216
216
|
}).toThrowError("'collections[0]' must NOT be valid");
|
|
217
217
|
});
|
|
218
218
|
|
|
219
|
+
it('should allow sortable_fields to have object format with field property', () => {
|
|
220
|
+
expect(() => {
|
|
221
|
+
validateConfig(
|
|
222
|
+
merge({}, validConfig, {
|
|
223
|
+
collections: [{ sortable_fields: [{ field: 'title' }] }],
|
|
224
|
+
}),
|
|
225
|
+
);
|
|
226
|
+
}).not.toThrow();
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it('should allow sortable_fields with default_sort as boolean', () => {
|
|
230
|
+
expect(() => {
|
|
231
|
+
validateConfig(
|
|
232
|
+
merge({}, validConfig, {
|
|
233
|
+
collections: [{ sortable_fields: [{ field: 'title', default_sort: true }] }],
|
|
234
|
+
}),
|
|
235
|
+
);
|
|
236
|
+
}).not.toThrow();
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it('should allow sortable_fields with default_sort as asc/desc', () => {
|
|
240
|
+
expect(() => {
|
|
241
|
+
validateConfig(
|
|
242
|
+
merge({}, validConfig, {
|
|
243
|
+
collections: [{ sortable_fields: ['title', { field: 'date', default_sort: 'desc' }] }],
|
|
244
|
+
}),
|
|
245
|
+
);
|
|
246
|
+
}).not.toThrow();
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it('should allow sortable_fields with custom label', () => {
|
|
250
|
+
expect(() => {
|
|
251
|
+
validateConfig(
|
|
252
|
+
merge({}, validConfig, {
|
|
253
|
+
collections: [{ sortable_fields: [{ field: 'date', label: 'Publish Date' }] }],
|
|
254
|
+
}),
|
|
255
|
+
);
|
|
256
|
+
}).not.toThrow();
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('should allow sortable_fields with label and default_sort', () => {
|
|
260
|
+
expect(() => {
|
|
261
|
+
validateConfig(
|
|
262
|
+
merge({}, validConfig, {
|
|
263
|
+
collections: [
|
|
264
|
+
{
|
|
265
|
+
sortable_fields: [
|
|
266
|
+
'title',
|
|
267
|
+
{ field: 'date', label: 'Publish Date', default_sort: 'desc' },
|
|
268
|
+
],
|
|
269
|
+
},
|
|
270
|
+
],
|
|
271
|
+
}),
|
|
272
|
+
);
|
|
273
|
+
}).not.toThrow();
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it('should allow mixed string and object format in sortable_fields', () => {
|
|
277
|
+
expect(() => {
|
|
278
|
+
validateConfig(
|
|
279
|
+
merge({}, validConfig, {
|
|
280
|
+
collections: [{ sortable_fields: ['title', { field: 'date', default_sort: true }] }],
|
|
281
|
+
}),
|
|
282
|
+
);
|
|
283
|
+
}).not.toThrow();
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it('should throw if more than one sortable field has default_sort property', () => {
|
|
287
|
+
expect(() => {
|
|
288
|
+
validateConfig(
|
|
289
|
+
merge({}, validConfig, {
|
|
290
|
+
collections: [
|
|
291
|
+
{
|
|
292
|
+
sortable_fields: [
|
|
293
|
+
{ field: 'title', default_sort: true },
|
|
294
|
+
{ field: 'date', default_sort: true },
|
|
295
|
+
],
|
|
296
|
+
},
|
|
297
|
+
],
|
|
298
|
+
}),
|
|
299
|
+
);
|
|
300
|
+
}).toThrowError('only one sortable field can have the default_sort property');
|
|
301
|
+
});
|
|
302
|
+
|
|
219
303
|
it('should throw if collection names are not unique', () => {
|
|
220
304
|
expect(() => {
|
|
221
305
|
validateConfig(
|
|
@@ -253,7 +253,21 @@ function getConfigSchema() {
|
|
|
253
253
|
sortable_fields: {
|
|
254
254
|
type: 'array',
|
|
255
255
|
items: {
|
|
256
|
-
|
|
256
|
+
oneOf: [
|
|
257
|
+
{ type: 'string' },
|
|
258
|
+
{
|
|
259
|
+
type: 'object',
|
|
260
|
+
properties: {
|
|
261
|
+
field: { type: 'string' },
|
|
262
|
+
label: { type: 'string' },
|
|
263
|
+
default_sort: {
|
|
264
|
+
oneOf: [{ type: 'boolean' }, { type: 'string', enum: ['asc', 'desc'] }],
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
required: ['field'],
|
|
268
|
+
additionalProperties: false,
|
|
269
|
+
},
|
|
270
|
+
],
|
|
257
271
|
},
|
|
258
272
|
},
|
|
259
273
|
sortableFields: {
|
|
@@ -405,4 +419,23 @@ export function validateConfig(config) {
|
|
|
405
419
|
console.error('Config Errors', errors);
|
|
406
420
|
throw new ConfigError(errors);
|
|
407
421
|
}
|
|
422
|
+
|
|
423
|
+
// Custom validation: only one sortable field can have default_sort property
|
|
424
|
+
if (config.collections) {
|
|
425
|
+
config.collections.forEach((collection, index) => {
|
|
426
|
+
if (collection.sortable_fields) {
|
|
427
|
+
const defaultFields = collection.sortable_fields.filter(
|
|
428
|
+
field => typeof field === 'object' && field.default_sort !== undefined,
|
|
429
|
+
);
|
|
430
|
+
if (defaultFields.length > 1) {
|
|
431
|
+
const error = {
|
|
432
|
+
instancePath: `/collections/${index}/sortable_fields`,
|
|
433
|
+
message: 'only one sortable field can have the default_sort property',
|
|
434
|
+
};
|
|
435
|
+
console.error('Config Errors', [error]);
|
|
436
|
+
throw new ConfigError([error]);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
});
|
|
440
|
+
}
|
|
408
441
|
}
|
|
@@ -274,8 +274,8 @@ describe('formatters', () => {
|
|
|
274
274
|
};
|
|
275
275
|
|
|
276
276
|
describe('slugFormatter', () => {
|
|
277
|
-
const date = new Date('2020-01-01');
|
|
278
|
-
jest.spyOn(
|
|
277
|
+
const date = new Date('2020-01-01').valueOf();
|
|
278
|
+
Date.now = jest.spyOn(Date, 'now').mockImplementation(() => date);
|
|
279
279
|
|
|
280
280
|
const { selectIdentifier } = require('../../reducers/collections');
|
|
281
281
|
|
|
@@ -312,6 +312,34 @@ describe('formatters', () => {
|
|
|
312
312
|
).toBe('entry-slug');
|
|
313
313
|
});
|
|
314
314
|
|
|
315
|
+
it('should see date filters applied to date from entry if it exists', () => {
|
|
316
|
+
const { selectInferredField } = require('../../reducers/collections');
|
|
317
|
+
selectInferredField.mockReturnValue('date');
|
|
318
|
+
const entryDate = new Date('2026-10-20');
|
|
319
|
+
|
|
320
|
+
expect(
|
|
321
|
+
slugFormatter(
|
|
322
|
+
Map({ slug: '{{year}}-{{month}}-{{day}}-{{title}}' }),
|
|
323
|
+
Map({ date: entryDate, title: 'post title' }),
|
|
324
|
+
slugConfig,
|
|
325
|
+
),
|
|
326
|
+
).toBe('2026-10-20-post-title');
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it('should see date filters applied to publishDate from entry if it exists', () => {
|
|
330
|
+
const { selectInferredField } = require('../../reducers/collections');
|
|
331
|
+
selectInferredField.mockReturnValue('publishDate');
|
|
332
|
+
const entryDate = new Date('2026-10-20');
|
|
333
|
+
|
|
334
|
+
expect(
|
|
335
|
+
slugFormatter(
|
|
336
|
+
Map({ slug: '{{year}}-{{month}}-{{day}}-{{title}}' }),
|
|
337
|
+
Map({ publishDate: entryDate, title: 'post title' }),
|
|
338
|
+
slugConfig,
|
|
339
|
+
),
|
|
340
|
+
).toBe('2026-10-20-post-title');
|
|
341
|
+
});
|
|
342
|
+
|
|
315
343
|
it('should return slug', () => {
|
|
316
344
|
selectIdentifier.mockReturnValueOnce('title');
|
|
317
345
|
|
package/src/lib/formatters.ts
CHANGED
|
@@ -21,6 +21,7 @@ import type { Map } from 'immutable';
|
|
|
21
21
|
const {
|
|
22
22
|
compileStringTemplate,
|
|
23
23
|
parseDateFromEntry,
|
|
24
|
+
parseDateFromEntryData,
|
|
24
25
|
SLUG_MISSING_REQUIRED_DATE,
|
|
25
26
|
keyToPathArray,
|
|
26
27
|
addFileTemplateFields,
|
|
@@ -129,7 +130,11 @@ export function slugFormatter(
|
|
|
129
130
|
}
|
|
130
131
|
|
|
131
132
|
const processSegment = getProcessSegment(slugConfig);
|
|
132
|
-
const date =
|
|
133
|
+
const date =
|
|
134
|
+
parseDateFromEntryData(
|
|
135
|
+
entryData as unknown as Map<string, unknown>,
|
|
136
|
+
selectInferredField(collection, 'date'),
|
|
137
|
+
) || new Date(Date.now());
|
|
133
138
|
const slug = compileStringTemplate(slugTemplate, date, identifier, entryData, processSegment);
|
|
134
139
|
|
|
135
140
|
if (!collection.has('path')) {
|
|
@@ -11,6 +11,7 @@ import collections, {
|
|
|
11
11
|
getFieldsNames,
|
|
12
12
|
selectField,
|
|
13
13
|
updateFieldByKey,
|
|
14
|
+
selectInferredField,
|
|
14
15
|
} from '../collections';
|
|
15
16
|
import { FILES, FOLDER } from '../../constants/collectionTypes';
|
|
16
17
|
|
|
@@ -568,4 +569,42 @@ describe('collections', () => {
|
|
|
568
569
|
);
|
|
569
570
|
});
|
|
570
571
|
});
|
|
572
|
+
|
|
573
|
+
describe("selectInferredField(collection, 'date')", () => {
|
|
574
|
+
it('should return publishDate if set', () => {
|
|
575
|
+
const collection = fromJS({
|
|
576
|
+
fields: [{ name: 'title' }, { name: 'publishDate', widget: 'datetime' }],
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
expect(selectInferredField(collection, 'date')).toEqual('publishDate');
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
it('should return publish_date if set', () => {
|
|
583
|
+
const collection = fromJS({
|
|
584
|
+
fields: [{ name: 'title' }, { name: 'publish_date', widget: 'datetime' }],
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
expect(selectInferredField(collection, 'date')).toEqual('publish_date');
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
it('should return date if set', () => {
|
|
591
|
+
const collection = fromJS({
|
|
592
|
+
fields: [{ name: 'title' }, { name: 'date', widget: 'datetime' }],
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
expect(selectInferredField(collection, 'date')).toEqual('date');
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
it('should return first date field if multiple synonyms are present', () => {
|
|
599
|
+
const collection = fromJS({
|
|
600
|
+
fields: [
|
|
601
|
+
{ name: 'title' },
|
|
602
|
+
{ name: 'publishDate', widget: 'datetime' },
|
|
603
|
+
{ name: 'date', widget: 'datetime' },
|
|
604
|
+
],
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
expect(selectInferredField(collection, 'date')).toEqual('publishDate');
|
|
608
|
+
});
|
|
609
|
+
});
|
|
571
610
|
});
|
|
@@ -412,23 +412,46 @@ export function selectDefaultSortableFields(
|
|
|
412
412
|
defaultSortable = [COMMIT_DATE, ...defaultSortable];
|
|
413
413
|
}
|
|
414
414
|
|
|
415
|
-
|
|
415
|
+
// Return as objects with field property
|
|
416
|
+
return defaultSortable.map(field => ({ field })) as {
|
|
417
|
+
field: string;
|
|
418
|
+
label?: string;
|
|
419
|
+
default_sort?: boolean | 'asc' | 'desc';
|
|
420
|
+
}[];
|
|
416
421
|
}
|
|
417
422
|
|
|
418
423
|
export function selectSortableFields(collection: Collection, t: (key: string) => string) {
|
|
419
424
|
const fields = collection
|
|
420
425
|
.get('sortable_fields')
|
|
421
426
|
.toArray()
|
|
422
|
-
.map(
|
|
427
|
+
.map(sortableField => {
|
|
428
|
+
// Extract the field name and custom label from the sortable field object
|
|
429
|
+
const key = sortableField.get('field');
|
|
430
|
+
const customLabel = sortableField.get('label');
|
|
431
|
+
|
|
423
432
|
if (key === COMMIT_DATE) {
|
|
424
|
-
|
|
433
|
+
const label = customLabel || t('collection.defaultFields.updatedOn.label');
|
|
434
|
+
return { key, field: { name: key, label } };
|
|
425
435
|
}
|
|
426
436
|
const field = selectField(collection, key);
|
|
427
437
|
if (key === COMMIT_AUTHOR && !field) {
|
|
428
|
-
|
|
438
|
+
const label = customLabel || t('collection.defaultFields.author.label');
|
|
439
|
+
return { key, field: { name: key, label } };
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
let fieldObj: Record<string, unknown> | undefined = field?.toJS();
|
|
443
|
+
|
|
444
|
+
// If custom label is provided, override the field's label
|
|
445
|
+
if (fieldObj && customLabel) {
|
|
446
|
+
fieldObj = { ...fieldObj, label: customLabel };
|
|
429
447
|
}
|
|
430
448
|
|
|
431
|
-
|
|
449
|
+
// If no label exists at all, use the field name
|
|
450
|
+
if (fieldObj && !fieldObj.label) {
|
|
451
|
+
fieldObj = { ...fieldObj, label: (fieldObj.name as string) || key };
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
return { key, field: fieldObj };
|
|
432
455
|
})
|
|
433
456
|
.filter(item => !!item.field)
|
|
434
457
|
.map(item => ({ ...item.field, key: item.key }));
|
|
@@ -436,6 +459,30 @@ export function selectSortableFields(collection: Collection, t: (key: string) =>
|
|
|
436
459
|
return fields;
|
|
437
460
|
}
|
|
438
461
|
|
|
462
|
+
export function selectDefaultSortField(collection: Collection) {
|
|
463
|
+
const sortableFields = collection.get('sortable_fields').toArray();
|
|
464
|
+
const defaultField = sortableFields.find(field => field.get('default_sort') !== undefined);
|
|
465
|
+
|
|
466
|
+
if (!defaultField) {
|
|
467
|
+
return null;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
const fieldName = defaultField.get('field');
|
|
471
|
+
const defaultSortValue = defaultField.get('default_sort');
|
|
472
|
+
|
|
473
|
+
// Determine direction based on default_sort value
|
|
474
|
+
let direction;
|
|
475
|
+
if (defaultSortValue === true || defaultSortValue === 'asc') {
|
|
476
|
+
direction = 'asc';
|
|
477
|
+
} else if (defaultSortValue === 'desc') {
|
|
478
|
+
direction = 'desc';
|
|
479
|
+
} else {
|
|
480
|
+
direction = 'asc'; // fallback
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
return { field: fieldName, direction };
|
|
484
|
+
}
|
|
485
|
+
|
|
439
486
|
export function selectSortDataPath(collection: Collection, key: string) {
|
|
440
487
|
if (key === COMMIT_DATE) {
|
|
441
488
|
return 'updatedOn';
|
package/src/reducers/entries.ts
CHANGED
|
@@ -78,6 +78,14 @@ let slug: string;
|
|
|
78
78
|
|
|
79
79
|
const storageSortKey = 'decap-cms.entries.sort';
|
|
80
80
|
const viewStyleKey = 'decap-cms.entries.viewStyle';
|
|
81
|
+
|
|
82
|
+
function normalizeDoubleSlashes(path: string) {
|
|
83
|
+
if (!path) {
|
|
84
|
+
return path;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return path.replace(/([^:]\/)\/+/g, '$1');
|
|
88
|
+
}
|
|
81
89
|
type StorageSortObject = SortObject & { index: number };
|
|
82
90
|
type StorageSort = { [collection: string]: { [key: string]: StorageSortObject } };
|
|
83
91
|
|
|
@@ -785,12 +793,14 @@ export function selectMediaFilePublicPath(
|
|
|
785
793
|
}
|
|
786
794
|
|
|
787
795
|
const name = 'public_folder';
|
|
788
|
-
let publicFolder = config[name]
|
|
796
|
+
let publicFolder = normalizeDoubleSlashes(config[name]!);
|
|
789
797
|
|
|
790
798
|
const customFolder = hasCustomFolder(name, collection, entryMap?.get('slug'), field);
|
|
791
799
|
|
|
792
800
|
if (customFolder) {
|
|
793
|
-
publicFolder =
|
|
801
|
+
publicFolder = normalizeDoubleSlashes(
|
|
802
|
+
evaluateFolder(name, config, collection!, entryMap, field),
|
|
803
|
+
);
|
|
794
804
|
}
|
|
795
805
|
|
|
796
806
|
if (isAbsolutePath(publicFolder)) {
|
package/src/types/redux.ts
CHANGED
|
@@ -309,6 +309,12 @@ export interface ViewGroup {
|
|
|
309
309
|
id: string;
|
|
310
310
|
}
|
|
311
311
|
|
|
312
|
+
export interface SortableField {
|
|
313
|
+
field: string;
|
|
314
|
+
label?: string;
|
|
315
|
+
default_sort?: boolean | 'asc' | 'desc';
|
|
316
|
+
}
|
|
317
|
+
|
|
312
318
|
export interface CmsCollection {
|
|
313
319
|
name: string;
|
|
314
320
|
label: string;
|
|
@@ -348,7 +354,7 @@ export interface CmsCollection {
|
|
|
348
354
|
path?: string;
|
|
349
355
|
media_folder?: string;
|
|
350
356
|
public_folder?: string;
|
|
351
|
-
sortable_fields?: string[];
|
|
357
|
+
sortable_fields?: (string | SortableField)[];
|
|
352
358
|
view_filters?: ViewFilter[];
|
|
353
359
|
view_groups?: ViewGroup[];
|
|
354
360
|
i18n?: boolean | CmsI18nConfig;
|
|
@@ -356,7 +362,7 @@ export interface CmsCollection {
|
|
|
356
362
|
/**
|
|
357
363
|
* @deprecated Use sortable_fields instead
|
|
358
364
|
*/
|
|
359
|
-
sortableFields?: string[];
|
|
365
|
+
sortableFields?: (string | SortableField)[];
|
|
360
366
|
}
|
|
361
367
|
|
|
362
368
|
export interface CmsBackend {
|
|
@@ -635,7 +641,7 @@ type CollectionObject = {
|
|
|
635
641
|
slug?: string;
|
|
636
642
|
label_singular?: string;
|
|
637
643
|
label: string;
|
|
638
|
-
sortable_fields: List<
|
|
644
|
+
sortable_fields: List<StaticallyTypedRecord<SortableField>>;
|
|
639
645
|
view_filters: List<StaticallyTypedRecord<ViewFilter>>;
|
|
640
646
|
view_groups: List<StaticallyTypedRecord<ViewGroup>>;
|
|
641
647
|
nested?: Nested;
|