@pranaysahith/decap-cms-core 3.9.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.
Files changed (299) hide show
  1. package/README.md +9 -0
  2. package/dist/@pranaysahith/decap-cms-core.js +52 -0
  3. package/dist/@pranaysahith/decap-cms-core.js.LICENSE.txt +141 -0
  4. package/dist/@pranaysahith/decap-cms-core.js.map +1 -0
  5. package/dist/decap-cms-core.js +47 -0
  6. package/dist/decap-cms-core.js.LICENSE.txt +116 -0
  7. package/dist/decap-cms-core.js.map +1 -0
  8. package/dist/esm/actions/auth.js +97 -0
  9. package/dist/esm/actions/collections.js +15 -0
  10. package/dist/esm/actions/config.js +493 -0
  11. package/dist/esm/actions/deploys.js +79 -0
  12. package/dist/esm/actions/editorialWorkflow.js +480 -0
  13. package/dist/esm/actions/entries.js +865 -0
  14. package/dist/esm/actions/media.js +147 -0
  15. package/dist/esm/actions/mediaLibrary.js +552 -0
  16. package/dist/esm/actions/notifications.js +21 -0
  17. package/dist/esm/actions/search.js +149 -0
  18. package/dist/esm/actions/status.js +74 -0
  19. package/dist/esm/actions/waitUntil.js +32 -0
  20. package/dist/esm/backend.js +1082 -0
  21. package/dist/esm/bootstrap.js +101 -0
  22. package/dist/esm/components/App/App.js +289 -0
  23. package/dist/esm/components/App/Header.js +172 -0
  24. package/dist/esm/components/App/NotFoundPage.js +19 -0
  25. package/dist/esm/components/Collection/Collection.js +198 -0
  26. package/dist/esm/components/Collection/CollectionControls.js +46 -0
  27. package/dist/esm/components/Collection/CollectionSearch.js +222 -0
  28. package/dist/esm/components/Collection/CollectionTop.js +68 -0
  29. package/dist/esm/components/Collection/ControlButton.js +17 -0
  30. package/dist/esm/components/Collection/Entries/Entries.js +73 -0
  31. package/dist/esm/components/Collection/Entries/EntriesCollection.js +241 -0
  32. package/dist/esm/components/Collection/Entries/EntriesSearch.js +113 -0
  33. package/dist/esm/components/Collection/Entries/EntryCard.js +177 -0
  34. package/dist/esm/components/Collection/Entries/EntryListing.js +143 -0
  35. package/dist/esm/components/Collection/FilterControl.js +33 -0
  36. package/dist/esm/components/Collection/FolderRenameControl.js +403 -0
  37. package/dist/esm/components/Collection/GroupControl.js +33 -0
  38. package/dist/esm/components/Collection/NestedCollection.js +308 -0
  39. package/dist/esm/components/Collection/Sidebar.js +91 -0
  40. package/dist/esm/components/Collection/SortControl.js +59 -0
  41. package/dist/esm/components/Collection/ViewStyleControl.js +38 -0
  42. package/dist/esm/components/Editor/Editor.js +466 -0
  43. package/dist/esm/components/Editor/EditorControlPane/EditorControl.js +395 -0
  44. package/dist/esm/components/Editor/EditorControlPane/EditorControlPane.js +254 -0
  45. package/dist/esm/components/Editor/EditorControlPane/Widget.js +374 -0
  46. package/dist/esm/components/Editor/EditorInterface.js +386 -0
  47. package/dist/esm/components/Editor/EditorPreviewPane/EditorPreview.js +47 -0
  48. package/dist/esm/components/Editor/EditorPreviewPane/EditorPreviewContent.js +66 -0
  49. package/dist/esm/components/Editor/EditorPreviewPane/EditorPreviewPane.js +288 -0
  50. package/dist/esm/components/Editor/EditorPreviewPane/PreviewHOC.js +27 -0
  51. package/dist/esm/components/Editor/EditorToolbar.js +536 -0
  52. package/dist/esm/components/Editor/EntryPathEditor.js +272 -0
  53. package/dist/esm/components/Editor/withWorkflow.js +56 -0
  54. package/dist/esm/components/EditorWidgets/Unknown/UnknownControl.js +18 -0
  55. package/dist/esm/components/EditorWidgets/Unknown/UnknownPreview.js +20 -0
  56. package/dist/esm/components/EditorWidgets/index.js +4 -0
  57. package/dist/esm/components/MediaLibrary/EmptyMessage.js +22 -0
  58. package/dist/esm/components/MediaLibrary/MediaLibrary.js +446 -0
  59. package/dist/esm/components/MediaLibrary/MediaLibraryButtons.js +93 -0
  60. package/dist/esm/components/MediaLibrary/MediaLibraryCard.js +99 -0
  61. package/dist/esm/components/MediaLibrary/MediaLibraryCardGrid.js +198 -0
  62. package/dist/esm/components/MediaLibrary/MediaLibraryHeader.js +32 -0
  63. package/dist/esm/components/MediaLibrary/MediaLibraryModal.js +156 -0
  64. package/dist/esm/components/MediaLibrary/MediaLibrarySearch.js +51 -0
  65. package/dist/esm/components/MediaLibrary/MediaLibraryTop.js +123 -0
  66. package/dist/esm/components/UI/DragDrop.js +67 -0
  67. package/dist/esm/components/UI/ErrorBoundary.js +173 -0
  68. package/dist/esm/components/UI/FileUploadButton.js +27 -0
  69. package/dist/esm/components/UI/Modal.js +104 -0
  70. package/dist/esm/components/UI/Notifications.js +62 -0
  71. package/dist/esm/components/UI/SettingsDropdown.js +107 -0
  72. package/dist/esm/components/UI/index.js +6 -0
  73. package/dist/esm/components/Workflow/Workflow.js +133 -0
  74. package/dist/esm/components/Workflow/WorkflowCard.js +128 -0
  75. package/dist/esm/components/Workflow/WorkflowList.js +204 -0
  76. package/dist/esm/constants/collectionTypes.js +2 -0
  77. package/dist/esm/constants/collectionViews.js +2 -0
  78. package/dist/esm/constants/commitProps.js +2 -0
  79. package/dist/esm/constants/configSchema.js +644 -0
  80. package/dist/esm/constants/fieldInference.js +57 -0
  81. package/dist/esm/constants/publishModes.js +18 -0
  82. package/dist/esm/constants/validationErrorTypes.js +6 -0
  83. package/dist/esm/formats/formats.js +83 -0
  84. package/dist/esm/formats/frontmatter.js +146 -0
  85. package/dist/esm/formats/helpers.js +12 -0
  86. package/dist/esm/formats/json.js +8 -0
  87. package/dist/esm/formats/toml.js +32 -0
  88. package/dist/esm/formats/yaml.js +51 -0
  89. package/dist/esm/index.js +7 -0
  90. package/dist/esm/integrations/index.js +28 -0
  91. package/dist/esm/integrations/providers/algolia/implementation.js +174 -0
  92. package/dist/esm/integrations/providers/assetStore/implementation.js +165 -0
  93. package/dist/esm/lib/consoleError.js +3 -0
  94. package/dist/esm/lib/formatters.js +191 -0
  95. package/dist/esm/lib/i18n.js +367 -0
  96. package/dist/esm/lib/phrases.js +6 -0
  97. package/dist/esm/lib/polyfill.js +8 -0
  98. package/dist/esm/lib/registry.js +329 -0
  99. package/dist/esm/lib/serializeEntryValues.js +67 -0
  100. package/dist/esm/lib/stega.js +142 -0
  101. package/dist/esm/lib/textHelper.js +9 -0
  102. package/dist/esm/lib/urlHelper.js +111 -0
  103. package/dist/esm/mediaLibrary.js +37 -0
  104. package/dist/esm/reducers/auth.js +27 -0
  105. package/dist/esm/reducers/collections.js +428 -0
  106. package/dist/esm/reducers/combinedReducer.js +8 -0
  107. package/dist/esm/reducers/config.js +29 -0
  108. package/dist/esm/reducers/cursors.js +31 -0
  109. package/dist/esm/reducers/deploys.js +45 -0
  110. package/dist/esm/reducers/editorialWorkflow.js +83 -0
  111. package/dist/esm/reducers/entries.js +568 -0
  112. package/dist/esm/reducers/entryDraft.js +212 -0
  113. package/dist/esm/reducers/globalUI.js +25 -0
  114. package/dist/esm/reducers/index.js +66 -0
  115. package/dist/esm/reducers/integrations.js +53 -0
  116. package/dist/esm/reducers/mediaLibrary.js +252 -0
  117. package/dist/esm/reducers/medias.js +68 -0
  118. package/dist/esm/reducers/notifications.js +23 -0
  119. package/dist/esm/reducers/search.js +92 -0
  120. package/dist/esm/reducers/status.js +30 -0
  121. package/dist/esm/redux/index.js +7 -0
  122. package/dist/esm/redux/middleware/waitUntilAction.js +48 -0
  123. package/dist/esm/routing/history.js +12 -0
  124. package/dist/esm/types/diacritics.d.js +0 -0
  125. package/dist/esm/types/global.d.js +1 -0
  126. package/dist/esm/types/immutable.js +7 -0
  127. package/dist/esm/types/redux.js +14 -0
  128. package/dist/esm/types/tomlify-j0.4.d.js +0 -0
  129. package/dist/esm/valueObjects/AssetProxy.js +44 -0
  130. package/dist/esm/valueObjects/EditorComponent.js +34 -0
  131. package/dist/esm/valueObjects/Entry.js +20 -0
  132. package/index.d.ts +618 -0
  133. package/package.json +106 -0
  134. package/src/__tests__/backend.spec.js +1161 -0
  135. package/src/actions/__tests__/config.spec.js +1009 -0
  136. package/src/actions/__tests__/editorialWorkflow.spec.js +216 -0
  137. package/src/actions/__tests__/entries.spec.js +596 -0
  138. package/src/actions/__tests__/media.spec.ts +171 -0
  139. package/src/actions/__tests__/mediaLibrary.spec.js +327 -0
  140. package/src/actions/__tests__/search.spec.js +209 -0
  141. package/src/actions/auth.ts +127 -0
  142. package/src/actions/collections.ts +18 -0
  143. package/src/actions/config.ts +565 -0
  144. package/src/actions/deploys.ts +104 -0
  145. package/src/actions/editorialWorkflow.ts +567 -0
  146. package/src/actions/entries.ts +1055 -0
  147. package/src/actions/media.ts +139 -0
  148. package/src/actions/mediaLibrary.ts +574 -0
  149. package/src/actions/notifications.ts +36 -0
  150. package/src/actions/search.ts +221 -0
  151. package/src/actions/status.ts +99 -0
  152. package/src/actions/waitUntil.ts +49 -0
  153. package/src/backend.ts +1400 -0
  154. package/src/bootstrap.js +104 -0
  155. package/src/components/App/App.js +286 -0
  156. package/src/components/App/Header.js +266 -0
  157. package/src/components/App/NotFoundPage.js +23 -0
  158. package/src/components/Collection/Collection.js +210 -0
  159. package/src/components/Collection/CollectionControls.js +58 -0
  160. package/src/components/Collection/CollectionSearch.js +243 -0
  161. package/src/components/Collection/CollectionTop.js +81 -0
  162. package/src/components/Collection/ControlButton.js +27 -0
  163. package/src/components/Collection/Entries/Entries.js +82 -0
  164. package/src/components/Collection/Entries/EntriesCollection.js +277 -0
  165. package/src/components/Collection/Entries/EntriesSearch.js +102 -0
  166. package/src/components/Collection/Entries/EntryCard.js +246 -0
  167. package/src/components/Collection/Entries/EntryListing.js +151 -0
  168. package/src/components/Collection/Entries/__tests__/EntriesCollection.spec.js +163 -0
  169. package/src/components/Collection/Entries/__tests__/__snapshots__/EntriesCollection.spec.js.snap +46 -0
  170. package/src/components/Collection/FilterControl.js +39 -0
  171. package/src/components/Collection/GroupControl.js +39 -0
  172. package/src/components/Collection/NestedCollection.js +330 -0
  173. package/src/components/Collection/Sidebar.js +136 -0
  174. package/src/components/Collection/SortControl.js +68 -0
  175. package/src/components/Collection/ViewStyleControl.js +50 -0
  176. package/src/components/Collection/__tests__/Collection.spec.js +75 -0
  177. package/src/components/Collection/__tests__/NestedCollection.spec.js +445 -0
  178. package/src/components/Collection/__tests__/Sidebar.spec.js +87 -0
  179. package/src/components/Collection/__tests__/__snapshots__/Collection.spec.js.snap +144 -0
  180. package/src/components/Collection/__tests__/__snapshots__/NestedCollection.spec.js.snap +550 -0
  181. package/src/components/Collection/__tests__/__snapshots__/Sidebar.spec.js.snap +312 -0
  182. package/src/components/Editor/Editor.js +497 -0
  183. package/src/components/Editor/EditorControlPane/EditorControl.js +452 -0
  184. package/src/components/Editor/EditorControlPane/EditorControlPane.js +269 -0
  185. package/src/components/Editor/EditorControlPane/Widget.js +384 -0
  186. package/src/components/Editor/EditorInterface.js +444 -0
  187. package/src/components/Editor/EditorPreviewPane/EditorPreview.js +40 -0
  188. package/src/components/Editor/EditorPreviewPane/EditorPreviewContent.js +74 -0
  189. package/src/components/Editor/EditorPreviewPane/EditorPreviewPane.js +333 -0
  190. package/src/components/Editor/EditorPreviewPane/PreviewHOC.js +33 -0
  191. package/src/components/Editor/EditorToolbar.js +691 -0
  192. package/src/components/Editor/__tests__/Editor.spec.js +221 -0
  193. package/src/components/Editor/__tests__/EditorToolbar.spec.js +120 -0
  194. package/src/components/Editor/__tests__/__snapshots__/Editor.spec.js.snap +45 -0
  195. package/src/components/Editor/__tests__/__snapshots__/EditorToolbar.spec.js.snap +4233 -0
  196. package/src/components/Editor/withWorkflow.js +61 -0
  197. package/src/components/EditorWidgets/Unknown/UnknownControl.js +17 -0
  198. package/src/components/EditorWidgets/Unknown/UnknownPreview.js +19 -0
  199. package/src/components/EditorWidgets/index.js +5 -0
  200. package/src/components/MediaLibrary/EmptyMessage.js +28 -0
  201. package/src/components/MediaLibrary/MediaLibrary.js +411 -0
  202. package/src/components/MediaLibrary/MediaLibraryButtons.js +136 -0
  203. package/src/components/MediaLibrary/MediaLibraryCard.js +128 -0
  204. package/src/components/MediaLibrary/MediaLibraryCardGrid.js +199 -0
  205. package/src/components/MediaLibrary/MediaLibraryHeader.js +48 -0
  206. package/src/components/MediaLibrary/MediaLibraryModal.js +200 -0
  207. package/src/components/MediaLibrary/MediaLibrarySearch.js +61 -0
  208. package/src/components/MediaLibrary/MediaLibraryTop.js +143 -0
  209. package/src/components/MediaLibrary/__tests__/MediaLibraryButtons.spec.js +45 -0
  210. package/src/components/MediaLibrary/__tests__/MediaLibraryCard.spec.js +49 -0
  211. package/src/components/MediaLibrary/__tests__/__snapshots__/MediaLibraryCard.spec.js.snap +264 -0
  212. package/src/components/UI/DragDrop.js +66 -0
  213. package/src/components/UI/ErrorBoundary.js +214 -0
  214. package/src/components/UI/FileUploadButton.js +24 -0
  215. package/src/components/UI/Modal.js +112 -0
  216. package/src/components/UI/Notifications.tsx +83 -0
  217. package/src/components/UI/SettingsDropdown.js +103 -0
  218. package/src/components/UI/__tests__/ErrorBoundary.spec.js +57 -0
  219. package/src/components/UI/index.js +6 -0
  220. package/src/components/Workflow/Workflow.js +169 -0
  221. package/src/components/Workflow/WorkflowCard.js +177 -0
  222. package/src/components/Workflow/WorkflowList.js +272 -0
  223. package/src/constants/__tests__/configSchema.spec.js +611 -0
  224. package/src/constants/collectionTypes.ts +2 -0
  225. package/src/constants/collectionViews.js +2 -0
  226. package/src/constants/commitProps.ts +2 -0
  227. package/src/constants/configSchema.js +441 -0
  228. package/src/constants/fieldInference.tsx +78 -0
  229. package/src/constants/publishModes.ts +22 -0
  230. package/src/constants/validationErrorTypes.js +6 -0
  231. package/src/formats/__tests__/formats.spec.js +87 -0
  232. package/src/formats/__tests__/frontmatter.spec.js +429 -0
  233. package/src/formats/__tests__/toml.spec.js +9 -0
  234. package/src/formats/__tests__/yaml.spec.js +162 -0
  235. package/src/formats/formats.ts +97 -0
  236. package/src/formats/frontmatter.ts +150 -0
  237. package/src/formats/helpers.ts +14 -0
  238. package/src/formats/json.ts +9 -0
  239. package/src/formats/toml.ts +33 -0
  240. package/src/formats/yaml.ts +58 -0
  241. package/src/index.js +8 -0
  242. package/src/integrations/index.js +35 -0
  243. package/src/integrations/providers/algolia/implementation.js +176 -0
  244. package/src/integrations/providers/assetStore/implementation.js +148 -0
  245. package/src/lib/__tests__/formatters.spec.js +751 -0
  246. package/src/lib/__tests__/i18n.spec.js +792 -0
  247. package/src/lib/__tests__/phrases.spec.js +119 -0
  248. package/src/lib/__tests__/registry.spec.js +261 -0
  249. package/src/lib/__tests__/serializeEntryValues.spec.js +22 -0
  250. package/src/lib/__tests__/urlHelper.spec.js +138 -0
  251. package/src/lib/consoleError.js +7 -0
  252. package/src/lib/formatters.ts +286 -0
  253. package/src/lib/i18n.ts +454 -0
  254. package/src/lib/phrases.js +8 -0
  255. package/src/lib/polyfill.js +9 -0
  256. package/src/lib/registry.js +312 -0
  257. package/src/lib/serializeEntryValues.js +75 -0
  258. package/src/lib/stega.ts +145 -0
  259. package/src/lib/textHelper.js +11 -0
  260. package/src/lib/urlHelper.ts +128 -0
  261. package/src/mediaLibrary.ts +51 -0
  262. package/src/reducers/__tests__/auth.spec.ts +38 -0
  263. package/src/reducers/__tests__/collections.spec.js +610 -0
  264. package/src/reducers/__tests__/config.spec.js +38 -0
  265. package/src/reducers/__tests__/entries.spec.js +694 -0
  266. package/src/reducers/__tests__/entryDraft.spec.js +297 -0
  267. package/src/reducers/__tests__/globalUI.js +43 -0
  268. package/src/reducers/__tests__/integrations.spec.ts +76 -0
  269. package/src/reducers/__tests__/mediaLibrary.spec.js +154 -0
  270. package/src/reducers/__tests__/medias.spec.ts +49 -0
  271. package/src/reducers/auth.ts +46 -0
  272. package/src/reducers/collections.ts +535 -0
  273. package/src/reducers/combinedReducer.ts +11 -0
  274. package/src/reducers/config.ts +38 -0
  275. package/src/reducers/cursors.js +36 -0
  276. package/src/reducers/deploys.ts +52 -0
  277. package/src/reducers/editorialWorkflow.ts +163 -0
  278. package/src/reducers/entries.ts +819 -0
  279. package/src/reducers/entryDraft.js +260 -0
  280. package/src/reducers/globalUI.ts +45 -0
  281. package/src/reducers/index.ts +82 -0
  282. package/src/reducers/integrations.ts +59 -0
  283. package/src/reducers/mediaLibrary.ts +296 -0
  284. package/src/reducers/medias.ts +66 -0
  285. package/src/reducers/notifications.ts +52 -0
  286. package/src/reducers/search.ts +111 -0
  287. package/src/reducers/status.ts +40 -0
  288. package/src/redux/index.ts +18 -0
  289. package/src/redux/middleware/waitUntilAction.ts +64 -0
  290. package/src/routing/__tests__/history.spec.ts +49 -0
  291. package/src/routing/history.ts +17 -0
  292. package/src/types/diacritics.d.ts +1 -0
  293. package/src/types/global.d.ts +8 -0
  294. package/src/types/immutable.ts +49 -0
  295. package/src/types/redux.ts +827 -0
  296. package/src/types/tomlify-j0.4.d.ts +13 -0
  297. package/src/valueObjects/AssetProxy.ts +48 -0
  298. package/src/valueObjects/EditorComponent.js +38 -0
  299. package/src/valueObjects/Entry.ts +63 -0
