@plone/volto 18.0.0-alpha.30 → 18.0.0-alpha.32

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 (65) hide show
  1. package/.eslintrc +2 -1
  2. package/CHANGELOG.md +47 -0
  3. package/cypress/support/commands.js +16 -6
  4. package/locales/ca/LC_MESSAGES/volto.po +10 -5
  5. package/locales/ca.json +1 -1
  6. package/locales/de/LC_MESSAGES/volto.po +10 -5
  7. package/locales/de.json +1 -1
  8. package/locales/en/LC_MESSAGES/volto.po +10 -5
  9. package/locales/en.json +1 -1
  10. package/locales/es/LC_MESSAGES/volto.po +10 -5
  11. package/locales/es.json +1 -1
  12. package/locales/eu/LC_MESSAGES/volto.po +10 -5
  13. package/locales/eu.json +1 -1
  14. package/locales/fi/LC_MESSAGES/volto.po +10 -5
  15. package/locales/fi.json +1 -1
  16. package/locales/fr/LC_MESSAGES/volto.po +10 -5
  17. package/locales/fr.json +1 -1
  18. package/locales/hi/LC_MESSAGES/volto.po +4977 -0
  19. package/locales/hi.json +1 -0
  20. package/locales/it/LC_MESSAGES/volto.po +10 -5
  21. package/locales/it.json +1 -1
  22. package/locales/ja/LC_MESSAGES/volto.po +10 -5
  23. package/locales/ja.json +1 -1
  24. package/locales/nl/LC_MESSAGES/volto.po +10 -5
  25. package/locales/nl.json +1 -1
  26. package/locales/pt/LC_MESSAGES/volto.po +10 -5
  27. package/locales/pt.json +1 -1
  28. package/locales/pt_BR/LC_MESSAGES/volto.po +10 -5
  29. package/locales/pt_BR.json +1 -1
  30. package/locales/ro/LC_MESSAGES/volto.po +10 -5
  31. package/locales/ro.json +1 -1
  32. package/locales/volto.pot +11 -6
  33. package/locales/zh_CN/LC_MESSAGES/volto.po +10 -5
  34. package/locales/zh_CN.json +1 -1
  35. package/package.json +4 -4
  36. package/razzle.config.js +2 -0
  37. package/src/actions/content/content.js +14 -0
  38. package/src/actions/content/content.test.js +14 -0
  39. package/src/actions/index.js +1 -0
  40. package/src/components/manage/Blocks/Block/EditBlockWrapper.jsx +2 -0
  41. package/src/components/manage/Contents/ContentsUploadModal.jsx +32 -9
  42. package/src/components/manage/Form/Form.jsx +1 -1
  43. package/src/components/theme/View/View.jsx +4 -2
  44. package/src/components/theme/View/View.test.jsx +1 -0
  45. package/src/config/ControlPanels.js +7 -0
  46. package/src/config/config.test.js +232 -0
  47. package/src/constants/ActionTypes.js +1 -0
  48. package/src/constants/Languages.js +1 -0
  49. package/src/express-middleware/files.js +1 -0
  50. package/src/express-middleware/images.js +1 -0
  51. package/src/helpers/Api/Api.js +12 -0
  52. package/src/helpers/Api/Api.plone.rest.test.js +1 -0
  53. package/src/helpers/Api/Api.test.js +1 -0
  54. package/src/helpers/Blocks/Blocks.js +1 -1
  55. package/src/middleware/api.js +9 -2
  56. package/src/reducers/content/content.js +7 -0
  57. package/src/reducers/content/content.test.js +13 -0
  58. package/theme/themes/pastanaga/extras/blocks.less +10 -10
  59. package/theme/themes/pastanaga/extras/contents.less +42 -0
  60. package/types/actions/content/content.d.ts +7 -0
  61. package/types/actions/index.d.ts +1 -1
  62. package/types/config/config.test.d.ts +1 -0
  63. package/types/constants/ActionTypes.d.ts +1 -0
  64. package/types/constants/Languages.d.ts +1 -0
  65. package/types/helpers/Blocks/Blocks.d.ts +2 -2
