@plone/volto 14.0.0-alpha.35 → 14.0.0-alpha.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Change Log
2
2
 
3
+ ## 14.0.0-alpha.36 (2021-11-25)
4
+
5
+ ### Bugfix
6
+
7
+ - Fix regression in actions vocabularies calls because the change to use contextual schemas @sneridagh
8
+ - Include block schema enhancers (main block schema enhancer + variation schema enhancer) when calculating block default data @tiberiuichim
9
+
10
+ ### Internal
11
+
12
+ - Fix references to old configuration style in apiExpanders documentation @tiberiuichim
13
+ - Add `applySchemaDefaults`, in addition to `applyBlockDefaults`, to allow reuse in object widgets and other advanced scenarios @tiberiuichim
14
+
3
15
  ## 14.0.0-alpha.35 (2021-11-24)
4
16
 
5
17
  ### Bugfix
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  }
10
10
  ],
11
11
  "license": "MIT",
12
- "version": "14.0.0-alpha.35",
12
+ "version": "14.0.0-alpha.36",
13
13
  "repository": {
14
14
  "type": "git",
15
15
  "url": "git@github.com:plone/volto.git"
@@ -7,7 +7,6 @@ import {
7
7
  GET_VOCABULARY,
8
8
  GET_VOCABULARY_TOKEN_TITLE,
9
9
  } from '@plone/volto/constants/ActionTypes';
10
- import config from '@plone/volto/registry';
11
10
 
12
11
  /**
13
12
  * Get vocabulary given a URL (coming from a Schema) or from a vocabulary name.
@@ -18,11 +17,10 @@ import config from '@plone/volto/registry';
18
17
  * @returns {Object} Get vocabulary action.
19
18
  */
20
19
  export function getVocabulary(vocabNameOrURL, query = null, start = 0) {
21
- const { settings } = config;
22
20
  // In case we have a URL, we have to get the vocabulary name
23
- const vocabulary =
24
- vocabNameOrURL &&
25
- vocabNameOrURL.replace(`${settings.apiPath}/@vocabularies/`, '');
21
+ const vocabulary = /(?<=\/@vocabularies\/)(.*)/.test(vocabNameOrURL)
22
+ ? /(?<=\/@vocabularies\/)(.*)/.exec(vocabNameOrURL)[0]
23
+ : vocabNameOrURL;
26
24
  let queryString = `b_start=${start}`;
27
25
  if (query) {
28
26
  queryString = `${queryString}&title=${query}`;
@@ -47,12 +45,10 @@ export function getVocabulary(vocabNameOrURL, query = null, start = 0) {
47
45
  * @returns {Object} Get vocabulary action.
48
46
  */
49
47
  export function getVocabularyTokenTitle(vocabNameOrURL, token = null) {
50
- const { settings } = config;
51
48
  // In case we have a URL, we have to get the vocabulary name
52
- const vocabulary = vocabNameOrURL.replace(
53
- `${settings.apiPath}/@vocabularies/`,
54
- '',
55
- );
49
+ const vocabulary = /(?<=\/@vocabularies\/)(.*)/.test(vocabNameOrURL)
50
+ ? /(?<=\/@vocabularies\/)(.*)/.exec(vocabNameOrURL)[0]
51
+ : vocabNameOrURL;
56
52
 
57
53
  return {
58
54
  type: GET_VOCABULARY_TOKEN_TITLE,
@@ -15,5 +15,31 @@ describe('Vocabularies actions', () => {
15
15
  `/@vocabularies/${vocabulary}?b_start=0&title=${query}`,
16
16
  );
17
17
  });
18
+ it('should create an action to get a vocabulary if a URL is passed', () => {
19
+ const vocabulary =
20
+ 'http://localhost:8080/@vocabularies/plone.app.vocabularies.Keywords';
21
+ const query = 'john';
22
+ const action = getVocabulary(vocabulary, query);
23
+
24
+ expect(action.type).toEqual(GET_VOCABULARY);
25
+ expect(action.vocabulary).toEqual(vocabulary);
26
+ expect(action.request.op).toEqual('get');
27
+ expect(action.request.path).toEqual(
28
+ `/@vocabularies/plone.app.vocabularies.Keywords?b_start=0&title=${query}`,
29
+ );
30
+ });
31
+ it('should create an action to get a vocabulary if a URL with path is passed', () => {
32
+ const vocabulary =
33
+ 'http://localhost:8080/de/foo/bar/@vocabularies/plone.app.vocabularies.Keywords';
34
+ const query = 'john';
35
+ const action = getVocabulary(vocabulary, query);
36
+
37
+ expect(action.type).toEqual(GET_VOCABULARY);
38
+ expect(action.vocabulary).toEqual(vocabulary);
39
+ expect(action.request.op).toEqual('get');
40
+ expect(action.request.path).toEqual(
41
+ `/@vocabularies/plone.app.vocabularies.Keywords?b_start=0&title=${query}`,
42
+ );
43
+ });
18
44
  });
19
45
  });
@@ -7,6 +7,7 @@ import { omit, without, endsWith, find, keys } from 'lodash';
7
7
  import move from 'lodash-move';
8
8
  import { v4 as uuid } from 'uuid';
9
9
  import config from '@plone/volto/registry';
10
+ import { applySchemaEnhancer } from '@plone/volto/helpers';
10
11
 
11
12
  /**
12
13
  * Get blocks field.
@@ -361,8 +362,27 @@ export function visitBlocks(content, callback) {
361
362
  }
362
363
  }
363
364
 
365
+ /**
366
+ * Initializes data with the default values coming from schema
367
+ */
368
+ export function applySchemaDefaults({ data = {}, schema }) {
369
+ const derivedData = {
370
+ ...Object.keys(schema.properties).reduce((accumulator, currentField) => {
371
+ return schema.properties[currentField].default
372
+ ? {
373
+ ...accumulator,
374
+ [currentField]: schema.properties[currentField].default,
375
+ }
376
+ : accumulator;
377
+ }, {}),
378
+ ...data,
379
+ };
380
+ return derivedData;
381
+ }
382
+
364
383
  /**
365
384
  * Apply the block's default (as defined in schema) to the block data.
385
+ *
366
386
  * @function applyBlockDefaults
367
387
  * @param {Object} params An object with data, intl and anything else
368
388
  * @return {Object} Derived data, with the defaults extracted from the schema
@@ -373,22 +393,11 @@ export function applyBlockDefaults({ data, intl, ...rest }, blocksConfig) {
373
393
  (blocksConfig || config.blocks.blocksConfig)[block_type] || {};
374
394
  if (!blockSchema) return data;
375
395
 
376
- const schema =
396
+ let schema =
377
397
  typeof blockSchema === 'function'
378
398
  ? blockSchema({ data, intl, ...rest })
379
399
  : blockSchema;
400
+ schema = applySchemaEnhancer({ schema, formData: data, intl });
380
401
 
381
- const derivedData = {
382
- ...Object.keys(schema.properties).reduce((accumulator, currentField) => {
383
- return schema.properties[currentField].default
384
- ? {
385
- ...accumulator,
386
- [currentField]: schema.properties[currentField].default,
387
- }
388
- : accumulator;
389
- }, {}),
390
- ...data,
391
- };
392
-
393
- return derivedData;
402
+ return applySchemaDefaults({ data, schema });
394
403
  }
@@ -15,6 +15,7 @@ import {
15
15
  previousBlockId,
16
16
  visitBlocks,
17
17
  applyBlockDefaults,
18
+ applySchemaDefaults,
18
19
  } from './Blocks';
19
20
 
20
21
  import config from '@plone/volto/registry';
@@ -54,6 +55,111 @@ config.blocks.blocksConfig.text = {
54
55
  }),
55
56
  };
56
57
 
58
+ config.blocks.blocksConfig.enhancedBlock = {
59
+ id: 'enhancedBlock',
60
+ title: 'Text',
61
+ group: 'text',
62
+ restricted: false,
63
+ mostUsed: false,
64
+ blockHasOwnFocusManagement: true,
65
+ blockHasValue: (data) => {
66
+ const isEmpty =
67
+ !data.text ||
68
+ (data.text?.blocks?.length === 1 && data.text.blocks[0].text === '');
69
+ return !isEmpty;
70
+ },
71
+ schemaEnhancer: ({ schema, formData }) => {
72
+ schema.fieldsets[0].fields.push('extra');
73
+ schema.properties.extra = { default: 'Extra value' };
74
+ return schema;
75
+ },
76
+ variations: [
77
+ {
78
+ id: 'firstVariation',
79
+ schemaEnhancer: ({ schema, formData }) => {
80
+ schema.fieldsets[0].fields.push('extraVariationField');
81
+ schema.properties['extraVariationField'] = {
82
+ default: 'Extra variation field',
83
+ };
84
+ return schema;
85
+ },
86
+ },
87
+ ],
88
+ blockSchema: ({ data }) => ({
89
+ fieldsets: [
90
+ {
91
+ id: 'default',
92
+ fields: ['title', 'description', 'nonDefault'],
93
+ title: 'Default',
94
+ },
95
+ ],
96
+ properties: {
97
+ title: {
98
+ default: 'Default title',
99
+ },
100
+ description: {
101
+ default: 'Default description',
102
+ },
103
+ nonDefault: {
104
+ title: 'Non default',
105
+ },
106
+ },
107
+ }),
108
+ };
109
+
110
+ config.blocks.blocksConfig.enhancedBlockCase2 = {
111
+ id: 'enhancedBlockCase2',
112
+ title: 'Text',
113
+ group: 'text',
114
+ restricted: false,
115
+ mostUsed: false,
116
+ blockHasOwnFocusManagement: true,
117
+ blockHasValue: (data) => {
118
+ const isEmpty =
119
+ !data.text ||
120
+ (data.text?.blocks?.length === 1 && data.text.blocks[0].text === '');
121
+ return !isEmpty;
122
+ },
123
+ schemaEnhancer: ({ schema, formData }) => {
124
+ schema.fieldsets[0].fields.push('extra');
125
+ schema.properties.extra = {
126
+ default: 'Extra value from block schema enhancer',
127
+ };
128
+ return schema;
129
+ },
130
+ variations: [
131
+ {
132
+ id: 'firstVariation',
133
+ schemaEnhancer: ({ schema, formData }) => {
134
+ schema.properties['extra'] = {
135
+ default: 'Extra variation field',
136
+ };
137
+ return schema;
138
+ },
139
+ },
140
+ ],
141
+ blockSchema: ({ data }) => ({
142
+ fieldsets: [
143
+ {
144
+ id: 'default',
145
+ fields: ['title', 'description', 'nonDefault'],
146
+ title: 'Default',
147
+ },
148
+ ],
149
+ properties: {
150
+ title: {
151
+ default: 'Default title',
152
+ },
153
+ description: {
154
+ default: 'Default description',
155
+ },
156
+ nonDefault: {
157
+ title: 'Non default',
158
+ },
159
+ },
160
+ }),
161
+ };
162
+
57
163
  config.settings.defaultBlockType = 'text';
58
164
 
59
165
  describe('Blocks', () => {
@@ -383,6 +489,21 @@ describe('Blocks', () => {
383
489
  });
384
490
  });
385
491
 
492
+ describe('applySchemaDefaults', () => {
493
+ it('Sets data according to schema default values', () => {
494
+ const data = {
495
+ '@type': 'text',
496
+ description: 'already filled',
497
+ };
498
+ const schema = config.blocks.blocksConfig.text.blockSchema({ data });
499
+ expect(applySchemaDefaults({ schema, data })).toEqual({
500
+ '@type': 'text',
501
+ title: 'Default title',
502
+ description: 'already filled',
503
+ });
504
+ });
505
+ });
506
+
386
507
  describe('applyBlockDefaults', () => {
387
508
  it('Sets data according to schema default values', () => {
388
509
  const data = {
@@ -406,5 +527,50 @@ describe('Blocks', () => {
406
527
  description: 'already filled',
407
528
  });
408
529
  });
530
+
531
+ it('Supports block schema enhancers', () => {
532
+ const data = {
533
+ '@type': 'enhancedBlock',
534
+ description: 'already filled',
535
+ // variation: 'firstVariation',
536
+ };
537
+ expect(applyBlockDefaults({ data })).toEqual({
538
+ '@type': 'enhancedBlock',
539
+ title: 'Default title',
540
+ description: 'already filled',
541
+ extra: 'Extra value',
542
+ });
543
+ });
544
+
545
+ it('Supports block schema enhancers coming from variations', () => {
546
+ const data = {
547
+ '@type': 'enhancedBlock',
548
+ description: 'already filled',
549
+ variation: 'firstVariation',
550
+ };
551
+ expect(applyBlockDefaults({ data })).toEqual({
552
+ '@type': 'enhancedBlock',
553
+ title: 'Default title',
554
+ description: 'already filled',
555
+ extra: 'Extra value',
556
+ extraVariationField: 'Extra variation field',
557
+ variation: 'firstVariation',
558
+ });
559
+ });
560
+
561
+ it('Block schema enhancers override variations', () => {
562
+ const data = {
563
+ '@type': 'enhancedBlockCase2',
564
+ description: 'already filled',
565
+ variation: 'firstVariation',
566
+ };
567
+ expect(applyBlockDefaults({ data })).toEqual({
568
+ '@type': 'enhancedBlockCase2',
569
+ title: 'Default title',
570
+ description: 'already filled',
571
+ extra: 'Extra value from block schema enhancer',
572
+ variation: 'firstVariation',
573
+ });
574
+ });
409
575
  });
410
576
  });
@@ -59,6 +59,39 @@ export const addExtensionFieldToSchema = ({
59
59
  return schema;
60
60
  };
61
61
 
62
+ /**
63
+ * A generic HOC that provides "schema enhancer functionality" for any custom
64
+ * block extension.
65
+ *
66
+ * This enables blocks to have additional "variations", beyond the usual
67
+ * `variations` field. This function is not directly used by Volto.
68
+ *
69
+ * To be used with a block configuration like:
70
+ *
71
+ * ```
72
+ * {
73
+ * id: 'someBlockId',
74
+ * extensions: {
75
+ * '<someExtensionName>': {
76
+ * items: [
77
+ * {
78
+ * id: 'selectFacet',
79
+ * title: 'Select',
80
+ * view: SelectFacet,
81
+ * isDefault: true,
82
+ * },
83
+ * {
84
+ * id: 'checkboxFacet',
85
+ * title: 'Checkbox',
86
+ * view: CheckboxFacet,
87
+ * isDefault: false,
88
+ * },
89
+ * ]
90
+ * }
91
+ * }
92
+ * }
93
+ * ```
94
+ */
62
95
  export const withBlockSchemaEnhancer = (
63
96
  FormComponent,
64
97
  extensionName = 'vendor',
@@ -111,6 +144,36 @@ export const withBlockSchemaEnhancer = (
111
144
  return <FormComponent {...props} schema={schema} />;
112
145
  };
113
146
 
147
+ /**
148
+ * Apply block variation schema enhancers to the provided schema, using block
149
+ * information from the provided block data (as `formData`).
150
+ *
151
+ * Blocks can be enhanced with variations declared like:
152
+ *
153
+ * ```
154
+ * {
155
+ * id: 'searchBlock',
156
+ * schemaEnhancer: ({schema, formData, intl}) => schema,
157
+ * variations: [
158
+ * {
159
+ * id: 'facetsRightSide',
160
+ * title: 'Facets on right side',
161
+ * view: RightColumnFacets,
162
+ * isDefault: true,
163
+ * },
164
+ * {
165
+ * id: 'facetsLeftSide',
166
+ * title: 'Facets on left side',
167
+ * view: LeftColumnFacets,
168
+ * isDefault: false,
169
+ * schemaEnhancer: ({schema, formData, intl}) => schema,
170
+ * },
171
+ * ],
172
+ *
173
+ * ```
174
+ * Notice that each variation can declare an option schema enhancer, and each
175
+ * block supports an optional `schemaEnhancer` function.
176
+ */
114
177
  export const applySchemaEnhancer = ({
115
178
  schema: originalSchema,
116
179
  formData,
@@ -123,11 +186,15 @@ export const applySchemaEnhancer = ({
123
186
  const variations = blocks?.blocksConfig[blockType]?.variations || [];
124
187
 
125
188
  if (variations.length === 0) {
126
- // No variations present but anyways
127
- // finalize the schema with a schemaEnhancer in the block config is present
189
+ // No variations present but we finalize the schema with a schemaEnhancer
190
+ // in the block config (if present)
128
191
  schemaEnhancer = blocks.blocksConfig?.[blockType]?.schemaEnhancer;
129
192
  if (schemaEnhancer)
130
- schema = schemaEnhancer({ schema: originalSchema, formData, intl });
193
+ schema = schemaEnhancer({
194
+ schema: cloneDeep(originalSchema),
195
+ formData,
196
+ intl,
197
+ });
131
198
  return schema || originalSchema;
132
199
  }
133
200
 
@@ -148,6 +215,13 @@ export const applySchemaEnhancer = ({
148
215
  return schema || originalSchema;
149
216
  };
150
217
 
218
+ /**
219
+ * A HOC that enhances the incoming schema prop with block variations support
220
+ * by:
221
+ *
222
+ * - applies the selected variation's schema enhancer
223
+ * - adds the variation selection input (as a choice widget)
224
+ */
151
225
  export const withVariationSchemaEnhancer = (FormComponent) => (props) => {
152
226
  const { formData, schema: originalSchema } = props;
153
227
  const intl = useIntl();
@@ -50,6 +50,7 @@ export {
50
50
  nextBlockId,
51
51
  previousBlockId,
52
52
  applyBlockDefaults,
53
+ applySchemaDefaults,
53
54
  } from '@plone/volto/helpers/Blocks/Blocks';
54
55
  export BodyClass from '@plone/volto/helpers/BodyClass/BodyClass';
55
56
  export ScrollToTop from '@plone/volto/helpers/ScrollToTop/ScrollToTop';