@@ -0,0 +1,1055 @@
1
+ import { fromJS, List, Map } from 'immutable';
2
+ import isEqual from 'lodash/isEqual';
3
+ import { Cursor } from 'decap-cms-lib-util';
4
+
5
+ import { selectCollectionEntriesCursor } from '../reducers/cursors';
6
+ import { selectFields, updateFieldByKey, selectDefaultSortField } from '../reducers/collections';
7
+ import { selectIntegration, selectPublishedSlugs } from '../reducers';
8
+ import { getIntegrationProvider } from '../integrations';
9
+ import { currentBackend } from '../backend';
10
+ import { serializeValues } from '../lib/serializeEntryValues';
11
+ import { createEntry } from '../valueObjects/Entry';
12
+ import { createAssetProxy } from '../valueObjects/AssetProxy';
13
+ import ValidationErrorTypes from '../constants/validationErrorTypes';
14
+ import { addAssets, getAsset } from './media';
15
+ import { SortDirection } from '../types/redux';
16
+ import { waitForMediaLibraryToLoad, loadMedia } from './mediaLibrary';
17
+ import { waitUntil } from './waitUntil';
18
+ import { selectIsFetching, selectEntriesSortFields, selectEntryByPath } from '../reducers/entries';
19
+ import { selectCustomPath } from '../reducers/entryDraft';
20
+ import { navigateToEntry } from '../routing/history';
21
+ import { getProcessSegment } from '../lib/formatters';
22
+ import { hasI18n, duplicateDefaultI18nFields, serializeI18n, I18N, I18N_FIELD } from '../lib/i18n';
23
+ import { addNotification } from './notifications';
24
+
25
+ import type { ImplementationMediaFile } from 'decap-cms-lib-util';
26
+ import type { AnyAction } from 'redux';
27
+ import type { ThunkDispatch } from 'redux-thunk';
28
+ import type {
29
+ Collection,
30
+ EntryMap,
31
+ State,
32
+ EntryFields,
33
+ EntryField,
34
+ ViewFilter,
35
+ ViewGroup,
36
+ Entry,
37
+ } from '../types/redux';
38
+ import type { EntryValue } from '../valueObjects/Entry';
39
+ import type { Backend } from '../backend';
40
+ import type AssetProxy from '../valueObjects/AssetProxy';
41
+ import type { Set } from 'immutable';
42
+
43
+ /*
44
+ * Constant Declarations
45
+ */
46
+ export const ENTRY_REQUEST = 'ENTRY_REQUEST';
47
+ export const ENTRY_SUCCESS = 'ENTRY_SUCCESS';
48
+ export const ENTRY_FAILURE = 'ENTRY_FAILURE';
49
+
50
+ export const ENTRIES_REQUEST = 'ENTRIES_REQUEST';
51
+ export const ENTRIES_SUCCESS = 'ENTRIES_SUCCESS';
52
+ export const ENTRIES_FAILURE = 'ENTRIES_FAILURE';
53
+
54
+ export const SORT_ENTRIES_REQUEST = 'SORT_ENTRIES_REQUEST';
55
+ export const SORT_ENTRIES_SUCCESS = 'SORT_ENTRIES_SUCCESS';
56
+ export const SORT_ENTRIES_FAILURE = 'SORT_ENTRIES_FAILURE';
57
+
58
+ export const FILTER_ENTRIES_REQUEST = 'FILTER_ENTRIES_REQUEST';
59
+ export const FILTER_ENTRIES_SUCCESS = 'FILTER_ENTRIES_SUCCESS';
60
+ export const FILTER_ENTRIES_FAILURE = 'FILTER_ENTRIES_FAILURE';
61
+
62
+ export const GROUP_ENTRIES_REQUEST = 'GROUP_ENTRIES_REQUEST';
63
+ export const GROUP_ENTRIES_SUCCESS = 'GROUP_ENTRIES_SUCCESS';
64
+ export const GROUP_ENTRIES_FAILURE = 'GROUP_ENTRIES_FAILURE';
65
+
66
+ export const DRAFT_CREATE_FROM_ENTRY = 'DRAFT_CREATE_FROM_ENTRY';
67
+ export const DRAFT_CREATE_EMPTY = 'DRAFT_CREATE_EMPTY';
68
+ export const DRAFT_DISCARD = 'DRAFT_DISCARD';
69
+ export const DRAFT_CHANGE_FIELD = 'DRAFT_CHANGE_FIELD';
70
+ export const DRAFT_VALIDATION_ERRORS = 'DRAFT_VALIDATION_ERRORS';
71
+ export const DRAFT_CLEAR_ERRORS = 'DRAFT_CLEAR_ERRORS';
72
+ export const DRAFT_LOCAL_BACKUP_RETRIEVED = 'DRAFT_LOCAL_BACKUP_RETRIEVED';
73
+ export const DRAFT_CREATE_FROM_LOCAL_BACKUP = 'DRAFT_CREATE_FROM_LOCAL_BACKUP';
74
+ export const DRAFT_CREATE_DUPLICATE_FROM_ENTRY = 'DRAFT_CREATE_DUPLICATE_FROM_ENTRY';
75
+
76
+ export const ENTRY_PERSIST_REQUEST = 'ENTRY_PERSIST_REQUEST';
77
+ export const ENTRY_PERSIST_SUCCESS = 'ENTRY_PERSIST_SUCCESS';
78
+ export const ENTRY_PERSIST_FAILURE = 'ENTRY_PERSIST_FAILURE';
79
+
80
+ export const ENTRY_DELETE_REQUEST = 'ENTRY_DELETE_REQUEST';
81
+ export const ENTRY_DELETE_SUCCESS = 'ENTRY_DELETE_SUCCESS';
82
+ export const ENTRY_DELETE_FAILURE = 'ENTRY_DELETE_FAILURE';
83
+
84
+ export const ADD_DRAFT_ENTRY_MEDIA_FILE = 'ADD_DRAFT_ENTRY_MEDIA_FILE';
85
+ export const REMOVE_DRAFT_ENTRY_MEDIA_FILE = 'REMOVE_DRAFT_ENTRY_MEDIA_FILE';
86
+
87
+ export const CHANGE_VIEW_STYLE = 'CHANGE_VIEW_STYLE';
88
+
89
+ /*
90
+ * Simple Action Creators (Internal)
91
+ * We still need to export them for tests
92
+ */
93
+ export function entryLoading(collection: Collection, slug: string) {
94
+ return {
95
+ type: ENTRY_REQUEST,
96
+ payload: {
97
+ collection: collection.get('name'),
98
+ slug,
99
+ },
100
+ };
101
+ }
102
+
103
+ export function entryLoaded(collection: Collection, entry: EntryValue) {
104
+ return {
105
+ type: ENTRY_SUCCESS,
106
+ payload: {
107
+ collection: collection.get('name'),
108
+ entry,
109
+ },
110
+ };
111
+ }
112
+
113
+ export function entryLoadError(error: Error, collection: Collection, slug: string) {
114
+ return {
115
+ type: ENTRY_FAILURE,
116
+ payload: {
117
+ error,
118
+ collection: collection.get('name'),
119
+ slug,
120
+ },
121
+ };
122
+ }
123
+
124
+ export function entriesLoading(collection: Collection) {
125
+ return {
126
+ type: ENTRIES_REQUEST,
127
+ payload: {
128
+ collection: collection.get('name'),
129
+ },
130
+ };
131
+ }
132
+
133
+ export function entriesLoaded(
134
+ collection: Collection,
135
+ entries: EntryValue[],
136
+ pagination: number | null,
137
+ cursor: Cursor,
138
+ append = true,
139
+ ) {
140
+ return {
141
+ type: ENTRIES_SUCCESS,
142
+ payload: {
143
+ collection: collection.get('name'),
144
+ entries,
145
+ page: pagination,
146
+ cursor: Cursor.create(cursor),
147
+ append,
148
+ },
149
+ };
150
+ }
151
+
152
+ export function entriesFailed(collection: Collection, error: Error) {
153
+ return {
154
+ type: ENTRIES_FAILURE,
155
+ error: 'Failed to load entries',
156
+ payload: error.toString(),
157
+ meta: { collection: collection.get('name') },
158
+ };
159
+ }
160
+
161
+ export async function getAllEntries(state: State, collection: Collection) {
162
+ const backend = currentBackend(state.config);
163
+ const integration = selectIntegration(state, collection.get('name'), 'listEntries');
164
+ const provider: Backend = integration
165
+ ? getIntegrationProvider(state.integrations, backend.getToken, integration)
166
+ : backend;
167
+ const entries = await provider.listAllEntries(collection);
168
+ return entries;
169
+ }
170
+
171
+ export function sortByField(
172
+ collection: Collection,
173
+ key: string,
174
+ direction: SortDirection = SortDirection.Ascending,
175
+ ) {
176
+ return async (dispatch: ThunkDispatch<State, {}, AnyAction>, getState: () => State) => {
177
+ const state = getState();
178
+ // if we're already fetching we update the sort key, but skip loading entries
179
+ const isFetching = selectIsFetching(state.entries, collection.get('name'));
180
+ dispatch({
181
+ type: SORT_ENTRIES_REQUEST,
182
+ payload: {
183
+ collection: collection.get('name'),
184
+ key,
185
+ direction,
186
+ },
187
+ });
188
+ if (isFetching) {
189
+ return;
190
+ }
191
+
192
+ try {
193
+ const entries = await getAllEntries(state, collection);
194
+ dispatch({
195
+ type: SORT_ENTRIES_SUCCESS,
196
+ payload: {
197
+ collection: collection.get('name'),
198
+ key,
199
+ direction,
200
+ entries,
201
+ },
202
+ });
203
+ } catch (error) {
204
+ dispatch({
205
+ type: SORT_ENTRIES_FAILURE,
206
+ payload: {
207
+ collection: collection.get('name'),
208
+ key,
209
+ direction,
210
+ error,
211
+ },
212
+ });
213
+ }
214
+ };
215
+ }
216
+
217
+ export function filterByField(collection: Collection, filter: ViewFilter) {
218
+ return async (dispatch: ThunkDispatch<State, {}, AnyAction>, getState: () => State) => {
219
+ const state = getState();
220
+ // if we're already fetching we update the filter key, but skip loading entries
221
+ const isFetching = selectIsFetching(state.entries, collection.get('name'));
222
+ dispatch({
223
+ type: FILTER_ENTRIES_REQUEST,
224
+ payload: {
225
+ collection: collection.get('name'),
226
+ filter,
227
+ },
228
+ });
229
+ if (isFetching) {
230
+ return;
231
+ }
232
+
233
+ try {
234
+ const entries = await getAllEntries(state, collection);
235
+ dispatch({
236
+ type: FILTER_ENTRIES_SUCCESS,
237
+ payload: {
238
+ collection: collection.get('name'),
239
+ filter,
240
+ entries,
241
+ },
242
+ });
243
+ } catch (error) {
244
+ dispatch({
245
+ type: FILTER_ENTRIES_FAILURE,
246
+ payload: {
247
+ collection: collection.get('name'),
248
+ filter,
249
+ error,
250
+ },
251
+ });
252
+ }
253
+ };
254
+ }
255
+
256
+ export function groupByField(collection: Collection, group: ViewGroup) {
257
+ return async (dispatch: ThunkDispatch<State, {}, AnyAction>, getState: () => State) => {
258
+ const state = getState();
259
+ const isFetching = selectIsFetching(state.entries, collection.get('name'));
260
+ dispatch({
261
+ type: GROUP_ENTRIES_REQUEST,
262
+ payload: {
263
+ collection: collection.get('name'),
264
+ group,
265
+ },
266
+ });
267
+ if (isFetching) {
268
+ return;
269
+ }
270
+
271
+ try {
272
+ const entries = await getAllEntries(state, collection);
273
+ dispatch({
274
+ type: GROUP_ENTRIES_SUCCESS,
275
+ payload: {
276
+ collection: collection.get('name'),
277
+ group,
278
+ entries,
279
+ },
280
+ });
281
+ } catch (error) {
282
+ dispatch({
283
+ type: GROUP_ENTRIES_FAILURE,
284
+ payload: {
285
+ collection: collection.get('name'),
286
+ group,
287
+ error,
288
+ },
289
+ });
290
+ }
291
+ };
292
+ }
293
+
294
+ export function changeViewStyle(viewStyle: string) {
295
+ return {
296
+ type: CHANGE_VIEW_STYLE,
297
+ payload: {
298
+ style: viewStyle,
299
+ },
300
+ };
301
+ }
302
+
303
+ export function entryPersisting(collection: Collection, entry: EntryMap) {
304
+ return {
305
+ type: ENTRY_PERSIST_REQUEST,
306
+ payload: {
307
+ collectionName: collection.get('name'),
308
+ entrySlug: entry.get('slug'),
309
+ },
310
+ };
311
+ }
312
+
313
+ export function entryPersisted(collection: Collection, entry: EntryMap, slug: string) {
314
+ return {
315
+ type: ENTRY_PERSIST_SUCCESS,
316
+ payload: {
317
+ collectionName: collection.get('name'),
318
+ entrySlug: entry.get('slug'),
319
+
320
+ /**
321
+ * Pass slug from backend for newly created entries.
322
+ */
323
+ slug,
324
+ },
325
+ };
326
+ }
327
+
328
+ export function entryPersistFail(collection: Collection, entry: EntryMap, error: Error) {
329
+ return {
330
+ type: ENTRY_PERSIST_FAILURE,
331
+ error: 'Failed to persist entry',
332
+ payload: {
333
+ collectionName: collection.get('name'),
334
+ entrySlug: entry.get('slug'),
335
+ error: error.toString(),
336
+ },
337
+ };
338
+ }
339
+
340
+ export function entryDeleting(collection: Collection, slug: string) {
341
+ return {
342
+ type: ENTRY_DELETE_REQUEST,
343
+ payload: {
344
+ collectionName: collection.get('name'),
345
+ entrySlug: slug,
346
+ },
347
+ };
348
+ }
349
+
350
+ export function entryDeleted(collection: Collection, slug: string) {
351
+ return {
352
+ type: ENTRY_DELETE_SUCCESS,
353
+ payload: {
354
+ collectionName: collection.get('name'),
355
+ entrySlug: slug,
356
+ },
357
+ };
358
+ }
359
+
360
+ export function entryDeleteFail(collection: Collection, slug: string, error: Error) {
361
+ return {
362
+ type: ENTRY_DELETE_FAILURE,
363
+ payload: {
364
+ collectionName: collection.get('name'),
365
+ entrySlug: slug,
366
+ error: error.toString(),
367
+ },
368
+ };
369
+ }
370
+
371
+ export function emptyDraftCreated(entry: EntryValue) {
372
+ return {
373
+ type: DRAFT_CREATE_EMPTY,
374
+ payload: entry,
375
+ };
376
+ }
377
+ /*
378
+ * Exported simple Action Creators
379
+ */
380
+ export function createDraftFromEntry(entry: EntryValue) {
381
+ return {
382
+ type: DRAFT_CREATE_FROM_ENTRY,
383
+ payload: { entry },
384
+ };
385
+ }
386
+
387
+ export function draftDuplicateEntry(entry: EntryMap) {
388
+ return {
389
+ type: DRAFT_CREATE_DUPLICATE_FROM_ENTRY,
390
+ payload: createEntry(entry.get('collection'), '', '', {
391
+ data: entry.get('data'),
392
+ i18n: entry.get('i18n'),
393
+ mediaFiles: entry.get('mediaFiles').toJS(),
394
+ }),
395
+ };
396
+ }
397
+
398
+ export function discardDraft() {
399
+ return { type: DRAFT_DISCARD };
400
+ }
401
+
402
+ export function changeDraftField({
403
+ field,
404
+ value,
405
+ metadata,
406
+ entries,
407
+ i18n,
408
+ }: {
409
+ field: EntryField;
410
+ value: string;
411
+ metadata: Record<string, unknown>;
412
+ entries: EntryMap[];
413
+ i18n?: {
414
+ currentLocale: string;
415
+ defaultLocale: string;
416
+ locales: string[];
417
+ };
418
+ }) {
419
+ return {
420
+ type: DRAFT_CHANGE_FIELD,
421
+ payload: { field, value, metadata, entries, i18n },
422
+ };
423
+ }
424
+
425
+ export function changeDraftFieldValidation(
426
+ uniquefieldId: string,
427
+ errors: { type: string; parentIds: string[]; message: string }[],
428
+ ) {
429
+ return {
430
+ type: DRAFT_VALIDATION_ERRORS,
431
+ payload: { uniquefieldId, errors },
432
+ };
433
+ }
434
+
435
+ export function clearFieldErrors(uniqueFieldId: string) {
436
+ return {
437
+ type: DRAFT_CLEAR_ERRORS,
438
+ payload: { uniqueFieldId },
439
+ };
440
+ }
441
+
442
+ export function localBackupRetrieved(entry: EntryValue) {
443
+ return {
444
+ type: DRAFT_LOCAL_BACKUP_RETRIEVED,
445
+ payload: { entry },
446
+ };
447
+ }
448
+
449
+ export function loadLocalBackup() {
450
+ return {
451
+ type: DRAFT_CREATE_FROM_LOCAL_BACKUP,
452
+ };
453
+ }
454
+
455
+ export function addDraftEntryMediaFile(file: ImplementationMediaFile) {
456
+ return { type: ADD_DRAFT_ENTRY_MEDIA_FILE, payload: file };
457
+ }
458
+
459
+ export function removeDraftEntryMediaFile({ id }: { id: string }) {
460
+ return { type: REMOVE_DRAFT_ENTRY_MEDIA_FILE, payload: { id } };
461
+ }
462
+
463
+ export function persistLocalBackup(entry: EntryMap, collection: Collection) {
464
+ return (_dispatch: ThunkDispatch<State, {}, AnyAction>, getState: () => State) => {
465
+ const state = getState();
466
+ const backend = currentBackend(state.config);
467
+
468
+ return backend.persistLocalDraftBackup(entry, collection);
469
+ };
470
+ }
471
+
472
+ export function createDraftDuplicateFromEntry(entry: EntryMap) {
473
+ return (dispatch: ThunkDispatch<State, {}, AnyAction>) => {
474
+ dispatch(
475
+ waitUntil({
476
+ predicate: ({ type }) => type === DRAFT_CREATE_EMPTY,
477
+ run: () => dispatch(draftDuplicateEntry(entry)),
478
+ }),
479
+ );
480
+ };
481
+ }
482
+
483
+ export function retrieveLocalBackup(collection: Collection, slug: string) {
484
+ return async (dispatch: ThunkDispatch<State, {}, AnyAction>, getState: () => State) => {
485
+ const state = getState();
486
+ const backend = currentBackend(state.config);
487
+ const { entry } = await backend.getLocalDraftBackup(collection, slug);
488
+
489
+ if (entry) {
490
+ // load assets from backup
491
+ const mediaFiles = entry.mediaFiles || [];
492
+ const assetProxies: AssetProxy[] = await Promise.all(
493
+ mediaFiles.map(file => {
494
+ if (file.file || file.url) {
495
+ return createAssetProxy({
496
+ path: file.path,
497
+ file: file.file,
498
+ url: file.url,
499
+ field: file.field,
500
+ });
501
+ } else {
502
+ return getAsset({
503
+ collection,
504
+ entry: fromJS(entry),
505
+ path: file.path,
506
+ field: file.field,
507
+ })(dispatch, getState);
508
+ }
509
+ }),
510
+ );
511
+ dispatch(addAssets(assetProxies));
512
+
513
+ return dispatch(localBackupRetrieved(entry));
514
+ }
515
+ };
516
+ }
517
+
518
+ export function deleteLocalBackup(collection: Collection, slug: string) {
519
+ return (_dispatch: ThunkDispatch<State, {}, AnyAction>, getState: () => State) => {
520
+ const state = getState();
521
+ const backend = currentBackend(state.config);
522
+ return backend.deleteLocalDraftBackup(collection, slug);
523
+ };
524
+ }
525
+
526
+ /*
527
+ * Exported Thunk Action Creators
528
+ */
529
+
530
+ export function loadEntry(collection: Collection, slug: string) {
531
+ return async (dispatch: ThunkDispatch<State, {}, AnyAction>, getState: () => State) => {
532
+ await waitForMediaLibraryToLoad(dispatch, getState());
533
+ dispatch(entryLoading(collection, slug));
534
+
535
+ try {
536
+ const loadedEntry = await tryLoadEntry(getState(), collection, slug);
537
+ dispatch(entryLoaded(collection, loadedEntry));
538
+ dispatch(createDraftFromEntry(loadedEntry));
539
+ } catch (error) {
540
+ dispatch(
541
+ addNotification({
542
+ message: {
543
+ details: error.message,
544
+ key: 'ui.toast.onFailToLoadEntries',
545
+ },
546
+ type: 'error',
547
+ dismissAfter: 8000,
548
+ }),
549
+ );
550
+ dispatch(entryLoadError(error, collection, slug));
551
+ }
552
+ };
553
+ }
554
+
555
+ export async function tryLoadEntry(state: State, collection: Collection, slug: string) {
556
+ const backend = currentBackend(state.config);
557
+ const loadedEntry = await backend.getEntry(state, collection, slug);
558
+ return loadedEntry;
559
+ }
560
+
561
+ const appendActions = fromJS({
562
+ ['append_next']: { action: 'next', append: true },
563
+ });
564
+
565
+ function addAppendActionsToCursor(cursor: Cursor) {
566
+ return Cursor.create(cursor).updateStore('actions', (actions: Set<string>) => {
567
+ return actions.union(
568
+ appendActions
569
+ .filter((v: Map<string, string | boolean>) => actions.has(v.get('action') as string))
570
+ .keySeq(),
571
+ );
572
+ });
573
+ }
574
+
575
+ export function loadEntries(collection: Collection, page = 0) {
576
+ return async (dispatch: ThunkDispatch<State, {}, AnyAction>, getState: () => State) => {
577
+ if (collection.get('isFetching')) {
578
+ return;
579
+ }
580
+ const state = getState();
581
+ const sortFields = selectEntriesSortFields(state.entries, collection.get('name'));
582
+
583
+ // If user has already set a sort, use it
584
+ if (sortFields && sortFields.length > 0) {
585
+ const field = sortFields[0];
586
+ return dispatch(sortByField(collection, field.get('key'), field.get('direction')));
587
+ }
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
+
597
+ const backend = currentBackend(state.config);
598
+ const integration = selectIntegration(state, collection.get('name'), 'listEntries');
599
+ const provider = integration
600
+ ? getIntegrationProvider(state.integrations, backend.getToken, integration)
601
+ : backend;
602
+ const append = !!(page && !isNaN(page) && page > 0);
603
+ dispatch(entriesLoading(collection));
604
+
605
+ try {
606
+ const loadAllEntries = collection.has('nested') || hasI18n(collection);
607
+
608
+ let response: {
609
+ cursor: Cursor;
610
+ pagination: number;
611
+ entries: EntryValue[];
612
+ } = await (loadAllEntries
613
+ ? // nested collections require all entries to construct the tree
614
+ provider.listAllEntries(collection).then((entries: EntryValue[]) => ({ entries }))
615
+ : provider.listEntries(collection, page));
616
+ response = {
617
+ ...response,
618
+ // The only existing backend using the pagination system is the
619
+ // Algolia integration, which is also the only integration used
620
+ // to list entries. Thus, this checking for an integration can
621
+ // determine whether or not this is using the old integer-based
622
+ // pagination API. Other backends will simply store an empty
623
+ // cursor, which behaves identically to no cursor at all.
624
+ cursor: integration
625
+ ? Cursor.create({
626
+ actions: ['next'],
627
+ meta: { usingOldPaginationAPI: true },
628
+ data: { nextPage: page + 1 },
629
+ })
630
+ : Cursor.create(response.cursor),
631
+ };
632
+
633
+ dispatch(
634
+ entriesLoaded(
635
+ collection,
636
+ response.cursor.meta!.get('usingOldPaginationAPI')
637
+ ? response.entries.reverse()
638
+ : response.entries,
639
+ response.pagination,
640
+ addAppendActionsToCursor(response.cursor),
641
+ append,
642
+ ),
643
+ );
644
+ } catch (err) {
645
+ dispatch(
646
+ addNotification({
647
+ message: {
648
+ details: err,
649
+ key: 'ui.toast.onFailToLoadEntries',
650
+ },
651
+ type: 'error',
652
+ dismissAfter: 8000,
653
+ }),
654
+ );
655
+ return Promise.reject(dispatch(entriesFailed(collection, err)));
656
+ }
657
+ };
658
+ }
659
+
660
+ function traverseCursor(backend: Backend, cursor: Cursor, action: string) {
661
+ if (!cursor.actions!.has(action)) {
662
+ throw new Error(`The current cursor does not support the pagination action "${action}".`);
663
+ }
664
+ return backend.traverseCursor(cursor, action);
665
+ }
666
+
667
+ export function traverseCollectionCursor(collection: Collection, action: string) {
668
+ return async (dispatch: ThunkDispatch<State, {}, AnyAction>, getState: () => State) => {
669
+ const state = getState();
670
+ const collectionName = collection.get('name');
671
+ if (state.entries.getIn(['pages', `${collectionName}`, 'isFetching'])) {
672
+ return;
673
+ }
674
+ const backend = currentBackend(state.config);
675
+
676
+ const { action: realAction, append } = appendActions.has(action)
677
+ ? appendActions.get(action).toJS()
678
+ : { action, append: false };
679
+ const cursor = selectCollectionEntriesCursor(state.cursors, collection.get('name'));
680
+
681
+ // Handle cursors representing pages in the old, integer-based
682
+ // pagination API
683
+ if (cursor.meta!.get('usingOldPaginationAPI', false)) {
684
+ return dispatch(loadEntries(collection, cursor.data!.get('nextPage') as number));
685
+ }
686
+
687
+ try {
688
+ dispatch(entriesLoading(collection));
689
+ const { entries, cursor: newCursor } = await traverseCursor(backend, cursor, realAction);
690
+
691
+ const pagination = newCursor.meta?.get('page');
692
+ return dispatch(
693
+ entriesLoaded(collection, entries, pagination, addAppendActionsToCursor(newCursor), append),
694
+ );
695
+ } catch (err) {
696
+ console.error(err);
697
+ dispatch(
698
+ addNotification({
699
+ message: {
700
+ details: err,
701
+ key: 'ui.toast.onFailToLoadEntries',
702
+ },
703
+ type: 'error',
704
+ dismissAfter: 8000,
705
+ }),
706
+ );
707
+ return Promise.reject(dispatch(entriesFailed(collection, err)));
708
+ }
709
+ };
710
+ }
711
+
712
+ function escapeHtml(unsafe: string) {
713
+ return unsafe
714
+ .replace(/&/g, '&amp;')
715
+ .replace(/</g, '&lt;')
716
+ .replace(/>/g, '&gt;')
717
+ .replace(/"/g, '&quot;')
718
+ .replace(/'/g, '&#039;');
719
+ }
720
+
721
+ function processValue(unsafe: string) {
722
+ if (['true', 'True', 'TRUE'].includes(unsafe)) {
723
+ return true;
724
+ }
725
+ if (['false', 'False', 'FALSE'].includes(unsafe)) {
726
+ return false;
727
+ }
728
+
729
+ return escapeHtml(unsafe);
730
+ }
731
+
732
+ function getDataFields(fields: EntryFields) {
733
+ return fields.filter(f => !f!.get('meta')).toList();
734
+ }
735
+
736
+ function getMetaFields(fields: EntryFields) {
737
+ return fields.filter(f => f!.get('meta') === true).toList();
738
+ }
739
+
740
+ export function createEmptyDraft(collection: Collection, search: string) {
741
+ return async (dispatch: ThunkDispatch<State, {}, AnyAction>, getState: () => State) => {
742
+ const params = new URLSearchParams(search);
743
+ params.forEach((value, key) => {
744
+ collection = updateFieldByKey(collection, key, field =>
745
+ field.set('default', processValue(value)),
746
+ );
747
+ });
748
+
749
+ const fields = collection.get('fields', List());
750
+
751
+ const dataFields = getDataFields(fields);
752
+ const data = createEmptyDraftData(dataFields);
753
+
754
+ const metaFields = getMetaFields(fields);
755
+ const meta = createEmptyDraftData(metaFields);
756
+
757
+ const state = getState();
758
+ const backend = currentBackend(state.config);
759
+
760
+ if (!collection.has('media_folder')) {
761
+ await waitForMediaLibraryToLoad(dispatch, getState());
762
+ }
763
+
764
+ const i18nFields = createEmptyDraftI18nData(collection, dataFields);
765
+
766
+ let newEntry = createEntry(collection.get('name'), '', '', {
767
+ data,
768
+ i18n: i18nFields,
769
+ mediaFiles: [],
770
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
771
+ meta: meta as any,
772
+ });
773
+ newEntry = await backend.processEntry(state, collection, newEntry);
774
+ dispatch(emptyDraftCreated(newEntry));
775
+ };
776
+ }
777
+
778
+ interface DraftEntryData {
779
+ [name: string]:
780
+ | string
781
+ | null
782
+ | boolean
783
+ | List<unknown>
784
+ | DraftEntryData
785
+ | DraftEntryData[]
786
+ | (string | DraftEntryData | boolean | List<unknown>)[];
787
+ }
788
+
789
+ export function createEmptyDraftData(
790
+ fields: EntryFields,
791
+ skipField: (field: EntryField) => boolean = () => false,
792
+ ) {
793
+ return fields.reduce(
794
+ (
795
+ reduction: DraftEntryData | string | undefined | boolean | List<unknown>,
796
+ value: EntryField | undefined | boolean,
797
+ ) => {
798
+ const acc = reduction as DraftEntryData;
799
+ const item = value as EntryField;
800
+
801
+ if (skipField(item)) {
802
+ return acc;
803
+ }
804
+
805
+ const subfields = item.get('field') || item.get('fields');
806
+ const list = item.get('widget') == 'list';
807
+ const name = item.get('name');
808
+ const defaultValue = item.get('default', null);
809
+
810
+ function isEmptyDefaultValue(val: unknown) {
811
+ return [[{}], {}].some(e => isEqual(val, e));
812
+ }
813
+
814
+ const hasSubfields = List.isList(subfields) || Map.isMap(subfields);
815
+ if (hasSubfields) {
816
+ if (list && List.isList(defaultValue)) {
817
+ acc[name] = defaultValue;
818
+ } else {
819
+ const asList = List.isList(subfields)
820
+ ? (subfields as EntryFields)
821
+ : List([subfields as EntryField]);
822
+
823
+ const subDefaultValue = list
824
+ ? [createEmptyDraftData(asList, skipField)]
825
+ : createEmptyDraftData(asList, skipField);
826
+
827
+ if (!isEmptyDefaultValue(subDefaultValue)) {
828
+ acc[name] = subDefaultValue;
829
+ }
830
+ }
831
+ return acc;
832
+ }
833
+
834
+ if (defaultValue !== null) {
835
+ acc[name] = defaultValue;
836
+ }
837
+
838
+ return acc;
839
+ },
840
+ {} as DraftEntryData,
841
+ );
842
+ }
843
+
844
+ function createEmptyDraftI18nData(collection: Collection, dataFields: EntryFields) {
845
+ if (!hasI18n(collection)) {
846
+ return {};
847
+ }
848
+
849
+ function skipField(field: EntryField) {
850
+ return field.get(I18N) !== I18N_FIELD.DUPLICATE && field.get(I18N) !== I18N_FIELD.TRANSLATE;
851
+ }
852
+
853
+ const i18nData = createEmptyDraftData(dataFields, skipField);
854
+ return duplicateDefaultI18nFields(collection, i18nData);
855
+ }
856
+
857
+ export function getMediaAssets({ entry }: { entry: EntryMap }) {
858
+ const filesArray = entry.get('mediaFiles').toArray();
859
+ const assets = filesArray
860
+ .filter(file => file.get('draft'))
861
+ .map(file =>
862
+ createAssetProxy({
863
+ path: file.get('path'),
864
+ file: file.get('file'),
865
+ url: file.get('url'),
866
+ field: file.get('field'),
867
+ }),
868
+ );
869
+
870
+ return assets;
871
+ }
872
+
873
+ export function getSerializedEntry(collection: Collection, entry: Entry) {
874
+ /**
875
+ * Serialize the values of any fields with registered serializers, and
876
+ * update the entry and entryDraft with the serialized values.
877
+ */
878
+ const fields = selectFields(collection, entry.get('slug'));
879
+
880
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
881
+ function serializeData(data: any) {
882
+ return serializeValues(data, fields);
883
+ }
884
+
885
+ const serializedData = serializeData(entry.get('data'));
886
+ let serializedEntry = entry.set('data', serializedData);
887
+ if (hasI18n(collection)) {
888
+ serializedEntry = serializeI18n(collection, serializedEntry, serializeData);
889
+ }
890
+ return serializedEntry;
891
+ }
892
+
893
+ export function persistEntry(collection: Collection) {
894
+ return async (dispatch: ThunkDispatch<State, {}, AnyAction>, getState: () => State) => {
895
+ const state = getState();
896
+ const entryDraft = state.entryDraft;
897
+ const fieldsErrors = entryDraft.get('fieldsErrors');
898
+ const usedSlugs = selectPublishedSlugs(state, collection.get('name'));
899
+
900
+ // Early return if draft contains validation errors
901
+ if (!fieldsErrors.isEmpty()) {
902
+ const hasPresenceErrors = fieldsErrors.some(errors =>
903
+ errors.some(error => error.type && error.type === ValidationErrorTypes.PRESENCE),
904
+ );
905
+
906
+ if (hasPresenceErrors) {
907
+ dispatch(
908
+ addNotification({
909
+ message: {
910
+ key: 'ui.toast.missingRequiredField',
911
+ },
912
+ type: 'error',
913
+ dismissAfter: 8000,
914
+ }),
915
+ );
916
+ }
917
+
918
+ return Promise.reject();
919
+ }
920
+
921
+ const backend = currentBackend(state.config);
922
+ const entry = entryDraft.get('entry');
923
+ const assetProxies = getMediaAssets({
924
+ entry,
925
+ });
926
+
927
+ const serializedEntry = getSerializedEntry(collection, entry);
928
+ const serializedEntryDraft = entryDraft.set('entry', serializedEntry);
929
+ dispatch(entryPersisting(collection, serializedEntry));
930
+ return backend
931
+ .persistEntry({
932
+ config: state.config,
933
+ collection,
934
+ entryDraft: serializedEntryDraft,
935
+ assetProxies,
936
+ usedSlugs,
937
+ })
938
+ .then(async (newSlug: string) => {
939
+ dispatch(
940
+ addNotification({
941
+ message: {
942
+ key: 'ui.toast.entrySaved',
943
+ },
944
+ type: 'success',
945
+ dismissAfter: 4000,
946
+ }),
947
+ );
948
+
949
+ // re-load media library if entry had media files
950
+ if (assetProxies.length > 0) {
951
+ await dispatch(loadMedia());
952
+ }
953
+ dispatch(entryPersisted(collection, serializedEntry, newSlug));
954
+ if (collection.has('nested')) {
955
+ await dispatch(loadEntries(collection));
956
+ }
957
+ if (entry.get('slug') !== newSlug) {
958
+ await dispatch(loadEntry(collection, newSlug));
959
+ navigateToEntry(collection.get('name'), newSlug);
960
+ }
961
+ })
962
+ .catch((error: Error) => {
963
+ console.error(error);
964
+ dispatch(
965
+ addNotification({
966
+ message: {
967
+ details: error,
968
+ key: 'ui.toast.onFailToPersist',
969
+ },
970
+ type: 'error',
971
+ dismissAfter: 8000,
972
+ }),
973
+ );
974
+ return Promise.reject(dispatch(entryPersistFail(collection, serializedEntry, error)));
975
+ });
976
+ };
977
+ }
978
+
979
+ export function deleteEntry(collection: Collection, slug: string) {
980
+ return (dispatch: ThunkDispatch<State, {}, AnyAction>, getState: () => State) => {
981
+ const state = getState();
982
+ const backend = currentBackend(state.config);
983
+
984
+ dispatch(entryDeleting(collection, slug));
985
+ return backend
986
+ .deleteEntry(state, collection, slug)
987
+ .then(() => {
988
+ return dispatch(entryDeleted(collection, slug));
989
+ })
990
+ .catch((error: Error) => {
991
+ dispatch(
992
+ addNotification({
993
+ message: {
994
+ details: error,
995
+ key: 'ui.toast.onFailToDelete',
996
+ },
997
+ type: 'error',
998
+ dismissAfter: 8000,
999
+ }),
1000
+ );
1001
+ console.error(error);
1002
+ return Promise.reject(dispatch(entryDeleteFail(collection, slug, error)));
1003
+ });
1004
+ };
1005
+ }
1006
+
1007
+ function getPathError(
1008
+ path: string | undefined,
1009
+ key: string,
1010
+ t: (key: string, args: Record<string, unknown>) => string,
1011
+ ) {
1012
+ return {
1013
+ error: {
1014
+ type: ValidationErrorTypes.CUSTOM,
1015
+ message: t(`editor.editorControlPane.widget.${key}`, {
1016
+ path,
1017
+ }),
1018
+ },
1019
+ };
1020
+ }
1021
+
1022
+ export function validateMetaField(
1023
+ state: State,
1024
+ collection: Collection,
1025
+ field: EntryField,
1026
+ value: string | undefined,
1027
+ t: (key: string, args: Record<string, unknown>) => string,
1028
+ ) {
1029
+ if (field.get('meta') && field.get('name') === 'path') {
1030
+ if (!value) {
1031
+ return getPathError(value, 'invalidPath', t);
1032
+ }
1033
+ const sanitizedPath = (value as string)
1034
+ .split('/')
1035
+ .map(getProcessSegment(state.config.slug, undefined, true))
1036
+ .join('/');
1037
+
1038
+ if (value !== sanitizedPath) {
1039
+ return getPathError(value, 'invalidPath', t);
1040
+ }
1041
+
1042
+ const customPath = selectCustomPath(collection, fromJS({ entry: { meta: { path: value } } }));
1043
+ const existingEntry = customPath
1044
+ ? selectEntryByPath(state.entries, collection.get('name'), customPath)
1045
+ : undefined;
1046
+
1047
+ const existingEntryPath = existingEntry?.get('path');
1048
+ const draftPath = state.entryDraft?.getIn(['entry', 'path']);
1049
+
1050
+ if (existingEntryPath && existingEntryPath !== draftPath) {
1051
+ return getPathError(value, 'pathExists', t);
1052
+ }
1053
+ }
1054
+ return { error: false };
1055
+ }