@@ -0,0 +1,232 @@
1
+ import config from '@plone/volto/registry';
2
+
3
+ const navigation_controlpanel = {
4
+ '@id': '/@controlpanels/navigation',
5
+ data: {
6
+ displayed_types: [
7
+ {
8
+ title: 'Link',
9
+ token: 'Link',
10
+ },
11
+ {
12
+ title: 'News Item',
13
+ token: 'News Item',
14
+ },
15
+ {
16
+ title: 'Folder',
17
+ token: 'Folder',
18
+ },
19
+ {
20
+ title: 'Page',
21
+ token: 'Document',
22
+ },
23
+ {
24
+ title: 'Event',
25
+ token: 'Event',
26
+ },
27
+ {
28
+ title: 'Collection',
29
+ token: 'Collection',
30
+ },
31
+ ],
32
+ filter_on_workflow: false,
33
+ generate_tabs: true,
34
+ navigation_depth: 3,
35
+ nonfolderish_tabs: true,
36
+ parent_types_not_to_query: ['TempFolder'],
37
+ root: '/',
38
+ show_excluded_items: false,
39
+ sitemap_depth: 3,
40
+ sort_tabs_on: {
41
+ title: 'Position in Parent',
42
+ token: 'getObjPositionInParent',
43
+ },
44
+ sort_tabs_reversed: false,
45
+ workflow_states_to_show: [],
46
+ },
47
+ group: 'General',
48
+ schema: {
49
+ fieldsets: [
50
+ {
51
+ behavior: 'plone',
52
+ fields: [
53
+ 'navigation_depth',
54
+ 'generate_tabs',
55
+ 'nonfolderish_tabs',
56
+ 'sort_tabs_on',
57
+ 'sort_tabs_reversed',
58
+ 'displayed_types',
59
+ 'filter_on_workflow',
60
+ 'workflow_states_to_show',
61
+ 'show_excluded_items',
62
+ 'root',
63
+ 'sitemap_depth',
64
+ 'parent_types_not_to_query',
65
+ ],
66
+ id: 'default',
67
+ title: 'Default',
68
+ },
69
+ ],
70
+ properties: {
71
+ displayed_types: {
72
+ additionalItems: true,
73
+ default: [
74
+ 'Link',
75
+ 'News Item',
76
+ 'Folder',
77
+ 'Document',
78
+ 'Event',
79
+ 'Collection',
80
+ ],
81
+ description:
82
+ 'The content types that should be shown in the navigation and site map.',
83
+ factory: 'Tuple',
84
+ items: {
85
+ description: '',
86
+ factory: 'Choice',
87
+ title: '',
88
+ type: 'string',
89
+ vocabulary: {
90
+ '@id':
91
+ 'http://localhost:3000/@vocabularies/plone.app.vocabularies.ReallyUserFriendlyTypes',
92
+ },
93
+ },
94
+ title: 'Displayed content types',
95
+ type: 'array',
96
+ uniqueItems: true,
97
+ },
98
+ filter_on_workflow: {
99
+ default: false,
100
+ description:
101
+ 'The workflow states that should be shown in the navigation and the site map.',
102
+ factory: 'Yes/No',
103
+ title: 'Filter on workflow state',
104
+ type: 'boolean',
105
+ },
106
+ generate_tabs: {
107
+ default: true,
108
+ description:
109
+ 'By default, all items created at the root level will appear as tabs. You can turn this off if you prefer manually constructing this part of the navigation.',
110
+ factory: 'Yes/No',
111
+ title: 'Automatically generate tabs',
112
+ type: 'boolean',
113
+ },
114
+ navigation_depth: {
115
+ default: 3,
116
+ description: 'Number of folder levels to show in the navigation.',
117
+ factory: 'Integer',
118
+ title: 'Navigation depth',
119
+ type: 'integer',
120
+ },
121
+ nonfolderish_tabs: {
122
+ default: true,
123
+ description:
124
+ "By default, any content item in the root of the portal will appear as a tab. If you turn this option off, only folders will be shown. This only has an effect if 'automatically generate tabs' is enabled.",
125
+ factory: 'Yes/No',
126
+ title: 'Generate tabs for items other than folders.',
127
+ type: 'boolean',
128
+ },
129
+ parent_types_not_to_query: {
130
+ additionalItems: true,
131
+ default: ['TempFolder'],
132
+ description: 'Hide content inside the following types in Navigation.',
133
+ factory: 'List',
134
+ items: {
135
+ description: '',
136
+ factory: 'Text line (String)',
137
+ title: '',
138
+ type: 'string',
139
+ },
140
+ title: 'Hide children of these types',
141
+ type: 'array',
142
+ uniqueItems: false,
143
+ },
144
+ root: {
145
+ default: '/',
146
+ description:
147
+ "Path to be used as navigation root, relative to Plone site root.Starts with '/'",
148
+ factory: 'Text line (String)',
149
+ title: 'Root',
150
+ type: 'string',
151
+ },
152
+ show_excluded_items: {
153
+ default: false,
154
+ description:
155
+ 'If an item has been excluded from navigation should it be shown in navigation when viewing content contained within it or within a subfolder.',
156
+ factory: 'Yes/No',
157
+ title:
158
+ 'Show items normally excluded from navigation if viewing their children.',
159
+ type: 'boolean',
160
+ },
161
+ sitemap_depth: {
162
+ default: 3,
163
+ description: 'Number of folder levels to show in the site map.',
164
+ factory: 'Integer',
165
+ title: 'Sitemap depth',
166
+ type: 'integer',
167
+ },
168
+ sort_tabs_on: {
169
+ choices: [
170
+ ['getObjPositionInParent', 'Position in Parent'],
171
+ ['sortable_title', 'Title'],
172
+ ['getId', 'Short Name (ID)'],
173
+ ],
174
+ default: 'getObjPositionInParent',
175
+ description: 'Index used to sort the tabs',
176
+ enum: ['getObjPositionInParent', 'sortable_title', 'getId'],
177
+ enumNames: ['Position in Parent', 'Title', 'Short Name (ID)'],
178
+ factory: 'Choice',
179
+ title: 'Sort tabs on',
180
+ type: 'string',
181
+ vocabulary: {
182
+ '@id': 'http://localhost:3000/@sources/sort_tabs_on',
183
+ },
184
+ },
185
+ sort_tabs_reversed: {
186
+ default: false,
187
+ description: 'Sort tabs in descending.',
188
+ factory: 'Yes/No',
189
+ title: 'Reversed sort order for tabs.',
190
+ type: 'boolean',
191
+ },
192
+ workflow_states_to_show: {
193
+ additionalItems: true,
194
+ default: [],
195
+ description: '',
196
+ factory: 'Tuple',
197
+ items: {
198
+ description: '',
199
+ factory: 'Choice',
200
+ title: '',
201
+ type: 'string',
202
+ vocabulary: {
203
+ '@id':
204
+ 'http://localhost:3000/@vocabularies/plone.app.vocabularies.WorkflowStates',
205
+ },
206
+ },
207
+ title: '',
208
+ type: 'array',
209
+ uniqueItems: true,
210
+ },
211
+ },
212
+ required: ['navigation_depth', 'sort_tabs_on', 'root', 'sitemap_depth'],
213
+ type: 'object',
214
+ },
215
+ title: 'Navigation',
216
+ };
217
+
218
+ test('test navigation controlpanel fields', () => {
219
+ const { filterControlPanelsSchema } = config.settings;
220
+ const result = filterControlPanelsSchema(navigation_controlpanel);
221
+ const not_in_navigation = [
222
+ 'generate_tabs',
223
+ 'navigation_depth',
224
+ 'sort_tabs_on',
225
+ 'sort_tabs_reversed',
226
+ 'sitemap_depth',
227
+ ];
228
+ const hasField = result['fieldsets'][0]['fields'].some((field) =>
229
+ not_in_navigation.includes(field),
230
+ );
231
+ expect(hasField).toBe(false);
232
+ });
@@ -145,3 +145,4 @@ export const RESET_LOGIN_REQUEST = 'RESET_LOGIN_REQUEST';
145
145
  export const GET_SITE = 'GET_SITE';
