decap-cms-core 3.6.3 → 3.7.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 (137) hide show
  1. package/dist/decap-cms-core.js +25 -25
  2. package/dist/decap-cms-core.js.LICENSE.txt +14 -8
  3. package/dist/decap-cms-core.js.map +1 -1
  4. package/dist/esm/actions/config.js +57 -49
  5. package/dist/esm/actions/editorialWorkflow.js +4 -4
  6. package/dist/esm/actions/entries.js +8 -14
  7. package/dist/esm/actions/mediaLibrary.js +6 -11
  8. package/dist/esm/actions/search.js +2 -2
  9. package/dist/esm/actions/status.js +2 -8
  10. package/dist/esm/backend.js +70 -79
  11. package/dist/esm/bootstrap.js +3 -2
  12. package/dist/esm/components/App/App.js +28 -34
  13. package/dist/esm/components/App/Header.js +32 -39
  14. package/dist/esm/components/Collection/Collection.js +45 -48
  15. package/dist/esm/components/Collection/CollectionSearch.js +76 -81
  16. package/dist/esm/components/Collection/CollectionTop.js +1 -2
  17. package/dist/esm/components/Collection/Entries/Entries.js +2 -4
  18. package/dist/esm/components/Collection/Entries/EntriesCollection.js +25 -29
  19. package/dist/esm/components/Collection/Entries/EntriesSearch.js +34 -38
  20. package/dist/esm/components/Collection/Entries/EntryCard.js +8 -13
  21. package/dist/esm/components/Collection/Entries/EntryListing.js +72 -76
  22. package/dist/esm/components/Collection/FilterControl.js +1 -1
  23. package/dist/esm/components/Collection/GroupControl.js +1 -1
  24. package/dist/esm/components/Collection/NestedCollection.js +50 -53
  25. package/dist/esm/components/Collection/Sidebar.js +35 -38
  26. package/dist/esm/components/Collection/SortControl.js +3 -3
  27. package/dist/esm/components/Collection/ViewStyleControl.js +1 -2
  28. package/dist/esm/components/Editor/Editor.js +197 -201
  29. package/dist/esm/components/Editor/EditorControlPane/EditorControl.js +79 -87
  30. package/dist/esm/components/Editor/EditorControlPane/EditorControlPane.js +75 -86
  31. package/dist/esm/components/Editor/EditorControlPane/Widget.js +226 -228
  32. package/dist/esm/components/Editor/EditorInterface.js +69 -80
  33. package/dist/esm/components/Editor/EditorPreviewPane/EditorPreview.js +1 -2
  34. package/dist/esm/components/Editor/EditorPreviewPane/EditorPreviewContent.js +20 -28
  35. package/dist/esm/components/Editor/EditorPreviewPane/EditorPreviewPane.js +163 -161
  36. package/dist/esm/components/Editor/EditorPreviewPane/PreviewHOC.js +4 -8
  37. package/dist/esm/components/Editor/EditorToolbar.js +335 -347
  38. package/dist/esm/components/Editor/withWorkflow.js +5 -6
  39. package/dist/esm/components/MediaLibrary/MediaLibrary.js +304 -294
  40. package/dist/esm/components/MediaLibrary/MediaLibraryButtons.js +40 -46
  41. package/dist/esm/components/MediaLibrary/MediaLibraryCard.js +1 -2
  42. package/dist/esm/components/MediaLibrary/MediaLibraryCardGrid.js +8 -13
  43. package/dist/esm/components/MediaLibrary/MediaLibraryModal.js +3 -3
  44. package/dist/esm/components/MediaLibrary/MediaLibrarySearch.js +1 -2
  45. package/dist/esm/components/MediaLibrary/MediaLibraryTop.js +3 -6
  46. package/dist/esm/components/UI/DragDrop.js +15 -23
  47. package/dist/esm/components/UI/ErrorBoundary.js +23 -25
  48. package/dist/esm/components/UI/Modal.js +10 -12
  49. package/dist/esm/components/UI/Notifications.js +4 -8
  50. package/dist/esm/components/UI/SettingsDropdown.js +4 -8
  51. package/dist/esm/components/Workflow/Workflow.js +19 -20
  52. package/dist/esm/components/Workflow/WorkflowCard.js +2 -4
  53. package/dist/esm/components/Workflow/WorkflowList.js +105 -113
  54. package/dist/esm/constants/configSchema.js +18 -16
  55. package/dist/esm/formats/formats.js +11 -12
  56. package/dist/esm/formats/frontmatter.js +17 -21
  57. package/dist/esm/formats/toml.js +2 -2
  58. package/dist/esm/formats/yaml.js +2 -6
  59. package/dist/esm/index.js +3 -7
  60. package/dist/esm/integrations/providers/algolia/implementation.js +12 -14
  61. package/dist/esm/integrations/providers/assetStore/implementation.js +10 -12
  62. package/dist/esm/lib/formatters.js +13 -17
  63. package/dist/esm/lib/i18n.js +35 -33
  64. package/dist/esm/lib/phrases.js +2 -2
  65. package/dist/esm/lib/polyfill.js +8 -0
  66. package/dist/esm/lib/registry.js +35 -35
  67. package/dist/esm/lib/serializeEntryValues.js +3 -3
  68. package/dist/esm/lib/stega.js +142 -0
  69. package/dist/esm/lib/urlHelper.js +16 -18
  70. package/dist/esm/mediaLibrary.js +3 -4
  71. package/dist/esm/reducers/collections.js +26 -42
  72. package/dist/esm/reducers/combinedReducer.js +3 -6
  73. package/dist/esm/reducers/config.js +3 -7
  74. package/dist/esm/reducers/editorialWorkflow.js +5 -9
  75. package/dist/esm/reducers/entries.js +33 -35
  76. package/dist/esm/reducers/entryDraft.js +2 -2
  77. package/dist/esm/reducers/integrations.js +8 -14
  78. package/dist/esm/reducers/mediaLibrary.js +18 -20
  79. package/dist/esm/reducers/notifications.js +4 -8
  80. package/dist/esm/types/immutable.js +7 -1
  81. package/dist/esm/valueObjects/AssetProxy.js +1 -9
  82. package/dist/esm/valueObjects/EditorComponent.js +18 -25
  83. package/dist/esm/valueObjects/Entry.js +2 -2
  84. package/index.d.ts +2 -0
  85. package/package.json +14 -11
  86. package/src/actions/__tests__/config.spec.js +3 -3
  87. package/src/actions/config.ts +3 -1
  88. package/src/actions/editorialWorkflow.ts +1 -1
  89. package/src/actions/entries.ts +1 -1
  90. package/src/actions/search.ts +1 -1
  91. package/src/backend.ts +8 -1
  92. package/src/bootstrap.js +1 -0
  93. package/src/components/App/App.js +5 -0
  94. package/src/components/App/Header.js +3 -0
  95. package/src/components/Collection/Collection.js +5 -0
  96. package/src/components/Collection/CollectionSearch.js +5 -0
  97. package/src/components/Collection/Entries/EntriesCollection.js +4 -1
  98. package/src/components/Collection/Entries/EntriesSearch.js +4 -1
  99. package/src/components/Collection/Entries/EntryListing.js +5 -0
  100. package/src/components/Collection/Entries/__tests__/__snapshots__/EntriesCollection.spec.js.snap +0 -4
  101. package/src/components/Collection/NestedCollection.js +6 -1
  102. package/src/components/Collection/Sidebar.js +5 -0
  103. package/src/components/Editor/Editor.js +4 -1
  104. package/src/components/Editor/EditorControlPane/EditorControl.js +7 -1
  105. package/src/components/Editor/EditorControlPane/Widget.js +5 -0
  106. package/src/components/Editor/EditorPreviewPane/EditorPreviewPane.js +1 -1
  107. package/src/components/Editor/EditorToolbar.js +3 -0
  108. package/src/components/Editor/__tests__/Editor.spec.js +3 -4
  109. package/src/components/Editor/__tests__/__snapshots__/Editor.spec.js.snap +5 -5
  110. package/src/components/Editor/__tests__/__snapshots__/EditorToolbar.spec.js.snap +708 -393
  111. package/src/components/MediaLibrary/MediaLibrary.js +5 -1
  112. package/src/components/MediaLibrary/MediaLibraryModal.js +1 -1
  113. package/src/components/UI/ErrorBoundary.js +6 -1
  114. package/src/components/UI/Modal.js +3 -0
  115. package/src/components/Workflow/Workflow.js +3 -0
  116. package/src/components/Workflow/WorkflowList.js +5 -0
  117. package/src/constants/__tests__/configSchema.spec.js +1 -1
  118. package/src/formats/formats.ts +1 -1
  119. package/src/formats/toml.ts +2 -2
  120. package/src/integrations/providers/algolia/implementation.js +2 -2
  121. package/src/integrations/providers/assetStore/implementation.js +2 -1
  122. package/src/lib/formatters.ts +4 -1
  123. package/src/lib/i18n.ts +3 -1
  124. package/src/lib/phrases.js +1 -1
  125. package/src/lib/polyfill.js +9 -0
  126. package/src/lib/serializeEntryValues.js +1 -1
  127. package/src/lib/stega.ts +145 -0
  128. package/src/lib/urlHelper.ts +4 -1
  129. package/src/mediaLibrary.ts +1 -1
  130. package/src/reducers/collections.ts +2 -1
  131. package/src/reducers/editorialWorkflow.ts +1 -1
  132. package/src/reducers/entries.ts +6 -1
  133. package/src/reducers/entryDraft.js +1 -1
  134. package/src/types/immutable.ts +10 -0
  135. package/src/types/redux.ts +2 -0
  136. package/src/valueObjects/EditorComponent.js +1 -1
  137. package/src/valueObjects/Entry.ts +1 -1
