decap-cms-core 3.13.0 → 3.14.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 +18 -18
- package/dist/decap-cms-core.js.map +1 -1
- package/dist/esm/actions/config.js +14 -1
- package/dist/esm/actions/entries.js +15 -4
- package/dist/esm/backend.js +2 -0
- package/dist/esm/bootstrap.js +2 -2
- package/dist/esm/components/App/App.js +12 -5
- package/dist/esm/components/App/Header.js +18 -18
- package/dist/esm/components/Collection/Entries/EntryCard.js +30 -15
- package/dist/esm/components/Collection/NestedCollection.js +20 -11
- package/dist/esm/components/Editor/EditorPreviewPane/EditorPreviewPane.js +22 -6
- package/dist/esm/components/UI/ErrorBoundary.js +2 -2
- package/dist/esm/components/UI/SettingsDropdown.js +25 -27
- package/dist/esm/constants/configSchema.js +1 -1
- package/dist/esm/lib/registry.js +4 -1
- package/dist/esm/reducers/entryDraft.js +36 -3
- package/index.d.ts +17 -1
- package/package.json +2 -2
- package/src/__tests__/backend.spec.js +214 -0
- package/src/actions/__tests__/config.spec.js +14 -0
- package/src/actions/__tests__/entries.spec.js +36 -1
- package/src/actions/config.ts +13 -1
- package/src/actions/entries.ts +22 -7
- package/src/backend.ts +2 -0
- package/src/components/App/App.js +22 -13
- package/src/components/App/Header.js +36 -11
- package/src/components/Collection/Entries/EntryCard.js +13 -3
- package/src/components/Collection/NestedCollection.js +14 -7
- package/src/components/Collection/__tests__/NestedCollection.spec.js +1 -1
- package/src/components/Collection/__tests__/__snapshots__/NestedCollection.spec.js.snap +0 -68
- package/src/components/Editor/EditorPreviewPane/EditorPreviewPane.js +6 -5
- package/src/components/Editor/__tests__/__snapshots__/EditorToolbar.spec.js.snap +104 -72
- package/src/components/UI/SettingsDropdown.js +36 -9
- package/src/constants/__tests__/configSchema.spec.js +9 -6
- package/src/constants/configSchema.js +1 -1
- package/src/lib/__tests__/formatters.spec.js +16 -4
- package/src/lib/__tests__/registry.spec.js +3 -3
- package/src/lib/registry.js +4 -1
- package/src/reducers/__tests__/entryDraft.spec.js +117 -0
- package/src/reducers/entryDraft.js +43 -3
- package/src/types/redux.ts +19 -2
|
@@ -106,6 +106,7 @@ exports[`EditorToolbar should render normal save button 1`] = `
|
|
|
106
106
|
-moz-user-select: none;
|
|
107
107
|
-ms-user-select: none;
|
|
108
108
|
user-select: none;
|
|
109
|
+
touch-action: manipulation;
|
|
109
110
|
margin: 0 10px;
|
|
110
111
|
}
|
|
111
112
|
|
|
@@ -127,6 +128,7 @@ exports[`EditorToolbar should render normal save button 1`] = `
|
|
|
127
128
|
padding-left: 20px;
|
|
128
129
|
padding-right: 40px;
|
|
129
130
|
position: relative;
|
|
131
|
+
white-space: nowrap;
|
|
130
132
|
overflow: hidden;
|
|
131
133
|
white-space: nowrap;
|
|
132
134
|
text-overflow: ellipsis;
|
|
@@ -245,15 +247,17 @@ exports[`EditorToolbar should render normal save button 1`] = `
|
|
|
245
247
|
<div
|
|
246
248
|
class="emotion-14 emotion-15 emotion-16"
|
|
247
249
|
>
|
|
248
|
-
<
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
250
|
+
<div>
|
|
251
|
+
<span
|
|
252
|
+
aria-expanded="false"
|
|
253
|
+
aria-haspopup="true"
|
|
254
|
+
class="emotion-17 emotion-18"
|
|
255
|
+
role="button"
|
|
256
|
+
tabindex="0"
|
|
257
|
+
>
|
|
258
|
+
editor.editorToolbar.publish
|
|
259
|
+
</span>
|
|
260
|
+
</div>
|
|
257
261
|
</div>
|
|
258
262
|
</div>
|
|
259
263
|
<div>
|
|
@@ -383,6 +387,7 @@ exports[`EditorToolbar should render normal save button 2`] = `
|
|
|
383
387
|
-moz-user-select: none;
|
|
384
388
|
-ms-user-select: none;
|
|
385
389
|
user-select: none;
|
|
390
|
+
touch-action: manipulation;
|
|
386
391
|
margin: 0 10px;
|
|
387
392
|
}
|
|
388
393
|
|
|
@@ -404,6 +409,7 @@ exports[`EditorToolbar should render normal save button 2`] = `
|
|
|
404
409
|
padding-left: 20px;
|
|
405
410
|
padding-right: 40px;
|
|
406
411
|
position: relative;
|
|
412
|
+
white-space: nowrap;
|
|
407
413
|
overflow: hidden;
|
|
408
414
|
white-space: nowrap;
|
|
409
415
|
text-overflow: ellipsis;
|
|
@@ -522,15 +528,17 @@ exports[`EditorToolbar should render normal save button 2`] = `
|
|
|
522
528
|
<div
|
|
523
529
|
class="emotion-14 emotion-15 emotion-16"
|
|
524
530
|
>
|
|
525
|
-
<
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
531
|
+
<div>
|
|
532
|
+
<span
|
|
533
|
+
aria-expanded="false"
|
|
534
|
+
aria-haspopup="true"
|
|
535
|
+
class="emotion-17 emotion-18"
|
|
536
|
+
role="button"
|
|
537
|
+
tabindex="0"
|
|
538
|
+
>
|
|
539
|
+
editor.editorToolbar.publish
|
|
540
|
+
</span>
|
|
541
|
+
</div>
|
|
534
542
|
</div>
|
|
535
543
|
</div>
|
|
536
544
|
<div>
|
|
@@ -927,6 +935,7 @@ exports[`EditorToolbar should render with status=draft,useOpenAuthoring=false 1`
|
|
|
927
935
|
-moz-user-select: none;
|
|
928
936
|
-ms-user-select: none;
|
|
929
937
|
user-select: none;
|
|
938
|
+
touch-action: manipulation;
|
|
930
939
|
margin: 0 10px;
|
|
931
940
|
}
|
|
932
941
|
|
|
@@ -948,6 +957,7 @@ exports[`EditorToolbar should render with status=draft,useOpenAuthoring=false 1`
|
|
|
948
957
|
padding-left: 20px;
|
|
949
958
|
padding-right: 40px;
|
|
950
959
|
position: relative;
|
|
960
|
+
white-space: nowrap;
|
|
951
961
|
overflow: hidden;
|
|
952
962
|
white-space: nowrap;
|
|
953
963
|
text-overflow: ellipsis;
|
|
@@ -1072,15 +1082,17 @@ exports[`EditorToolbar should render with status=draft,useOpenAuthoring=false 1`
|
|
|
1072
1082
|
<div
|
|
1073
1083
|
class="emotion-16 emotion-17 emotion-18"
|
|
1074
1084
|
>
|
|
1075
|
-
<
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1085
|
+
<div>
|
|
1086
|
+
<span
|
|
1087
|
+
aria-expanded="false"
|
|
1088
|
+
aria-haspopup="true"
|
|
1089
|
+
class="emotion-19 emotion-20"
|
|
1090
|
+
role="button"
|
|
1091
|
+
tabindex="0"
|
|
1092
|
+
>
|
|
1093
|
+
editor.editorToolbar.status
|
|
1094
|
+
</span>
|
|
1095
|
+
</div>
|
|
1084
1096
|
</div>
|
|
1085
1097
|
<button
|
|
1086
1098
|
class="emotion-21 emotion-22"
|
|
@@ -1238,6 +1250,7 @@ exports[`EditorToolbar should render with status=draft,useOpenAuthoring=true 1`]
|
|
|
1238
1250
|
-moz-user-select: none;
|
|
1239
1251
|
-ms-user-select: none;
|
|
1240
1252
|
user-select: none;
|
|
1253
|
+
touch-action: manipulation;
|
|
1241
1254
|
margin: 0 10px;
|
|
1242
1255
|
}
|
|
1243
1256
|
|
|
@@ -1259,6 +1272,7 @@ exports[`EditorToolbar should render with status=draft,useOpenAuthoring=true 1`]
|
|
|
1259
1272
|
padding-left: 20px;
|
|
1260
1273
|
padding-right: 40px;
|
|
1261
1274
|
position: relative;
|
|
1275
|
+
white-space: nowrap;
|
|
1262
1276
|
overflow: hidden;
|
|
1263
1277
|
white-space: nowrap;
|
|
1264
1278
|
text-overflow: ellipsis;
|
|
@@ -1417,15 +1431,17 @@ exports[`EditorToolbar should render with status=draft,useOpenAuthoring=true 1`]
|
|
|
1417
1431
|
<div
|
|
1418
1432
|
class="emotion-16 emotion-17 emotion-18"
|
|
1419
1433
|
>
|
|
1420
|
-
<
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1434
|
+
<div>
|
|
1435
|
+
<span
|
|
1436
|
+
aria-expanded="false"
|
|
1437
|
+
aria-haspopup="true"
|
|
1438
|
+
class="emotion-19 emotion-20"
|
|
1439
|
+
role="button"
|
|
1440
|
+
tabindex="0"
|
|
1441
|
+
>
|
|
1442
|
+
editor.editorToolbar.status
|
|
1443
|
+
</span>
|
|
1444
|
+
</div>
|
|
1429
1445
|
</div>
|
|
1430
1446
|
<div
|
|
1431
1447
|
class="emotion-21 emotion-22"
|
|
@@ -1610,6 +1626,7 @@ exports[`EditorToolbar should render with status=pending_publish,useOpenAuthorin
|
|
|
1610
1626
|
-moz-user-select: none;
|
|
1611
1627
|
-ms-user-select: none;
|
|
1612
1628
|
user-select: none;
|
|
1629
|
+
touch-action: manipulation;
|
|
1613
1630
|
margin: 0 10px;
|
|
1614
1631
|
}
|
|
1615
1632
|
|
|
@@ -1631,6 +1648,7 @@ exports[`EditorToolbar should render with status=pending_publish,useOpenAuthorin
|
|
|
1631
1648
|
padding-left: 20px;
|
|
1632
1649
|
padding-right: 40px;
|
|
1633
1650
|
position: relative;
|
|
1651
|
+
white-space: nowrap;
|
|
1634
1652
|
overflow: hidden;
|
|
1635
1653
|
white-space: nowrap;
|
|
1636
1654
|
text-overflow: ellipsis;
|
|
@@ -1755,15 +1773,17 @@ exports[`EditorToolbar should render with status=pending_publish,useOpenAuthorin
|
|
|
1755
1773
|
<div
|
|
1756
1774
|
class="emotion-16 emotion-17 emotion-18"
|
|
1757
1775
|
>
|
|
1758
|
-
<
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1776
|
+
<div>
|
|
1777
|
+
<span
|
|
1778
|
+
aria-expanded="false"
|
|
1779
|
+
aria-haspopup="true"
|
|
1780
|
+
class="emotion-19 emotion-20"
|
|
1781
|
+
role="button"
|
|
1782
|
+
tabindex="0"
|
|
1783
|
+
>
|
|
1784
|
+
editor.editorToolbar.status
|
|
1785
|
+
</span>
|
|
1786
|
+
</div>
|
|
1767
1787
|
</div>
|
|
1768
1788
|
<button
|
|
1769
1789
|
class="emotion-21 emotion-22"
|
|
@@ -1921,6 +1941,7 @@ exports[`EditorToolbar should render with status=pending_publish,useOpenAuthorin
|
|
|
1921
1941
|
-moz-user-select: none;
|
|
1922
1942
|
-ms-user-select: none;
|
|
1923
1943
|
user-select: none;
|
|
1944
|
+
touch-action: manipulation;
|
|
1924
1945
|
margin: 0 10px;
|
|
1925
1946
|
}
|
|
1926
1947
|
|
|
@@ -1942,6 +1963,7 @@ exports[`EditorToolbar should render with status=pending_publish,useOpenAuthorin
|
|
|
1942
1963
|
padding-left: 20px;
|
|
1943
1964
|
padding-right: 40px;
|
|
1944
1965
|
position: relative;
|
|
1966
|
+
white-space: nowrap;
|
|
1945
1967
|
overflow: hidden;
|
|
1946
1968
|
white-space: nowrap;
|
|
1947
1969
|
text-overflow: ellipsis;
|
|
@@ -2082,15 +2104,17 @@ exports[`EditorToolbar should render with status=pending_publish,useOpenAuthorin
|
|
|
2082
2104
|
<div
|
|
2083
2105
|
class="emotion-16 emotion-17 emotion-18"
|
|
2084
2106
|
>
|
|
2085
|
-
<
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2107
|
+
<div>
|
|
2108
|
+
<span
|
|
2109
|
+
aria-expanded="false"
|
|
2110
|
+
aria-haspopup="true"
|
|
2111
|
+
class="emotion-19 emotion-20"
|
|
2112
|
+
role="button"
|
|
2113
|
+
tabindex="0"
|
|
2114
|
+
>
|
|
2115
|
+
editor.editorToolbar.status
|
|
2116
|
+
</span>
|
|
2117
|
+
</div>
|
|
2094
2118
|
</div>
|
|
2095
2119
|
<div
|
|
2096
2120
|
class="emotion-21 emotion-22"
|
|
@@ -2270,6 +2294,7 @@ exports[`EditorToolbar should render with status=pending_review,useOpenAuthoring
|
|
|
2270
2294
|
-moz-user-select: none;
|
|
2271
2295
|
-ms-user-select: none;
|
|
2272
2296
|
user-select: none;
|
|
2297
|
+
touch-action: manipulation;
|
|
2273
2298
|
margin: 0 10px;
|
|
2274
2299
|
}
|
|
2275
2300
|
|
|
@@ -2291,6 +2316,7 @@ exports[`EditorToolbar should render with status=pending_review,useOpenAuthoring
|
|
|
2291
2316
|
padding-left: 20px;
|
|
2292
2317
|
padding-right: 40px;
|
|
2293
2318
|
position: relative;
|
|
2319
|
+
white-space: nowrap;
|
|
2294
2320
|
overflow: hidden;
|
|
2295
2321
|
white-space: nowrap;
|
|
2296
2322
|
text-overflow: ellipsis;
|
|
@@ -2415,15 +2441,17 @@ exports[`EditorToolbar should render with status=pending_review,useOpenAuthoring
|
|
|
2415
2441
|
<div
|
|
2416
2442
|
class="emotion-16 emotion-17 emotion-18"
|
|
2417
2443
|
>
|
|
2418
|
-
<
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2444
|
+
<div>
|
|
2445
|
+
<span
|
|
2446
|
+
aria-expanded="false"
|
|
2447
|
+
aria-haspopup="true"
|
|
2448
|
+
class="emotion-19 emotion-20"
|
|
2449
|
+
role="button"
|
|
2450
|
+
tabindex="0"
|
|
2451
|
+
>
|
|
2452
|
+
editor.editorToolbar.status
|
|
2453
|
+
</span>
|
|
2454
|
+
</div>
|
|
2427
2455
|
</div>
|
|
2428
2456
|
<button
|
|
2429
2457
|
class="emotion-21 emotion-22"
|
|
@@ -2581,6 +2609,7 @@ exports[`EditorToolbar should render with status=pending_review,useOpenAuthoring
|
|
|
2581
2609
|
-moz-user-select: none;
|
|
2582
2610
|
-ms-user-select: none;
|
|
2583
2611
|
user-select: none;
|
|
2612
|
+
touch-action: manipulation;
|
|
2584
2613
|
margin: 0 10px;
|
|
2585
2614
|
}
|
|
2586
2615
|
|
|
@@ -2602,6 +2631,7 @@ exports[`EditorToolbar should render with status=pending_review,useOpenAuthoring
|
|
|
2602
2631
|
padding-left: 20px;
|
|
2603
2632
|
padding-right: 40px;
|
|
2604
2633
|
position: relative;
|
|
2634
|
+
white-space: nowrap;
|
|
2605
2635
|
overflow: hidden;
|
|
2606
2636
|
white-space: nowrap;
|
|
2607
2637
|
text-overflow: ellipsis;
|
|
@@ -2760,15 +2790,17 @@ exports[`EditorToolbar should render with status=pending_review,useOpenAuthoring
|
|
|
2760
2790
|
<div
|
|
2761
2791
|
class="emotion-16 emotion-17 emotion-18"
|
|
2762
2792
|
>
|
|
2763
|
-
<
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2793
|
+
<div>
|
|
2794
|
+
<span
|
|
2795
|
+
aria-expanded="false"
|
|
2796
|
+
aria-haspopup="true"
|
|
2797
|
+
class="emotion-19 emotion-20"
|
|
2798
|
+
role="button"
|
|
2799
|
+
tabindex="0"
|
|
2800
|
+
>
|
|
2801
|
+
editor.editorToolbar.status
|
|
2802
|
+
</span>
|
|
2803
|
+
</div>
|
|
2772
2804
|
</div>
|
|
2773
2805
|
<div
|
|
2774
2806
|
class="emotion-21 emotion-22"
|
|
@@ -3,7 +3,14 @@ import PropTypes from 'prop-types';
|
|
|
3
3
|
import { css } from '@emotion/react';
|
|
4
4
|
import styled from '@emotion/styled';
|
|
5
5
|
import { translate } from 'react-polyglot';
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
Icon,
|
|
8
|
+
Dropdown,
|
|
9
|
+
DropdownItem,
|
|
10
|
+
DropdownButton,
|
|
11
|
+
colors,
|
|
12
|
+
shadows,
|
|
13
|
+
} from 'decap-cms-ui-default';
|
|
7
14
|
|
|
8
15
|
import { stripProtocol } from '../../lib/urlHelper';
|
|
9
16
|
|
|
@@ -33,18 +40,32 @@ const AvatarPlaceholderIcon = styled(Icon)`
|
|
|
33
40
|
background-color: ${colors.textFieldBorder};
|
|
34
41
|
`;
|
|
35
42
|
|
|
36
|
-
const
|
|
43
|
+
const AppHeaderLink = css`
|
|
37
44
|
font-size: 14px;
|
|
38
45
|
font-weight: 400;
|
|
39
|
-
color:
|
|
46
|
+
color: ${colors.text};
|
|
40
47
|
padding: 10px 16px;
|
|
48
|
+
overflow: hidden;
|
|
49
|
+
text-overflow: ellipsis;
|
|
50
|
+
white-space: nowrap;
|
|
41
51
|
`;
|
|
42
52
|
|
|
53
|
+
const AppHeaderSiteLink = styled.a(AppHeaderLink);
|
|
54
|
+
|
|
43
55
|
const AppHeaderTestRepoIndicator = styled.a`
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
56
|
+
${AppHeaderLink};
|
|
57
|
+
|
|
58
|
+
@media (max-width: 399px) {
|
|
59
|
+
position: absolute;
|
|
60
|
+
top: 0;
|
|
61
|
+
left: 50%;
|
|
62
|
+
transform: translateX(-50%);
|
|
63
|
+
font-size: 12px;
|
|
64
|
+
background-color: ${colors.background};
|
|
65
|
+
padding: 4px 12px;
|
|
66
|
+
border-radius: 0 0 4px 4px;
|
|
67
|
+
${shadows.drop}
|
|
68
|
+
}
|
|
48
69
|
`;
|
|
49
70
|
|
|
50
71
|
function Avatar({ imageUrl }) {
|
|
@@ -59,9 +80,15 @@ Avatar.propTypes = {
|
|
|
59
80
|
imageUrl: PropTypes.string,
|
|
60
81
|
};
|
|
61
82
|
|
|
83
|
+
const SettingsWrapper = styled.div`
|
|
84
|
+
display: flex;
|
|
85
|
+
align-items: center;
|
|
86
|
+
min-width: 0;
|
|
87
|
+
`;
|
|
88
|
+
|
|
62
89
|
function SettingsDropdown({ displayUrl, isTestRepo, imageUrl, onLogoutClick, t }) {
|
|
63
90
|
return (
|
|
64
|
-
<
|
|
91
|
+
<SettingsWrapper>
|
|
65
92
|
{isTestRepo && (
|
|
66
93
|
<AppHeaderTestRepoIndicator
|
|
67
94
|
href="https://www.decapcms.org/docs/test-backend"
|
|
@@ -88,7 +115,7 @@ function SettingsDropdown({ displayUrl, isTestRepo, imageUrl, onLogoutClick, t }
|
|
|
88
115
|
>
|
|
89
116
|
<DropdownItem label={t('ui.settingsDropdown.logOut')} onClick={onLogoutClick} />
|
|
90
117
|
</Dropdown>
|
|
91
|
-
</
|
|
118
|
+
</SettingsWrapper>
|
|
92
119
|
);
|
|
93
120
|
}
|
|
94
121
|
|
|
@@ -477,22 +477,25 @@ describe('config', () => {
|
|
|
477
477
|
merge({}, validConfig, { collections: [{ meta: { path: { label: 'Label' } } }] }),
|
|
478
478
|
);
|
|
479
479
|
}).toThrowError("'collections[0].meta.path' must have required property 'widget'");
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
it('should allow collection meta to have a path configuration with index_file', () => {
|
|
480
483
|
expect(() => {
|
|
481
484
|
validateConfig(
|
|
482
485
|
merge({}, validConfig, {
|
|
483
|
-
collections: [
|
|
486
|
+
collections: [
|
|
487
|
+
{ meta: { path: { label: 'Path', widget: 'string', index_file: 'index' } } },
|
|
488
|
+
],
|
|
484
489
|
}),
|
|
485
490
|
);
|
|
486
|
-
}).
|
|
491
|
+
}).not.toThrow();
|
|
487
492
|
});
|
|
488
493
|
|
|
489
|
-
it('should allow collection meta to have a path configuration', () => {
|
|
494
|
+
it('should allow collection meta to have a path configuration without index_file', () => {
|
|
490
495
|
expect(() => {
|
|
491
496
|
validateConfig(
|
|
492
497
|
merge({}, validConfig, {
|
|
493
|
-
collections: [
|
|
494
|
-
{ meta: { path: { label: 'Path', widget: 'string', index_file: 'index' } } },
|
|
495
|
-
],
|
|
498
|
+
collections: [{ meta: { path: { label: 'Path', widget: 'string' } } }],
|
|
496
499
|
}),
|
|
497
500
|
);
|
|
498
501
|
}).not.toThrow();
|
|
@@ -295,8 +295,8 @@ describe('formatters', () => {
|
|
|
295
295
|
};
|
|
296
296
|
|
|
297
297
|
describe('slugFormatter', () => {
|
|
298
|
-
const date = new Date('2020-01-
|
|
299
|
-
|
|
298
|
+
const date = new Date('2020-01-01T13:28:27.679Z').valueOf();
|
|
299
|
+
jest.spyOn(Date, 'now').mockImplementation(() => date);
|
|
300
300
|
|
|
301
301
|
const { selectIdentifier } = require('../../reducers/collections');
|
|
302
302
|
|
|
@@ -333,10 +333,22 @@ describe('formatters', () => {
|
|
|
333
333
|
).toBe('entry-slug');
|
|
334
334
|
});
|
|
335
335
|
|
|
336
|
+
it('should allow filters in slug templates', () => {
|
|
337
|
+
selectIdentifier.mockReturnValueOnce('published');
|
|
338
|
+
|
|
339
|
+
expect(
|
|
340
|
+
slugFormatter(
|
|
341
|
+
Map({ slug: "{{published | date('MM-DD')}}" }),
|
|
342
|
+
Map({ title: 'Post Title', published: new Date(date) }),
|
|
343
|
+
slugConfig,
|
|
344
|
+
),
|
|
345
|
+
).toBe('01-01');
|
|
346
|
+
});
|
|
347
|
+
|
|
336
348
|
it('should see date filters applied to date from entry if it exists', () => {
|
|
337
349
|
const { selectInferredField } = require('../../reducers/collections');
|
|
338
350
|
selectInferredField.mockReturnValue('date');
|
|
339
|
-
const entryDate = new Date('2026-10-
|
|
351
|
+
const entryDate = new Date('2026-10-20T13:28:27.679Z');
|
|
340
352
|
|
|
341
353
|
expect(
|
|
342
354
|
slugFormatter(
|
|
@@ -350,7 +362,7 @@ describe('formatters', () => {
|
|
|
350
362
|
it('should see date filters applied to publishDate from entry if it exists', () => {
|
|
351
363
|
const { selectInferredField } = require('../../reducers/collections');
|
|
352
364
|
selectInferredField.mockReturnValue('publishDate');
|
|
353
|
-
const entryDate = new Date('2026-10-
|
|
365
|
+
const entryDate = new Date('2026-10-20T13:28:27.679Z');
|
|
354
366
|
|
|
355
367
|
expect(
|
|
356
368
|
slugFormatter(
|
|
@@ -200,7 +200,7 @@ describe('registry', () => {
|
|
|
200
200
|
});
|
|
201
201
|
});
|
|
202
202
|
|
|
203
|
-
it(`should return
|
|
203
|
+
it(`should return the complete updated entry object`, async () => {
|
|
204
204
|
const { registerEventListener, invokeEvent } = require('../registry');
|
|
205
205
|
|
|
206
206
|
const event = 'preSave';
|
|
@@ -233,7 +233,7 @@ describe('registry', () => {
|
|
|
233
233
|
expect(handler1).toHaveBeenCalledWith(data, options);
|
|
234
234
|
expect(handler2).toHaveBeenCalledWith(dataAfterFirstHandlerExecution, options);
|
|
235
235
|
|
|
236
|
-
expect(result).toEqual(dataAfterSecondHandlerExecution.entry
|
|
236
|
+
expect(result).toEqual(dataAfterSecondHandlerExecution.entry);
|
|
237
237
|
});
|
|
238
238
|
|
|
239
239
|
it('should allow multiple events to not return a value', async () => {
|
|
@@ -254,7 +254,7 @@ describe('registry', () => {
|
|
|
254
254
|
|
|
255
255
|
expect(handler1).toHaveBeenCalledWith(data, options);
|
|
256
256
|
expect(handler2).toHaveBeenCalledWith(data, options);
|
|
257
|
-
expect(result).toEqual(data.entry
|
|
257
|
+
expect(result).toEqual(data.entry);
|
|
258
258
|
});
|
|
259
259
|
});
|
|
260
260
|
});
|
package/src/lib/registry.js
CHANGED
|
@@ -257,7 +257,10 @@ export async function invokeEvent({ name, data }) {
|
|
|
257
257
|
_data = { ...data, entry };
|
|
258
258
|
}
|
|
259
259
|
}
|
|
260
|
-
|
|
260
|
+
// Return the full entry object with all metadata (slug, path, meta, etc.)
|
|
261
|
+
// rather than just the data payload. Callers like invokePreSaveEvent expect
|
|
262
|
+
// the complete entry object to be preserved through the event handler chain.
|
|
263
|
+
return _data.entry;
|
|
261
264
|
}
|
|
262
265
|
|
|
263
266
|
export function removeEventListener({ name, handler }) {
|
|
@@ -195,4 +195,121 @@ describe('entryDraft reducer', () => {
|
|
|
195
195
|
});
|
|
196
196
|
});
|
|
197
197
|
});
|
|
198
|
+
|
|
199
|
+
describe('selectCustomPath', () => {
|
|
200
|
+
let selectCustomPath;
|
|
201
|
+
let selectHasMetaPath;
|
|
202
|
+
let selectFolderEntryExtension;
|
|
203
|
+
|
|
204
|
+
beforeEach(() => {
|
|
205
|
+
jest.resetModules();
|
|
206
|
+
selectHasMetaPath = jest.fn(
|
|
207
|
+
collection => collection.has('meta') && collection.get('meta').has('path'),
|
|
208
|
+
);
|
|
209
|
+
selectFolderEntryExtension = jest.fn(collection => collection.get('extension') || 'md');
|
|
210
|
+
|
|
211
|
+
jest.doMock('../collections', () => ({
|
|
212
|
+
selectHasMetaPath,
|
|
213
|
+
selectFolderEntryExtension,
|
|
214
|
+
}));
|
|
215
|
+
|
|
216
|
+
const entryDraftModule = require('../entryDraft');
|
|
217
|
+
selectCustomPath = entryDraftModule.selectCustomPath;
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
afterEach(() => {
|
|
221
|
+
jest.unmock('../collections');
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('should generate dynamic filename for new entries without index_file', () => {
|
|
225
|
+
const collection = fromJS({
|
|
226
|
+
folder: '_pages',
|
|
227
|
+
extension: 'md',
|
|
228
|
+
meta: { path: { label: 'Path', widget: 'string' } },
|
|
229
|
+
});
|
|
230
|
+
const entryDraft = fromJS({
|
|
231
|
+
entry: {
|
|
232
|
+
newRecord: true,
|
|
233
|
+
data: { title: 'My Great Article' },
|
|
234
|
+
meta: { path: 'blog' },
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
const result = selectCustomPath(collection, entryDraft);
|
|
239
|
+
expect(result).toBe('_pages/blog/my-great-article.md');
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it('should preserve filename for existing entries without index_file', () => {
|
|
243
|
+
const collection = fromJS({
|
|
244
|
+
folder: '_pages',
|
|
245
|
+
extension: 'md',
|
|
246
|
+
meta: { path: { label: 'Path', widget: 'string' } },
|
|
247
|
+
});
|
|
248
|
+
const entryDraft = fromJS({
|
|
249
|
+
entry: {
|
|
250
|
+
newRecord: false,
|
|
251
|
+
path: '_pages/old-folder/existing-file.md',
|
|
252
|
+
data: { title: 'Updated Title' },
|
|
253
|
+
meta: { path: 'new-folder' },
|
|
254
|
+
},
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
const result = selectCustomPath(collection, entryDraft);
|
|
258
|
+
expect(result).toBe('_pages/new-folder/existing-file.md');
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('should use index_file when specified (backward compatibility)', () => {
|
|
262
|
+
const collection = fromJS({
|
|
263
|
+
folder: '_pages',
|
|
264
|
+
extension: 'md',
|
|
265
|
+
meta: { path: { label: 'Path', widget: 'string', index_file: 'index' } },
|
|
266
|
+
});
|
|
267
|
+
const entryDraft = fromJS({
|
|
268
|
+
entry: {
|
|
269
|
+
newRecord: true,
|
|
270
|
+
data: { title: 'My Article' },
|
|
271
|
+
meta: { path: 'blog' },
|
|
272
|
+
},
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
const result = selectCustomPath(collection, entryDraft);
|
|
276
|
+
expect(result).toBe('_pages/blog/index.md');
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it('should return undefined when path is not set', () => {
|
|
280
|
+
const collection = fromJS({
|
|
281
|
+
folder: '_pages',
|
|
282
|
+
extension: 'md',
|
|
283
|
+
meta: { path: { label: 'Path', widget: 'string' } },
|
|
284
|
+
});
|
|
285
|
+
const entryDraft = fromJS({
|
|
286
|
+
entry: {
|
|
287
|
+
newRecord: true,
|
|
288
|
+
data: { title: 'My Article' },
|
|
289
|
+
meta: {},
|
|
290
|
+
},
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
const result = selectCustomPath(collection, entryDraft);
|
|
294
|
+
expect(result).toBeUndefined();
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it('should preserve non-latin characters in generated filename', () => {
|
|
298
|
+
const collection = fromJS({
|
|
299
|
+
folder: '_pages',
|
|
300
|
+
extension: 'md',
|
|
301
|
+
meta: { path: { label: 'Path', widget: 'string' } },
|
|
302
|
+
});
|
|
303
|
+
const entryDraft = fromJS({
|
|
304
|
+
entry: {
|
|
305
|
+
newRecord: true,
|
|
306
|
+
data: { title: '日本語のタイトル' },
|
|
307
|
+
meta: { path: 'blog' },
|
|
308
|
+
},
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
const result = selectCustomPath(collection, entryDraft);
|
|
312
|
+
expect(result).toBe('_pages/blog/日本語のタイトル.md');
|
|
313
|
+
});
|
|
314
|
+
});
|
|
198
315
|
});
|