146
146
  export const GET_NAVROOT = 'GET_NAVROOT';
147
147
  export const SET_FORM_DATA = 'SET_FORM_DATA';
148
+ export const UPDATE_UPLOADED_FILES = 'UPDATE_UPLOADED_FILES';
@@ -11,6 +11,7 @@ module.exports = {
11
11
  eu: 'Euskara',
12
12
  fi: 'Suomi',
13
13
  fr: 'Français',
14
+ hi: 'Hindi',
14
15
  it: 'Italiano',
15
16
  nl: 'Nederlands',
16
17
  ro: 'Română',
@@ -9,6 +9,7 @@ const HEADERS = [
9
9
  'content-type',
10
10
  'x-sendfile',
11
11
  'x-accel-redirect',
12
+ 'x-robots-tag',
12
13
  ];
13
14
 
14
15
  function filesMiddlewareFn(req, res, next) {
@@ -7,6 +7,7 @@ const HEADERS = [
7
7
  'cache-control',
8
8
  'x-sendfile',
9
9
  'x-accel-redirect',
10
+ 'x-robots-tag',
10
11
  ];
11
12
 
12
13
  function imageMiddlewareFn(req, res, next) {
@@ -80,6 +80,10 @@ class Api {
80
80
 
81
81
  Object.keys(headers).forEach((key) => request.set(key, headers[key]));
82
82
 
83
+ if (__SERVER__ && checkUrl && ['get', 'head'].includes(method)) {
84
+ request.redirects(0);
85
+ }
86
+
83
87
  if (data) {
84
88
  request.send(data);
85
89
  }
@@ -104,6 +108,14 @@ class Api {
104
108
  url: request.xhr.responseURL,
105
109
  });
106
110
  }
111
+
112
+ if ([301, 302].includes(err?.status)) {
113
+ return reject({
114
+ code: err.status,
115
+ url: err.response.headers.location,
116
+ });
117
+ }
118
+
107
119
  return err ? reject(err) : resolve(response.body || response.text);
108
120
  });