@@ -1,8 +1,3 @@
1
- function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
2
- function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
3
- 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; }
4
- function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : String(i); }
5
- function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
6
1
  import React from 'react';
7
2
  import { connect } from 'react-redux';
8
3
  import { EDITORIAL_WORKFLOW } from '../../constants/publishModes';
@@ -46,7 +41,11 @@ function mergeProps(stateProps, dispatchProps, ownProps) {
46
41
  // Overwrite persistEntry to persistUnpublishedEntry
47
42
  returnObj.persistEntry = collection => dispatch(persistUnpublishedEntry(collection, unpublishedEntry));
48
43
  }
49
- return _objectSpread(_objectSpread(_objectSpread({}, ownProps), stateProps), returnObj);
44
+ return {
45
+ ...ownProps,
46
+ ...stateProps,
47
+ ...returnObj
48
+ };
50
49
  }
51
50
  export default function withWorkflow(Editor) {
52
51
  return connect(mapStateToProps, null, mergeProps)(class WorkflowEditor extends React.Component {
@@ -1,14 +1,9 @@
1
- import _map from "lodash/map";
2
- import _orderBy from "lodash/orderBy";
3
- function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
4
- function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
5
- 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; }
6
- function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : String(i); }
7
- function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
8
1
  import React from 'react';
9
2
  import PropTypes from 'prop-types';
10
3
  import ImmutablePropTypes from 'react-immutable-proptypes';
11
4
  import { connect } from 'react-redux';
5
+ import orderBy from 'lodash/orderBy';
6
+ import map from 'lodash/map';
12
7
  import { translate } from 'react-polyglot';
13
8
  import fuzzy from 'fuzzy';
14
9
  import { fileExtension } from 'decap-cms-lib-util';
@@ -24,266 +19,47 @@ import { jsx as ___EmotionJSX } from "@emotion/react";
24
19
  const IMAGE_EXTENSIONS_VIEWABLE = ['jpg', 'jpeg', 'webp', 'gif', 'png', 'bmp', 'tiff', 'svg', 'avif'];
25
20
  const IMAGE_EXTENSIONS = [...IMAGE_EXTENSIONS_VIEWABLE];
26
21
  class MediaLibrary extends React.Component {
27
- constructor(...args) {
28
- super(...args);
29
- /**
30
- * The currently selected file and query are tracked in component state as
31
- * they do not impact the rest of the application.
32
- */
33
- _defineProperty(this, "state", {
34
- selectedFile: {},
35
- query: '',
36
- isPersisted: false
37
- });
38
- _defineProperty(this, "loadDisplayURL", file => {
39
- const {
40
- loadMediaDisplayURL
41
- } = this.props;
42
- loadMediaDisplayURL(file);
43
- });
44
- /**
45
- * Filter an array of file data to include only images.
46
- */
47
- _defineProperty(this, "filterImages", files => {
48
- return files.filter(file => {
49
- const ext = fileExtension(file.name).toLowerCase();
50
- return IMAGE_EXTENSIONS.includes(ext);
51
- });
52
- });
53
- /**
54
- * Transform file data for table display.
55
- */
56
- _defineProperty(this, "toTableData", files => {
57
- const tableData = files && files.map(({
58
- key,
59
- name,
60
- id,
61
- size,
62
- path,
63
- queryOrder,
64
- displayURL,
65
- draft
66
- }) => {
67
- const ext = fileExtension(name).toLowerCase();
68
- return {
69
- key,
70
- id,
71
- name,
72
- path,
73
- type: ext.toUpperCase(),
74
- size,
75
- queryOrder,
76
- displayURL,
77
- draft,
78
- isImage: IMAGE_EXTENSIONS.includes(ext),
79
- isViewableImage: IMAGE_EXTENSIONS_VIEWABLE.includes(ext)
80
- };
81
- });
22
+ static propTypes = {
23
+ isVisible: PropTypes.bool,
24
+ loadMediaDisplayURL: PropTypes.func,
25
+ displayURLs: ImmutablePropTypes.map,
26
+ canInsert: PropTypes.bool,
27
+ files: PropTypes.arrayOf(PropTypes.shape(fileShape)).isRequired,
28
+ dynamicSearch: PropTypes.bool,
29
+ dynamicSearchActive: PropTypes.bool,
30
+ forImage: PropTypes.bool,
31
+ isLoading: PropTypes.bool,
32
+ isPersisting: PropTypes.bool,
33
+ isDeleting: PropTypes.bool,
34
+ hasNextPage: PropTypes.bool,
35
+ isPaginating: PropTypes.bool,
36
+ privateUpload: PropTypes.bool,
37
+ config: ImmutablePropTypes.map,
38
+ loadMedia: PropTypes.func.isRequired,
39
+ dynamicSearchQuery: PropTypes.string,
40
+ page: PropTypes.number,
41
+ persistMedia: PropTypes.func.isRequired,
42
+ deleteMedia: PropTypes.func.isRequired,
43
+ insertMedia: PropTypes.func.isRequired,
44
+ closeMediaLibrary: PropTypes.func.isRequired,
45
+ t: PropTypes.func.isRequired
46
+ };
47
+ static defaultProps = {
48
+ files: []
49
+ };
82
50
 
83
- /**
84
- * Get the sort order for use with `lodash.orderBy`, and always add the
85
- * `queryOrder` sort as the lowest priority sort order.
86
- */
87
- const {
88
- sortFields
89
- } = this.state;
90
- const fieldNames = _map(sortFields, 'fieldName').concat('queryOrder');
91
- const directions = _map(sortFields, 'direction').concat('asc');
92
- return _orderBy(tableData, fieldNames, directions);
93
- });
94
- _defineProperty(this, "handleClose", () => {
95
- this.props.closeMediaLibrary();
96
- });
97
- /**
98
- * Toggle asset selection on click.
99
- */
100
- _defineProperty(this, "handleAssetClick", asset => {
101
- const selectedFile = this.state.selectedFile.key === asset.key ? {} : asset;
102
- this.setState({
103
- selectedFile
104
- });
105
- });
106
- /**
107
- * Upload a file.
108
- */
109
- _defineProperty(this, "handlePersist", async event => {
110
- /**
111
- * Stop the browser from automatically handling the file input click, and
112
- * get the file for upload, and retain the synthetic event for access after
113
- * the asynchronous persist operation.
114
- */
115
- event.persist();
116
- event.stopPropagation();
117
- event.preventDefault();
118
- const {
119
- persistMedia,
120
- privateUpload,
121
- config,
122
- t,
123
- field
124
- } = this.props;
125
- const {
126
- files: fileList
127
- } = event.dataTransfer || event.target;
128
- const files = [...fileList];
129
- const file = files[0];
130
- const maxFileSize = config.get('max_file_size');
131
- if (maxFileSize && file.size > maxFileSize) {
132
- window.alert(t('mediaLibrary.mediaLibrary.fileTooLarge', {
133
- size: Math.floor(maxFileSize / 1000)
134
- }));
135
- } else {
136
- await persistMedia(file, {
137
- privateUpload,
138
- field
139
- });
140
- this.setState({
141
- isPersisted: true
142
- });
143
- this.scrollToTop();
144
- }
145
- event.target.value = null;
146
- });
147
- /**
148
- * Stores the public path of the file in the application store, where the
149
- * editor field that launched the media library can retrieve it.
150
- */
151
- _defineProperty(this, "handleInsert", () => {
152
- const {
153
- selectedFile
154
- } = this.state;
155
- const {
156
- path
157
- } = selectedFile;
158
- const {
159
- insertMedia,
160
- field
161
- } = this.props;
162
- insertMedia(path, field);
163
- this.handleClose();
164
- });
165
- /**
166
- * Removes the selected file from the backend.
167
- */
168
- _defineProperty(this, "handleDelete", () => {
169
- const {
170
- selectedFile
171
- } = this.state;
172
- const {
173
- files,
174
- deleteMedia,
175
- privateUpload,
176
- t
177
- } = this.props;
178
- if (!window.confirm(t('mediaLibrary.mediaLibrary.onDelete'))) {
179
- return;
180
- }
181
- const file = files.find(file => selectedFile.key === file.key);
182
- deleteMedia(file, {
183
- privateUpload
184
- }).then(() => {
185
- this.setState({
186
- selectedFile: {}
187
- });
188
- });
189
- });
190
- /**
191
- * Downloads the selected file.
192
- */
193
- _defineProperty(this, "handleDownload", () => {
194
- const {
195
- selectedFile
196
- } = this.state;
197
- const {
198
- displayURLs
199
- } = this.props;
200
- const url = displayURLs.getIn([selectedFile.id, 'url']) || selectedFile.url;
201
- if (!url) {
202
- return;
203
- }
204
- const filename = selectedFile.name;
205
- const element = document.createElement('a');
206
- element.setAttribute('href', url);
207
- element.setAttribute('download', filename);
208
- element.style.display = 'none';
209
- document.body.appendChild(element);
210
- element.click();
211
- document.body.removeChild(element);
212
- this.setState({
213
- selectedFile: {}
214
- });
215
- });
216
- /**
217
- *
218
- */
219
- _defineProperty(this, "handleLoadMore", () => {
220
- const {
221
- loadMedia,
222
- dynamicSearchQuery,
223
- page,
224
- privateUpload
225
- } = this.props;
226
- loadMedia({
227
- query: dynamicSearchQuery,
228
- page: page + 1,
229
- privateUpload
230
- });
231
- });
232
- /**
233
- * Executes media library search for implementations that support dynamic
234
- * search via request. For these implementations, the Enter key must be
235
- * pressed to execute search. If assets are being stored directly through
236
- * the GitHub backend, search is in-memory and occurs as the query is typed,
237
- * so this handler has no impact.
238
- */
239
- _defineProperty(this, "handleSearchKeyDown", async event => {
240
- const {
241
- dynamicSearch,
242
- loadMedia,
243
- privateUpload
244
- } = this.props;
245
- if (event.key === 'Enter' && dynamicSearch) {
246
- await loadMedia({
247
- query: this.state.query,
248
- privateUpload
249
- });
250
- this.scrollToTop();
251
- }
252
- });
253
- _defineProperty(this, "scrollToTop", () => {
254
- this.scrollContainerRef.scrollTop = 0;
255
- });
256
- /**
257
- * Updates query state as the user types in the search field.
258
- */
259
- _defineProperty(this, "handleSearchChange", event => {
260
- this.setState({
261
- query: event.target.value
262
- });
263
- });
264
- /**
265
- * Filters files that do not match the query. Not used for dynamic search.
266
- */
267
- _defineProperty(this, "queryFilter", (query, files) => {
268
- /**
269
- * Because file names don't have spaces, typing a space eliminates all
270
- * potential matches, so we strip them all out internally before running the
271
- * query.
272
- */
273
- const strippedQuery = query.replace(/ /g, '');
274
- const matches = fuzzy.filter(strippedQuery, files, {
275
- extract: file => file.name
276
- });
277
- const matchFiles = matches.map((match, queryIndex) => {
278
- const file = files[match.index];
279
- return _objectSpread(_objectSpread({}, file), {}, {
280
- queryIndex
281
- });
282
- });
283
- return matchFiles;
284
- });
285
- }
51
+ /**
52
+ * The currently selected file and query are tracked in component state as
53
+ * they do not impact the rest of the application.
54
+ */
55
+ state = {
56
+ selectedFile: {},
57
+ query: '',
58
+ isPersisted: false
59
+ };
286
60
  componentDidMount() {
61
+ // Manually validate PropTypes - React 19 breaking change
62
+ PropTypes.checkPropTypes(MediaLibrary.propTypes, this.props, 'prop', 'MediaLibrary');
287
63
  this.props.loadMedia();
288
64
  }
289
65
  UNSAFE_componentWillReceiveProps(nextProps) {
@@ -320,6 +96,266 @@ class MediaLibrary extends React.Component {
320
96
  });
321
97
  }
322
98
  }
