decap-cms-core 3.1.1 → 3.2.1
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 +7 -7
- package/dist/decap-cms-core.js.map +1 -1
- package/dist/esm/bootstrap.js +2 -2
- package/dist/esm/components/Editor/EditorToolbar.js +32 -31
- package/dist/esm/components/UI/ErrorBoundary.js +2 -2
- package/dist/esm/constants/configSchema.js +1 -2
- package/dist/esm/formats/formats.js +16 -2
- package/dist/esm/lib/registry.js +45 -2
- package/dist/esm/reducers/collections.js +5 -1
- package/index.d.ts +10 -9
- package/package.json +2 -2
- package/src/components/Editor/EditorToolbar.js +1 -0
- package/src/components/Editor/__tests__/__snapshots__/EditorToolbar.spec.js.snap +15 -15
- package/src/constants/configSchema.js +2 -2
- package/src/formats/__tests__/formats.spec.js +87 -0
- package/src/formats/formats.ts +14 -3
- package/src/lib/__tests__/registry.spec.js +15 -0
- package/src/lib/registry.js +30 -0
- package/src/reducers/collections.ts +8 -4
- package/src/types/redux.ts +1 -1
|
@@ -54,8 +54,8 @@ function buildIssueTemplate({
|
|
|
54
54
|
let version = '';
|
|
55
55
|
if (typeof DECAP_CMS_VERSION === 'string') {
|
|
56
56
|
version = `decap-cms@${DECAP_CMS_VERSION}`;
|
|
57
|
-
} else if (typeof "3.0.
|
|
58
|
-
version = `decap-cms-app@${"3.0.
|
|
57
|
+
} else if (typeof "3.0.4" === 'string') {
|
|
58
|
+
version = `decap-cms-app@${"3.0.4"}`;
|
|
59
59
|
}
|
|
60
60
|
const template = getIssueTemplate({
|
|
61
61
|
version,
|
|
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.frontmatterFormats = exports.formatExtensions = exports.extensionFormatters = void 0;
|
|
7
|
+
exports.getFormatExtensions = getFormatExtensions;
|
|
7
8
|
exports.resolveFormat = resolveFormat;
|
|
8
9
|
var _get2 = _interopRequireDefault(require("lodash/get"));
|
|
9
10
|
var _immutable = require("immutable");
|
|
@@ -11,7 +12,13 @@ var _yaml = _interopRequireDefault(require("./yaml"));
|
|
|
11
12
|
var _toml = _interopRequireDefault(require("./toml"));
|
|
12
13
|
var _json = _interopRequireDefault(require("./json"));
|
|
13
14
|
var _frontmatter = require("./frontmatter");
|
|
15
|
+
var _registry = require("../lib/registry");
|
|
14
16
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
17
|
+
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
|
|
18
|
+
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
|
|
19
|
+
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
|
20
|
+
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); }
|
|
21
|
+
function _toPrimitive(input, hint) { if (typeof input !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (typeof res !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
|
|
15
22
|
const frontmatterFormats = ['yaml-frontmatter', 'toml-frontmatter', 'json-frontmatter'];
|
|
16
23
|
exports.frontmatterFormats = frontmatterFormats;
|
|
17
24
|
const formatExtensions = {
|
|
@@ -25,6 +32,9 @@ const formatExtensions = {
|
|
|
25
32
|
'yaml-frontmatter': 'md'
|
|
26
33
|
};
|
|
27
34
|
exports.formatExtensions = formatExtensions;
|
|
35
|
+
function getFormatExtensions() {
|
|
36
|
+
return _objectSpread(_objectSpread({}, formatExtensions), (0, _registry.getCustomFormatsExtensions)());
|
|
37
|
+
}
|
|
28
38
|
const extensionFormatters = {
|
|
29
39
|
yml: _yaml.default,
|
|
30
40
|
yaml: _yaml.default,
|
|
@@ -36,7 +46,7 @@ const extensionFormatters = {
|
|
|
36
46
|
};
|
|
37
47
|
exports.extensionFormatters = extensionFormatters;
|
|
38
48
|
function formatByName(name, customDelimiter) {
|
|
39
|
-
|
|
49
|
+
const formatters = _objectSpread({
|
|
40
50
|
yml: _yaml.default,
|
|
41
51
|
yaml: _yaml.default,
|
|
42
52
|
toml: _toml.default,
|
|
@@ -45,7 +55,11 @@ function formatByName(name, customDelimiter) {
|
|
|
45
55
|
'json-frontmatter': (0, _frontmatter.frontmatterJSON)(customDelimiter),
|
|
46
56
|
'toml-frontmatter': (0, _frontmatter.frontmatterTOML)(customDelimiter),
|
|
47
57
|
'yaml-frontmatter': (0, _frontmatter.frontmatterYAML)(customDelimiter)
|
|
48
|
-
}
|
|
58
|
+
}, (0, _registry.getCustomFormatsFormatters)());
|
|
59
|
+
if (name in formatters) {
|
|
60
|
+
return formatters[name];
|
|
61
|
+
}
|
|
62
|
+
throw new Error(`No formatter available with name: ${name}`);
|
|
49
63
|
}
|
|
50
64
|
function frontmatterDelimiterIsList(frontmatterDelimiter) {
|
|
51
65
|
return _immutable.List.isList(frontmatterDelimiter);
|
package/dist/esm/lib/registry.js
CHANGED
|
@@ -5,8 +5,12 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.default = void 0;
|
|
7
7
|
exports.getBackend = getBackend;
|
|
8
|
+
exports.getCustomFormats = getCustomFormats;
|
|
9
|
+
exports.getCustomFormatsExtensions = getCustomFormatsExtensions;
|
|
10
|
+
exports.getCustomFormatsFormatters = getCustomFormatsFormatters;
|
|
8
11
|
exports.getEditorComponents = getEditorComponents;
|
|
9
12
|
exports.getEventListeners = getEventListeners;
|
|
13
|
+
exports.getFormatter = getFormatter;
|
|
10
14
|
exports.getLocale = getLocale;
|
|
11
15
|
exports.getMediaLibrary = getMediaLibrary;
|
|
12
16
|
exports.getPreviewStyles = getPreviewStyles;
|
|
@@ -17,6 +21,7 @@ exports.getWidgetValueSerializer = getWidgetValueSerializer;
|
|
|
17
21
|
exports.getWidgets = getWidgets;
|
|
18
22
|
exports.invokeEvent = invokeEvent;
|
|
19
23
|
exports.registerBackend = registerBackend;
|
|
24
|
+
exports.registerCustomFormat = registerCustomFormat;
|
|
20
25
|
exports.registerEditorComponent = registerEditorComponent;
|
|
21
26
|
exports.registerEventListener = registerEventListener;
|
|
22
27
|
exports.registerLocale = registerLocale;
|
|
@@ -60,7 +65,8 @@ const registry = {
|
|
|
60
65
|
widgetValueSerializers: {},
|
|
61
66
|
mediaLibraries: [],
|
|
62
67
|
locales: {},
|
|
63
|
-
eventHandlers
|
|
68
|
+
eventHandlers,
|
|
69
|
+
formats: {}
|
|
64
70
|
};
|
|
65
71
|
var _default = {
|
|
66
72
|
registerPreviewStyle,
|
|
@@ -86,7 +92,11 @@ var _default = {
|
|
|
86
92
|
registerEventListener,
|
|
87
93
|
removeEventListener,
|
|
88
94
|
getEventListeners,
|
|
89
|
-
invokeEvent
|
|
95
|
+
invokeEvent,
|
|
96
|
+
registerCustomFormat,
|
|
97
|
+
getCustomFormats,
|
|
98
|
+
getCustomFormatsExtensions,
|
|
99
|
+
getCustomFormatsFormatters
|
|
90
100
|
};
|
|
91
101
|
/**
|
|
92
102
|
* Preview Styles
|
|
@@ -319,4 +329,37 @@ function registerLocale(locale, phrases) {
|
|
|
319
329
|
}
|
|
320
330
|
function getLocale(locale) {
|
|
321
331
|
return registry.locales[locale];
|
|
332
|
+
}
|
|
333
|
+
function registerCustomFormat(name, extension, formatter) {
|
|
334
|
+
registry.formats[name] = {
|
|
335
|
+
extension,
|
|
336
|
+
formatter
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
function getCustomFormats() {
|
|
340
|
+
return registry.formats;
|
|
341
|
+
}
|
|
342
|
+
function getCustomFormatsExtensions() {
|
|
343
|
+
return Object.entries(registry.formats).reduce(function (acc, [name, {
|
|
344
|
+
extension
|
|
345
|
+
}]) {
|
|
346
|
+
return _objectSpread(_objectSpread({}, acc), {}, {
|
|
347
|
+
[name]: extension
|
|
348
|
+
});
|
|
349
|
+
}, {});
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/** @type {() => Record<string, unknown>} */
|
|
353
|
+
function getCustomFormatsFormatters() {
|
|
354
|
+
return Object.entries(registry.formats).reduce(function (acc, [name, {
|
|
355
|
+
formatter
|
|
356
|
+
}]) {
|
|
357
|
+
return _objectSpread(_objectSpread({}, acc), {}, {
|
|
358
|
+
[name]: formatter
|
|
359
|
+
});
|
|
360
|
+
}, {});
|
|
361
|
+
}
|
|
362
|
+
function getFormatter(name) {
|
|
363
|
+
var _registry$formats$nam;
|
|
364
|
+
return (_registry$formats$nam = registry.formats[name]) === null || _registry$formats$nam === void 0 ? void 0 : _registry$formats$nam.formatter;
|
|
322
365
|
}
|
|
@@ -69,7 +69,11 @@ function collections(state = defaultState, action) {
|
|
|
69
69
|
const selectors = {
|
|
70
70
|
[_collectionTypes.FOLDER]: {
|
|
71
71
|
entryExtension(collection) {
|
|
72
|
-
|
|
72
|
+
const ext = collection.get('extension') || (0, _get2.default)((0, _formats.getFormatExtensions)(), collection.get('format') || 'frontmatter');
|
|
73
|
+
if (!ext) {
|
|
74
|
+
throw new Error(`No extension found for format ${collection.get('format')}`);
|
|
75
|
+
}
|
|
76
|
+
return ext.replace(/^\./, '');
|
|
73
77
|
},
|
|
74
78
|
fields(collection) {
|
|
75
79
|
return collection.get('fields');
|
package/index.d.ts
CHANGED
|
@@ -36,15 +36,7 @@ declare module 'decap-cms-core' {
|
|
|
36
36
|
value: any;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
export type CmsCollectionFormatType =
|
|
40
|
-
| 'yml'
|
|
41
|
-
| 'yaml'
|
|
42
|
-
| 'toml'
|
|
43
|
-
| 'json'
|
|
44
|
-
| 'frontmatter'
|
|
45
|
-
| 'yaml-frontmatter'
|
|
46
|
-
| 'toml-frontmatter'
|
|
47
|
-
| 'json-frontmatter';
|
|
39
|
+
export type CmsCollectionFormatType = string;
|
|
48
40
|
|
|
49
41
|
export type CmsAuthScope = 'repo' | 'public_repo';
|
|
50
42
|
|
|
@@ -501,6 +493,11 @@ declare module 'decap-cms-core' {
|
|
|
501
493
|
|
|
502
494
|
export type CmsLocalePhrases = any; // TODO: type properly
|
|
503
495
|
|
|
496
|
+
export type Formatter = {
|
|
497
|
+
fromFile(content: string): unknown;
|
|
498
|
+
toFile(data: object, sortedKeys?: string[], comments?: Record<string, string>): string;
|
|
499
|
+
};
|
|
500
|
+
|
|
504
501
|
export interface CmsRegistry {
|
|
505
502
|
backends: {
|
|
506
503
|
[name: string]: CmsRegistryBackend;
|
|
@@ -520,6 +517,9 @@ declare module 'decap-cms-core' {
|
|
|
520
517
|
locales: {
|
|
521
518
|
[name: string]: CmsLocalePhrases;
|
|
522
519
|
};
|
|
520
|
+
formats: {
|
|
521
|
+
[name: string]: Formatter;
|
|
522
|
+
};
|
|
523
523
|
}
|
|
524
524
|
|
|
525
525
|
type GetAssetFunction = (asset: string) => {
|
|
@@ -579,6 +579,7 @@ declare module 'decap-cms-core' {
|
|
|
579
579
|
serializer: CmsWidgetValueSerializer,
|
|
580
580
|
) => void;
|
|
581
581
|
resolveWidget: (name: string) => CmsWidget | undefined;
|
|
582
|
+
registerCustomFormat: (name: string, extension: string, formatter: Formatter) => void;
|
|
582
583
|
}
|
|
583
584
|
|
|
584
585
|
export const DecapCmsCore: CMS;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "decap-cms-core",
|
|
3
3
|
"description": "Decap CMS core application, see decap-cms package for the main distribution.",
|
|
4
|
-
"version": "3.
|
|
4
|
+
"version": "3.2.1",
|
|
5
5
|
"repository": "https://github.com/decaporg/decap-cms/tree/master/packages/decap-cms-core",
|
|
6
6
|
"bugs": "https://github.com/decaporg/decap-cms/issues",
|
|
7
7
|
"module": "dist/esm/index.js",
|
|
@@ -98,5 +98,5 @@
|
|
|
98
98
|
"@types/url-join": "^4.0.0",
|
|
99
99
|
"redux-mock-store": "^1.5.3"
|
|
100
100
|
},
|
|
101
|
-
"gitHead": "
|
|
101
|
+
"gitHead": "25ef3074bd4fdc285f2245f1e10100316b3ff7db"
|
|
102
102
|
}
|
|
@@ -200,7 +200,7 @@ exports[`EditorToolbar should render normal save button 1`] = `
|
|
|
200
200
|
class="emotion-21 emotion-22"
|
|
201
201
|
>
|
|
202
202
|
<mock-link
|
|
203
|
-
classname="css-
|
|
203
|
+
classname="css-h6rugo-ToolbarSectionBackLink-toolbarSection e1d2l9mo8"
|
|
204
204
|
to=""
|
|
205
205
|
>
|
|
206
206
|
<div
|
|
@@ -463,7 +463,7 @@ exports[`EditorToolbar should render normal save button 2`] = `
|
|
|
463
463
|
class="emotion-21 emotion-22"
|
|
464
464
|
>
|
|
465
465
|
<mock-link
|
|
466
|
-
classname="css-
|
|
466
|
+
classname="css-h6rugo-ToolbarSectionBackLink-toolbarSection e1d2l9mo8"
|
|
467
467
|
to=""
|
|
468
468
|
>
|
|
469
469
|
<div
|
|
@@ -695,7 +695,7 @@ exports[`EditorToolbar should render with default props 1`] = `
|
|
|
695
695
|
class="emotion-18 emotion-19"
|
|
696
696
|
>
|
|
697
697
|
<mock-link
|
|
698
|
-
classname="css-
|
|
698
|
+
classname="css-h6rugo-ToolbarSectionBackLink-toolbarSection e1d2l9mo8"
|
|
699
699
|
to=""
|
|
700
700
|
>
|
|
701
701
|
<div
|
|
@@ -980,7 +980,7 @@ exports[`EditorToolbar should render with status=draft,useOpenAuthoring=false 1`
|
|
|
980
980
|
class="emotion-23 emotion-24"
|
|
981
981
|
>
|
|
982
982
|
<mock-link
|
|
983
|
-
classname="css-
|
|
983
|
+
classname="css-h6rugo-ToolbarSectionBackLink-toolbarSection e1d2l9mo8"
|
|
984
984
|
to=""
|
|
985
985
|
>
|
|
986
986
|
<div
|
|
@@ -1310,7 +1310,7 @@ exports[`EditorToolbar should render with status=draft,useOpenAuthoring=true 1`]
|
|
|
1310
1310
|
class="emotion-30 emotion-31"
|
|
1311
1311
|
>
|
|
1312
1312
|
<mock-link
|
|
1313
|
-
classname="css-
|
|
1313
|
+
classname="css-h6rugo-ToolbarSectionBackLink-toolbarSection e1d2l9mo8"
|
|
1314
1314
|
to=""
|
|
1315
1315
|
>
|
|
1316
1316
|
<div
|
|
@@ -1634,7 +1634,7 @@ exports[`EditorToolbar should render with status=pending_publish,useOpenAuthorin
|
|
|
1634
1634
|
class="emotion-23 emotion-24"
|
|
1635
1635
|
>
|
|
1636
1636
|
<mock-link
|
|
1637
|
-
classname="css-
|
|
1637
|
+
classname="css-h6rugo-ToolbarSectionBackLink-toolbarSection e1d2l9mo8"
|
|
1638
1638
|
to=""
|
|
1639
1639
|
>
|
|
1640
1640
|
<div
|
|
@@ -1946,7 +1946,7 @@ exports[`EditorToolbar should render with status=pending_publish,useOpenAuthorin
|
|
|
1946
1946
|
class="emotion-28 emotion-29"
|
|
1947
1947
|
>
|
|
1948
1948
|
<mock-link
|
|
1949
|
-
classname="css-
|
|
1949
|
+
classname="css-h6rugo-ToolbarSectionBackLink-toolbarSection e1d2l9mo8"
|
|
1950
1950
|
to=""
|
|
1951
1951
|
>
|
|
1952
1952
|
<div
|
|
@@ -2265,7 +2265,7 @@ exports[`EditorToolbar should render with status=pending_review,useOpenAuthoring
|
|
|
2265
2265
|
class="emotion-23 emotion-24"
|
|
2266
2266
|
>
|
|
2267
2267
|
<mock-link
|
|
2268
|
-
classname="css-
|
|
2268
|
+
classname="css-h6rugo-ToolbarSectionBackLink-toolbarSection e1d2l9mo8"
|
|
2269
2269
|
to=""
|
|
2270
2270
|
>
|
|
2271
2271
|
<div
|
|
@@ -2595,7 +2595,7 @@ exports[`EditorToolbar should render with status=pending_review,useOpenAuthoring
|
|
|
2595
2595
|
class="emotion-30 emotion-31"
|
|
2596
2596
|
>
|
|
2597
2597
|
<mock-link
|
|
2598
|
-
classname="css-
|
|
2598
|
+
classname="css-h6rugo-ToolbarSectionBackLink-toolbarSection e1d2l9mo8"
|
|
2599
2599
|
to=""
|
|
2600
2600
|
>
|
|
2601
2601
|
<div
|
|
@@ -2862,7 +2862,7 @@ exports[`EditorToolbar should render with workflow controls hasUnpublishedChange
|
|
|
2862
2862
|
class="emotion-18 emotion-19"
|
|
2863
2863
|
>
|
|
2864
2864
|
<mock-link
|
|
2865
|
-
classname="css-
|
|
2865
|
+
classname="css-h6rugo-ToolbarSectionBackLink-toolbarSection e1d2l9mo8"
|
|
2866
2866
|
to=""
|
|
2867
2867
|
>
|
|
2868
2868
|
<div
|
|
@@ -3064,7 +3064,7 @@ exports[`EditorToolbar should render with workflow controls hasUnpublishedChange
|
|
|
3064
3064
|
class="emotion-16 emotion-17"
|
|
3065
3065
|
>
|
|
3066
3066
|
<mock-link
|
|
3067
|
-
classname="css-
|
|
3067
|
+
classname="css-h6rugo-ToolbarSectionBackLink-toolbarSection e1d2l9mo8"
|
|
3068
3068
|
to=""
|
|
3069
3069
|
>
|
|
3070
3070
|
<div
|
|
@@ -3286,7 +3286,7 @@ exports[`EditorToolbar should render with workflow controls hasUnpublishedChange
|
|
|
3286
3286
|
class="emotion-18 emotion-19"
|
|
3287
3287
|
>
|
|
3288
3288
|
<mock-link
|
|
3289
|
-
classname="css-
|
|
3289
|
+
classname="css-h6rugo-ToolbarSectionBackLink-toolbarSection e1d2l9mo8"
|
|
3290
3290
|
to=""
|
|
3291
3291
|
>
|
|
3292
3292
|
<div
|
|
@@ -3513,7 +3513,7 @@ exports[`EditorToolbar should render with workflow controls hasUnpublishedChange
|
|
|
3513
3513
|
class="emotion-18 emotion-19"
|
|
3514
3514
|
>
|
|
3515
3515
|
<mock-link
|
|
3516
|
-
classname="css-
|
|
3516
|
+
classname="css-h6rugo-ToolbarSectionBackLink-toolbarSection e1d2l9mo8"
|
|
3517
3517
|
to=""
|
|
3518
3518
|
>
|
|
3519
3519
|
<div
|
|
@@ -3740,7 +3740,7 @@ exports[`EditorToolbar should render with workflow controls hasUnpublishedChange
|
|
|
3740
3740
|
class="emotion-18 emotion-19"
|
|
3741
3741
|
>
|
|
3742
3742
|
<mock-link
|
|
3743
|
-
classname="css-
|
|
3743
|
+
classname="css-h6rugo-ToolbarSectionBackLink-toolbarSection e1d2l9mo8"
|
|
3744
3744
|
to=""
|
|
3745
3745
|
>
|
|
3746
3746
|
<div
|
|
@@ -3967,7 +3967,7 @@ exports[`EditorToolbar should render with workflow controls hasUnpublishedChange
|
|
|
3967
3967
|
class="emotion-18 emotion-19"
|
|
3968
3968
|
>
|
|
3969
3969
|
<mock-link
|
|
3970
|
-
classname="css-
|
|
3970
|
+
classname="css-h6rugo-ToolbarSectionBackLink-toolbarSection e1d2l9mo8"
|
|
3971
3971
|
to=""
|
|
3972
3972
|
>
|
|
3973
3973
|
<div
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
import ajvErrors from 'ajv-errors';
|
|
9
9
|
import uuid from 'uuid/v4';
|
|
10
10
|
|
|
11
|
-
import {
|
|
11
|
+
import { frontmatterFormats, extensionFormatters } from '../formats/formats';
|
|
12
12
|
import { getWidgets } from '../lib/registry';
|
|
13
13
|
import { I18N_STRUCTURE, I18N_FIELD } from '../lib/i18n';
|
|
14
14
|
|
|
@@ -231,7 +231,7 @@ function getConfigSchema() {
|
|
|
231
231
|
preview: { type: 'boolean' },
|
|
232
232
|
},
|
|
233
233
|
},
|
|
234
|
-
format: { type: 'string'
|
|
234
|
+
format: { type: 'string' },
|
|
235
235
|
extension: { type: 'string' },
|
|
236
236
|
frontmatter_delimiter: {
|
|
237
237
|
type: ['string', 'array'],
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { Map } from 'immutable';
|
|
2
|
+
|
|
3
|
+
import { extensionFormatters, resolveFormat } from '../formats';
|
|
4
|
+
import { registerCustomFormat } from '../../lib/registry';
|
|
5
|
+
|
|
6
|
+
describe('custom formats', () => {
|
|
7
|
+
const testEntry = {
|
|
8
|
+
collection: 'testCollection',
|
|
9
|
+
data: { x: 1 },
|
|
10
|
+
isModification: false,
|
|
11
|
+
label: 'testLabel',
|
|
12
|
+
mediaFiles: [],
|
|
13
|
+
meta: {},
|
|
14
|
+
newRecord: true,
|
|
15
|
+
partial: false,
|
|
16
|
+
path: 'testPath1',
|
|
17
|
+
raw: 'testRaw',
|
|
18
|
+
slug: 'testSlug',
|
|
19
|
+
author: 'testAuthor',
|
|
20
|
+
updatedOn: 'testUpdatedOn',
|
|
21
|
+
};
|
|
22
|
+
it('resolves builtint formats', () => {
|
|
23
|
+
const collection = Map({
|
|
24
|
+
name: 'posts',
|
|
25
|
+
});
|
|
26
|
+
expect(resolveFormat(collection, { ...testEntry, path: 'test.yml' })).toEqual(
|
|
27
|
+
extensionFormatters.yml,
|
|
28
|
+
);
|
|
29
|
+
expect(resolveFormat(collection, { ...testEntry, path: 'test.yaml' })).toEqual(
|
|
30
|
+
extensionFormatters.yml,
|
|
31
|
+
);
|
|
32
|
+
expect(resolveFormat(collection, { ...testEntry, path: 'test.toml' })).toEqual(
|
|
33
|
+
extensionFormatters.toml,
|
|
34
|
+
);
|
|
35
|
+
expect(resolveFormat(collection, { ...testEntry, path: 'test.json' })).toEqual(
|
|
36
|
+
extensionFormatters.json,
|
|
37
|
+
);
|
|
38
|
+
expect(resolveFormat(collection, { ...testEntry, path: 'test.md' })).toEqual(
|
|
39
|
+
extensionFormatters.md,
|
|
40
|
+
);
|
|
41
|
+
expect(resolveFormat(collection, { ...testEntry, path: 'test.markdown' })).toEqual(
|
|
42
|
+
extensionFormatters.markdown,
|
|
43
|
+
);
|
|
44
|
+
expect(resolveFormat(collection, { ...testEntry, path: 'test.html' })).toEqual(
|
|
45
|
+
extensionFormatters.html,
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('resolves custom format', () => {
|
|
50
|
+
registerCustomFormat('txt-querystring', 'txt', {
|
|
51
|
+
fromFile: file => Object.fromEntries(new URLSearchParams(file)),
|
|
52
|
+
toFile: value => new URLSearchParams(value).toString(),
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const collection = Map({
|
|
56
|
+
name: 'posts',
|
|
57
|
+
format: 'txt-querystring',
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const formatter = resolveFormat(collection, { ...testEntry, path: 'test.txt' });
|
|
61
|
+
|
|
62
|
+
expect(formatter.toFile({ foo: 'bar' })).toEqual('foo=bar');
|
|
63
|
+
expect(formatter.fromFile('foo=bar')).toEqual({ foo: 'bar' });
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('can override existing formatters', () => {
|
|
67
|
+
// simplified version of a more realistic use case: using a different yaml library like js-yaml
|
|
68
|
+
// to make netlify-cms play nice with other tools that edit content and spit out yaml
|
|
69
|
+
registerCustomFormat('bad-yaml', 'yml', {
|
|
70
|
+
fromFile: file => Object.fromEntries(file.split('\n').map(line => line.split(': '))),
|
|
71
|
+
toFile: value =>
|
|
72
|
+
Object.entries(value)
|
|
73
|
+
.map(([k, v]) => `${k}: ${v}`)
|
|
74
|
+
.join('\n'),
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const collection = Map({
|
|
78
|
+
name: 'posts',
|
|
79
|
+
format: 'bad-yaml',
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const formatter = resolveFormat(collection, { ...testEntry, path: 'test.txt' });
|
|
83
|
+
|
|
84
|
+
expect(formatter.toFile({ a: 'b', c: 'd' })).toEqual('a: b\nc: d');
|
|
85
|
+
expect(formatter.fromFile('a: b\nc: d')).toEqual({ a: 'b', c: 'd' });
|
|
86
|
+
});
|
|
87
|
+
});
|
package/src/formats/formats.ts
CHANGED
|
@@ -5,10 +5,12 @@ import yamlFormatter from './yaml';
|
|
|
5
5
|
import tomlFormatter from './toml';
|
|
6
6
|
import jsonFormatter from './json';
|
|
7
7
|
import { FrontmatterInfer, frontmatterJSON, frontmatterTOML, frontmatterYAML } from './frontmatter';
|
|
8
|
+
import { getCustomFormatsExtensions, getCustomFormatsFormatters } from '../lib/registry';
|
|
8
9
|
|
|
9
10
|
import type { Delimiter } from './frontmatter';
|
|
10
11
|
import type { Collection, EntryObject, Format } from '../types/redux';
|
|
11
12
|
import type { EntryValue } from '../valueObjects/Entry';
|
|
13
|
+
import type { Formatter } from 'decap-cms-core';
|
|
12
14
|
|
|
13
15
|
export const frontmatterFormats = ['yaml-frontmatter', 'toml-frontmatter', 'json-frontmatter'];
|
|
14
16
|
|
|
@@ -23,6 +25,10 @@ export const formatExtensions = {
|
|
|
23
25
|
'yaml-frontmatter': 'md',
|
|
24
26
|
};
|
|
25
27
|
|
|
28
|
+
export function getFormatExtensions() {
|
|
29
|
+
return { ...formatExtensions, ...getCustomFormatsExtensions() };
|
|
30
|
+
}
|
|
31
|
+
|
|
26
32
|
export const extensionFormatters = {
|
|
27
33
|
yml: yamlFormatter,
|
|
28
34
|
yaml: yamlFormatter,
|
|
@@ -33,8 +39,8 @@ export const extensionFormatters = {
|
|
|
33
39
|
html: FrontmatterInfer,
|
|
34
40
|
};
|
|
35
41
|
|
|
36
|
-
function formatByName(name: Format, customDelimiter?: Delimiter) {
|
|
37
|
-
|
|
42
|
+
function formatByName(name: Format, customDelimiter?: Delimiter): Formatter {
|
|
43
|
+
const formatters: Record<string, Formatter> = {
|
|
38
44
|
yml: yamlFormatter,
|
|
39
45
|
yaml: yamlFormatter,
|
|
40
46
|
toml: tomlFormatter,
|
|
@@ -43,7 +49,12 @@ function formatByName(name: Format, customDelimiter?: Delimiter) {
|
|
|
43
49
|
'json-frontmatter': frontmatterJSON(customDelimiter),
|
|
44
50
|
'toml-frontmatter': frontmatterTOML(customDelimiter),
|
|
45
51
|
'yaml-frontmatter': frontmatterYAML(customDelimiter),
|
|
46
|
-
|
|
52
|
+
...getCustomFormatsFormatters(),
|
|
53
|
+
};
|
|
54
|
+
if (name in formatters) {
|
|
55
|
+
return formatters[name];
|
|
56
|
+
}
|
|
57
|
+
throw new Error(`No formatter available with name: ${name}`);
|
|
47
58
|
}
|
|
48
59
|
|
|
49
60
|
function frontmatterDelimiterIsList(
|
|
@@ -46,6 +46,21 @@ describe('registry', () => {
|
|
|
46
46
|
});
|
|
47
47
|
});
|
|
48
48
|
|
|
49
|
+
describe('registerCustomFormat', () => {
|
|
50
|
+
it('can register a custom format', () => {
|
|
51
|
+
const { getCustomFormats, registerCustomFormat } = require('../registry');
|
|
52
|
+
|
|
53
|
+
expect(Object.keys(getCustomFormats())).not.toContain('querystring');
|
|
54
|
+
|
|
55
|
+
registerCustomFormat('querystring', 'qs', {
|
|
56
|
+
fromFile: content => Object.fromEntries(new URLSearchParams(content)),
|
|
57
|
+
toFile: obj => new URLSearchParams(obj).toString(),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
expect(Object.keys(getCustomFormats())).toContain('querystring');
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
49
64
|
describe('eventHandlers', () => {
|
|
50
65
|
const events = [
|
|
51
66
|
'prePublish',
|
package/src/lib/registry.js
CHANGED
|
@@ -31,6 +31,7 @@ const registry = {
|
|
|
31
31
|
mediaLibraries: [],
|
|
32
32
|
locales: {},
|
|
33
33
|
eventHandlers,
|
|
34
|
+
formats: {},
|
|
34
35
|
};
|
|
35
36
|
|
|
36
37
|
export default {
|
|
@@ -58,6 +59,10 @@ export default {
|
|
|
58
59
|
removeEventListener,
|
|
59
60
|
getEventListeners,
|
|
60
61
|
invokeEvent,
|
|
62
|
+
registerCustomFormat,
|
|
63
|
+
getCustomFormats,
|
|
64
|
+
getCustomFormatsExtensions,
|
|
65
|
+
getCustomFormatsFormatters,
|
|
61
66
|
};
|
|
62
67
|
|
|
63
68
|
/**
|
|
@@ -280,3 +285,28 @@ export function registerLocale(locale, phrases) {
|
|
|
280
285
|
export function getLocale(locale) {
|
|
281
286
|
return registry.locales[locale];
|
|
282
287
|
}
|
|
288
|
+
|
|
289
|
+
export function registerCustomFormat(name, extension, formatter) {
|
|
290
|
+
registry.formats[name] = { extension, formatter };
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
export function getCustomFormats() {
|
|
294
|
+
return registry.formats;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
export function getCustomFormatsExtensions() {
|
|
298
|
+
return Object.entries(registry.formats).reduce(function (acc, [name, { extension }]) {
|
|
299
|
+
return { ...acc, [name]: extension };
|
|
300
|
+
}, {});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/** @type {() => Record<string, unknown>} */
|
|
304
|
+
export function getCustomFormatsFormatters() {
|
|
305
|
+
return Object.entries(registry.formats).reduce(function (acc, [name, { formatter }]) {
|
|
306
|
+
return { ...acc, [name]: formatter };
|
|
307
|
+
}, {});
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
export function getFormatter(name) {
|
|
311
|
+
return registry.formats[name]?.formatter;
|
|
312
|
+
}
|
|
@@ -7,7 +7,7 @@ import { CONFIG_SUCCESS } from '../actions/config';
|
|
|
7
7
|
import { FILES, FOLDER } from '../constants/collectionTypes';
|
|
8
8
|
import { COMMIT_DATE, COMMIT_AUTHOR } from '../constants/commitProps';
|
|
9
9
|
import { INFERABLE_FIELDS, IDENTIFIER_FIELDS, SORTABLE_FIELDS } from '../constants/fieldInference';
|
|
10
|
-
import {
|
|
10
|
+
import { getFormatExtensions } from '../formats/formats';
|
|
11
11
|
import { selectMediaFolder } from './entries';
|
|
12
12
|
import { summaryFormatter } from '../lib/formatters';
|
|
13
13
|
|
|
@@ -46,10 +46,14 @@ function collections(state = defaultState, action: ConfigAction) {
|
|
|
46
46
|
const selectors = {
|
|
47
47
|
[FOLDER]: {
|
|
48
48
|
entryExtension(collection: Collection) {
|
|
49
|
-
|
|
49
|
+
const ext =
|
|
50
50
|
collection.get('extension') ||
|
|
51
|
-
get(
|
|
52
|
-
)
|
|
51
|
+
get(getFormatExtensions(), collection.get('format') || 'frontmatter');
|
|
52
|
+
if (!ext) {
|
|
53
|
+
throw new Error(`No extension found for format ${collection.get('format')}`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return ext.replace(/^\./, '');
|
|
53
57
|
},
|
|
54
58
|
fields(collection: Collection) {
|
|
55
59
|
return collection.get('fields');
|
package/src/types/redux.ts
CHANGED