109
121
  });
@@ -18,6 +18,7 @@ beforeAll(() => {
18
18
 
19
19
  const api = new Api();
20
20
  const { settings } = config;
21
+ global.__SERVER__ = true; // eslint-disable-line no-underscore-dangle
21
22
 
22
23
  test('get request', () => {});
23
24
 
@@ -24,6 +24,7 @@ beforeAll(() => {
24
24
 
25
25
  const api = new Api();
26
26
  const { settings } = config;
27
+ global.__SERVER__ = true; // eslint-disable-line no-underscore-dangle
27
28
 
28
29
  test('get request', () => {});
29
30
 
@@ -414,7 +414,7 @@ export function emptyBlocksForm() {
414
414
  * (could be empty, if not type given) and the number of blocks
415
415
  * @function blocksFormGenerator
416
416
  * @param {number} number How many blocks to generate of the type (could be "empty", if no type provided)
417
- * @param {number} type The type of the blocks
417
+ * @param {string} type The type of the blocks
418
418
  * @return {Object} blocks/blocks_layout pair filled with the generated blocks
419
419
  */
420
420
  export function blocksFormGenerator(number, type) {
@@ -17,7 +17,7 @@ import {
17
17
  RESET_APIERROR,
18
18
  SET_APIERROR,
19
19
  } from '@plone/volto/constants/ActionTypes';
20
- import { changeLanguage } from '@plone/volto/actions';
20
+ import { changeLanguage, updateUploadedFiles } from '@plone/volto/actions';
21
21
  import {
22
22
  toGettextLang,
23
23
  toReactIntlLang,
@@ -133,6 +133,7 @@ const apiMiddlewareFactory =
133
133
  const { settings } = config;
134
134
 
135
135
  const token = getState().userSession.token;
136
+ let uploadedFiles = getState().content.uploadedFiles;
136
137
  let isAnonymous = true;
137
138
  if (token) {
138
139
  const tokenExpiration = jwtDecode(token).exp;
@@ -154,7 +155,6 @@ const apiMiddlewareFactory =
154
155
  }
155
156
 
156
157
  next({ ...rest, type: `${type}_PENDING` });
157
-
158
158
  if (socket) {
159
159
  actionPromise = Array.isArray(request)
160
160
  ? Promise.all(
@@ -188,6 +188,9 @@ const apiMiddlewareFactory =
188
188
  ),
189
189
  },
190
190
  ).then((reqres) => {
191
+ if (action.subrequest === 'batch-upload') {
192
+ dispatch(updateUploadedFiles(++uploadedFiles));
193
+ }
191
194
  return [...acc, reqres];
192
195
  });
193
196
  });
@@ -214,6 +217,10 @@ const apiMiddlewareFactory =
214
217
  });
215
218
  actionPromise.then(
216
219
  (result) => {
220
+ if (uploadedFiles !== 0) {
221
+ dispatch(updateUploadedFiles(0));
222
+ }
223
+
217
224
  const { settings } = config;
218
225
  if (getState().apierror.connectionRefused) {
219
226
  next({
@@ -17,6 +17,7 @@ import {
17
17
  RESET_CONTENT,
18
18
  UPDATE_CONTENT,
19
19
  UPDATECOLUMNS_CONTENT,
20
+ UPDATE_UPLOADED_FILES,
20
21
  } from '@plone/volto/constants/ActionTypes';
21
22
 
22
23
  const initialState = {
@@ -62,6 +63,7 @@ const initialState = {
62
63
  },
63
64
  data: null,
64
65
  subrequests: {},
66
+ uploadedFiles: 0,
65
67
  };
66
68
 
67
69
  /**
@@ -350,6 +352,11 @@ export default function content(state = initialState, action = {}) {
350
352
  },
351
353
  data: null,
352
354
  };
355
+ case UPDATE_UPLOADED_FILES:
356
+ return {
357
+ ...state,
358
+ uploadedFiles: action.uploadedFiles,
359
+ };
353
360
  default:
354
361
  return state;
355
362
  }
@@ -8,6 +8,7 @@ import {
8
8
  UPDATE_CONTENT,
9
9
  LOCK_CONTENT,
10
10
  UNLOCK_CONTENT,
11
+ UPDATE_UPLOADED_FILES,
11
12
  } from '@plone/volto/constants/ActionTypes';
12
13
 
13
14
  const { settings } = config;
@@ -57,6 +58,7 @@ describe('Content reducer', () => {
57
58
  loading: false,
58
59
  error: null,
59
60
  },
61
+ uploadedFiles: 0,
60
62
  });
61
63
  });
62
64
 
@@ -579,4 +581,15 @@ describe('Content reducer', () => {
579
581
  },
580
582
  });
581
583
  });
584
+
585
+ it('should handle UPDATE_UPLOADED_FILES', () => {
586
+ expect(
587
+ content(undefined, {
588
+ type: UPDATE_UPLOADED_FILES,
589
+ uploadedFiles: 5,
590
+ }),
591
+ ).toMatchObject({
592
+ uploadedFiles: 5,
593
+ });
594
+ });
582
595
  });
@@ -318,6 +318,16 @@ body.has-toolbar.has-sidebar-collapsed .ui.wrapper > .ui.inner.block.full {
318
318
  0 2px 4px rgba(0, 0, 0, 0.05);
319
319
  transform: translate(-50%, 0);
320
320
 
321
+ .separator {
322
+ position: relative;
323
+ top: 5px;
324
+ display: inline-block;
325
+ height: 24px;
326
+ border-right: 1px solid #ddd;
327
+ margin: 0 0 4px 4px;
328
+ vertical-align: bottom;
329
+ }
330
+
321
331
  form {
322
332
  display: flex;
323
333
  }
@@ -591,16 +601,6 @@ body.has-toolbar.has-sidebar-collapsed .ui.wrapper > .ui.inner.block.full {
591
601
  height: 100%;
592
602
  }
593
603
 
594
- .separator {
595
- position: relative;
596
- top: 5px;
597
- display: inline-block;
598
- height: 24px;
599
- border-right: 1px solid #ddd;
600
- margin: 0 0 4px 4px;
601
- vertical-align: bottom;
602
- }
603
-
604
604
  // HTML block
605
605
  .html-editor {
606
606
  z-index: 1;
@@ -212,4 +212,46 @@
212
212
  padding: 0.67857143em 1em;
213
213
  }
214
214
  }
215
+
216
+ .ui.progress:first-child {
217
+ margin: 0 0 2.5em;
218
+ }
219
+
220
+ .progress-container {
221
+ padding: 35px;
222
+ background: white;
223
+ box-shadow: 0px 0px 12px 4px rgba(0, 0, 0, 0.43);
224
+ }
225
+
226
+ .progress-bar {
227
+ width: 300px;
228
+ height: 15px;
229
+ border-radius: 0;
230
+
231
+ .bar {
232
+ height: 15px;
233
+ border-radius: 0;
234
+ background: #0074a3;
235
+ }
236
+
237
+ .label {
238
+ position: relative;
239
+ margin: 0;
240
+ background: white;
241
+ color: grey;
242
+ font-size: small;
243
+ font-weight: normal;
244
+ line-height: 18px;
245
+ text-align: left;
246
+
247
+ .uploaded-files {
248
+ color: #0074a3;
249
+ }
250
+ }
251
+ }
252
+ }
253
+
254
+ .contents-upload-modal > .content {
255
+ overflow: auto;
256
+ max-height: 75vh;
215
257
  }
@@ -1,3 +1,10 @@
1
+ /**
2
+ * update uploaded files count function
3
+ * @function updateUploadedFiles
4
+ * @param {number} number of files uploaded
5
+ * @returns {Object} update uploaded files action
6
+ */
7
+ export function updateUploadedFiles(uploadedFiles: any): any;
1
8
  /**
2
9
  * Create content function.
3
10
  * @function createContent
@@ -21,7 +21,7 @@ export { getNavroot } from "./navroot/navroot";
21
21
  export { copy, cut, copyContent, moveContent } from "@plone/volto/actions/clipboard/clipboard";
22
22
  export { installAddon, listAddons, uninstallAddon, upgradeAddon } from "./addons/addons";
23
23
  export { addComment, deleteComment, listComments, listMoreComments, updateComment } from "@plone/volto/actions/comments/comments";
24
- export { createContent, deleteContent, updateContent, getContent, orderContent, sortContent, resetContent, updateColumnsContent, lockContent, unlockContent, linkIntegrityCheck } from "@plone/volto/actions/content/content";
24
+ export { createContent, deleteContent, updateContent, getContent, orderContent, sortContent, resetContent, updateColumnsContent, lockContent, unlockContent, linkIntegrityCheck, updateUploadedFiles } from "@plone/volto/actions/content/content";
25
25
  export { getControlpanel, postControlpanel, deleteControlpanel, listControlpanels, updateControlpanel, getSystemInformation, getDatabaseInformation } from "@plone/volto/actions/controlpanels/controlpanels";
26
26
  export { createGroup, deleteGroup, getGroup, listGroups, updateGroup } from "@plone/volto/actions/groups/groups";
27
27
  export { getHistory, revertHistory } from "@plone/volto/actions/history/history";
@@ -0,0 +1 @@
1
+ export {};
@@ -141,3 +141,4 @@ export const RESET_LOGIN_REQUEST: "RESET_LOGIN_REQUEST";
141
141
  export const GET_SITE: "GET_SITE";
142
142
  export const GET_NAVROOT: "GET_NAVROOT";
143
143
  export const SET_FORM_DATA: "SET_FORM_DATA";
144
+ export const UPDATE_UPLOADED_FILES: "UPDATE_UPLOADED_FILES";
@@ -5,6 +5,7 @@ export let es: string;
5
5
  export let eu: string;
6
6
  export let fi: string;
7
7
  export let fr: string;
8
+ export let hi: string;
8
9
  export let it: string;
9
10
  export let nl: string;
10
11
  export let ro: string;
@@ -107,10 +107,10 @@ export function emptyBlocksForm(): any;
107
107
  * (could be empty, if not type given) and the number of blocks
108
108
  * @function blocksFormGenerator
109
109
  * @param {number} number How many blocks to generate of the type (could be "empty", if no type provided)
110
- * @param {number} type The type of the blocks
110
+ * @param {string} type The type of the blocks
111
111
  * @return {Object} blocks/blocks_layout pair filled with the generated blocks
112
112
  */
113
- export function blocksFormGenerator(number: number, type: number): any;
113
+ export function blocksFormGenerator(number: number, type: string): any;
114
114
  /**
115
115
  * Recursively discover blocks in data and call the provided callback
116
116
  * @function visitBlocks