99
+ loadDisplayURL = file => {
100
+ const {
101
+ loadMediaDisplayURL
102
+ } = this.props;
103
+ loadMediaDisplayURL(file);
104
+ };
105
+
106
+ /**
107
+ * Filter an array of file data to include only images.
108
+ */
109
+ filterImages = files => {
110
+ return files.filter(file => {
111
+ const ext = fileExtension(file.name).toLowerCase();
112
+ return IMAGE_EXTENSIONS.includes(ext);
113
+ });
114
+ };
115
+
116
+ /**
117
+ * Transform file data for table display.
118
+ */
119
+ toTableData = files => {
120
+ const tableData = files && files.map(({
121
+ key,
122
+ name,
123
+ id,
124
+ size,
125
+ path,
126
+ queryOrder,
127
+ displayURL,
128
+ draft
129
+ }) => {
130
+ const ext = fileExtension(name).toLowerCase();
131
+ return {
132
+ key,
133
+ id,
134
+ name,
135
+ path,
136
+ type: ext.toUpperCase(),
137
+ size,
138
+ queryOrder,
139
+ displayURL,
140
+ draft,
141
+ isImage: IMAGE_EXTENSIONS.includes(ext),
142
+ isViewableImage: IMAGE_EXTENSIONS_VIEWABLE.includes(ext)
143
+ };
144
+ });
145
+
146
+ /**
147
+ * Get the sort order for use with `lodash.orderBy`, and always add the
148
+ * `queryOrder` sort as the lowest priority sort order.
149
+ */
150
+ const {
151
+ sortFields
152
+ } = this.state;
153
+ const fieldNames = map(sortFields, 'fieldName').concat('queryOrder');
154
+ const directions = map(sortFields, 'direction').concat('asc');
155
+ return orderBy(tableData, fieldNames, directions);
156
+ };
157
+ handleClose = () => {
158
+ this.props.closeMediaLibrary();
159
+ };
160
+
161
+ /**
162
+ * Toggle asset selection on click.
163
+ */
164
+ handleAssetClick = asset => {
165
+ const selectedFile = this.state.selectedFile.key === asset.key ? {} : asset;
166
+ this.setState({
167
+ selectedFile
168
+ });
169
+ };
170
+
171
+ /**
172
+ * Upload a file.
173
+ */
174
+ handlePersist = async event => {
175
+ /**
176
+ * Stop the browser from automatically handling the file input click, and
177
+ * get the file for upload, and retain the synthetic event for access after
178
+ * the asynchronous persist operation.
179
+ */
180
+ event.persist();
181
+ event.stopPropagation();
182
+ event.preventDefault();
183
+ const {
184
+ persistMedia,
185
+ privateUpload,
186
+ config,
187
+ t,
188
+ field
189
+ } = this.props;
190
+ const {
191
+ files: fileList
192
+ } = event.dataTransfer || event.target;
193
+ const files = [...fileList];
194
+ const file = files[0];
195
+ const maxFileSize = config.get('max_file_size');
196
+ if (maxFileSize && file.size > maxFileSize) {
197
+ window.alert(t('mediaLibrary.mediaLibrary.fileTooLarge', {
198
+ size: Math.floor(maxFileSize / 1000)
199
+ }));
200
+ } else {
201
+ await persistMedia(file, {
202
+ privateUpload,
203
+ field
204
+ });
205
+ this.setState({
206
+ isPersisted: true
207
+ });
208
+ this.scrollToTop();
209
+ }
210
+ event.target.value = null;
211
+ };
212
+
213
+ /**
214
+ * Stores the public path of the file in the application store, where the
215
+ * editor field that launched the media library can retrieve it.
216
+ */
217
+ handleInsert = () => {
218
+ const {
219
+ selectedFile
220
+ } = this.state;
221
+ const {
222
+ path
223
+ } = selectedFile;
224
+ const {
225
+ insertMedia,
226
+ field
227
+ } = this.props;
228
+ insertMedia(path, field);
229
+ this.handleClose();
230
+ };
231
+
232
+ /**
233
+ * Removes the selected file from the backend.
234
+ */
235
+ handleDelete = () => {
236
+ const {
237
+ selectedFile
238
+ } = this.state;
239
+ const {
240
+ files,
241
+ deleteMedia,
242
+ privateUpload,
243
+ t
244
+ } = this.props;
245
+ if (!window.confirm(t('mediaLibrary.mediaLibrary.onDelete'))) {
246
+ return;
247
+ }
248
+ const file = files.find(file => selectedFile.key === file.key);
249
+ deleteMedia(file, {
250
+ privateUpload
251
+ }).then(() => {
252
+ this.setState({
253
+ selectedFile: {}
254
+ });
255
+ });
256
+ };
257
+
258
+ /**
259
+ * Downloads the selected file.
260
+ */
261
+ handleDownload = () => {
262
+ const {
263
+ selectedFile
264
+ } = this.state;
265
+ const {
266
+ displayURLs
267
+ } = this.props;
268
+ const url = displayURLs.getIn([selectedFile.id, 'url']) || selectedFile.url;
269
+ if (!url) {
270
+ return;
271
+ }
272
+ const filename = selectedFile.name;
273
+ const element = document.createElement('a');
274
+ element.setAttribute('href', url);
275
+ element.setAttribute('download', filename);
276
+ element.style.display = 'none';
277
+ document.body.appendChild(element);
278
+ element.click();
279
+ document.body.removeChild(element);
280
+ this.setState({
281
+ selectedFile: {}
282
+ });
283
+ };
284
+
285
+ /**
286
+ *
287
+ */
288
+
289
+ handleLoadMore = () => {
290
+ const {
291
+ loadMedia,
292
+ dynamicSearchQuery,
293
+ page,
294
+ privateUpload
295
+ } = this.props;
296
+ loadMedia({
297
+ query: dynamicSearchQuery,
298
+ page: page + 1,
299
+ privateUpload
300
+ });
301
+ };
302
+
303
+ /**
304
+ * Executes media library search for implementations that support dynamic
305
+ * search via request. For these implementations, the Enter key must be
306
+ * pressed to execute search. If assets are being stored directly through
307
+ * the GitHub backend, search is in-memory and occurs as the query is typed,
308
+ * so this handler has no impact.
309
+ */
310
+ handleSearchKeyDown = async event => {
311
+ const {
312
+ dynamicSearch,
313
+ loadMedia,
314
+ privateUpload
315
+ } = this.props;
316
+ if (event.key === 'Enter' && dynamicSearch) {
317
+ await loadMedia({
318
+ query: this.state.query,
319
+ privateUpload
320
+ });
321
+ this.scrollToTop();
322
+ }
323
+ };
324
+ scrollToTop = () => {
325
+ this.scrollContainerRef.scrollTop = 0;
326
+ };
327
+
328
+ /**
329
+ * Updates query state as the user types in the search field.
330
+ */
331
+ handleSearchChange = event => {
332
+ this.setState({
333
+ query: event.target.value
334
+ });
335
+ };
336
+
337
+ /**
338
+ * Filters files that do not match the query. Not used for dynamic search.
339
+ */
340
+ queryFilter = (query, files) => {
341
+ /**
342
+ * Because file names don't have spaces, typing a space eliminates all
343
+ * potential matches, so we strip them all out internally before running the
344
+ * query.
345
+ */
346
+ const strippedQuery = query.replace(/ /g, '');
347
+ const matches = fuzzy.filter(strippedQuery, files, {
348
+ extract: file => file.name
349
+ });
350
+ const matchFiles = matches.map((match, queryIndex) => {
351
+ const file = files[match.index];
352
+ return {
353
+ ...file,
354
+ queryIndex
355
+ };
356
+ });
357
+ return matchFiles;
358
+ };
323
359
  render() {
324
360
  const {
325
361
  isVisible,
@@ -371,34 +407,6 @@ class MediaLibrary extends React.Component {
371
407
  });
372
408
  }
373
409
  }
374
- _defineProperty(MediaLibrary, "propTypes", {
375
- isVisible: PropTypes.bool,
376
- loadMediaDisplayURL: PropTypes.func,
377
- displayURLs: ImmutablePropTypes.map,
378
- canInsert: PropTypes.bool,
379
- files: PropTypes.arrayOf(PropTypes.shape(fileShape)).isRequired,
380
- dynamicSearch: PropTypes.bool,
381
- dynamicSearchActive: PropTypes.bool,
382
- forImage: PropTypes.bool,
383
- isLoading: PropTypes.bool,
384
- isPersisting: PropTypes.bool,
385
- isDeleting: PropTypes.bool,
386
- hasNextPage: PropTypes.bool,
387
- isPaginating: PropTypes.bool,
388
- privateUpload: PropTypes.bool,
389
- config: ImmutablePropTypes.map,
390
- loadMedia: PropTypes.func.isRequired,
391
- dynamicSearchQuery: PropTypes.string,
392
- page: PropTypes.number,
393
- persistMedia: PropTypes.func.isRequired,
394
- deleteMedia: PropTypes.func.isRequired,
395
- insertMedia: PropTypes.func.isRequired,
396
- closeMediaLibrary: PropTypes.func.isRequired,
397
- t: PropTypes.func.isRequired
398
- });
399
- _defineProperty(MediaLibrary, "defaultProps", {
400
- files: []
401
- });
402
410
  function mapStateToProps(state) {
403
411
  const {
404
412
  mediaLibrary
@@ -423,7 +431,9 @@ function mapStateToProps(state) {
423
431
  isPaginating: mediaLibrary.get('isPaginating'),
424
432
  field
425
433
  };
426
- return _objectSpread({}, mediaLibraryProps);
434
+ return {
435
+ ...mediaLibraryProps
436
+ };
427
437
  }
428
438
  const mapDispatchToProps = {
429
439
  loadMedia: loadMediaAction,