@plone/volto 16.0.0 → 16.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Change Log
2
2
 
3
+ ## 16.1.0 (2022-11-23)
4
+
5
+ ### Feature
6
+
7
+ - Support for drilled down current state and updater function from schema in `ObjectListWidget`. This allows to sync the current object selected from the UI and the block settings and viceversa @sneridagh
8
+ - Allow custom style wrapper classnames via fieldname suffixes. Added `config.settings.styleClassNameConverters` to register new suffix converters @tiberiuichim
9
+
10
+ ### Bugfix
11
+
12
+ - Fix jest moduleNameMapper for `@plone/volto/babel` @tiberiuichim
13
+ - Fix addons loader test @tiberiuichim
14
+ - Pass down `onChangeBlock` prop to all stock blocks in core @sneridagh
15
+ - Fix user search by full name in users control panel @reebalazs
16
+
3
17
  ## 16.0.0 (2022-11-22)
4
18
 
5
19
  ### Breaking
@@ -127,8 +141,6 @@ See https://6.dev-docs.plone.org/volto/upgrade-guide/index.html for more informa
127
141
  - Allow passing ariaHidden, id and style to an Icon's SVG @JeffersonBledsoe #3908
128
142
  - All Fields now understand the `default` prop as a fallback value in case their data value is missing. As a convenience, the `defaultValue` is also used as a fallback, but this shouldn't proliferate. @tiberiuichim
129
143
  - There is an experimental setting to move the button for adding a new block to show below any selected block, instead of only on the left of empty text blocks. Set `config.experimental.addBlockButton.enabled = true` to enable it. @davisagli
130
- - Allow custom style wrapper classnames via fieldname suffixes. Added `config.settings.styleClassNameConverters` to register new suffix converters @tiberiuichim
131
- - Support for drilled down current state and updater function from schema in `ObjectListWidget`. This allows to sync the current object selected from the UI and the block settings and viceversa @sneridagh
132
144
 
133
145
  ### Bugfix
134
146
 
@@ -257,7 +269,7 @@ See https://6.dev-docs.plone.org/volto/upgrade-guide/index.html for more informa
257
269
  - Fix image tag for Plone 5.2.x, use 5.2.9 for now @sneridagh
258
270
  - Cover an additional edge case for defaults @tiberiuichim
259
271
  - Fix issue when using list markdown when list is already active (volto-slate) @robgietema
260
- - Fix translation spelling of toggle @iFlameing
272
+ - Fix translation spelling of toggle @iFlameing
261
273
  - Fix keyboard accessibility issue of Clear button in Folder content view @iFlameing
262
274
 
263
275
  ### Internal
@@ -365,7 +377,7 @@ See https://6.dev-docs.plone.org/volto/upgrade-guide/index.html for more informa
365
377
 
366
378
  - Fix keyboard accessibility issue of Clear button in Folder content view @iFlameing
367
379
  - Fix issue when using list markdown when list is already active (volto-slate) @robgietema
368
- - Fix translation spelling of toggle @iFlameing
380
+ - Fix translation spelling of toggle @iFlameing
369
381
 
370
382
  ### Documentation
371
383
 
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  }
10
10
  ],
11
11
  "license": "MIT",
12
- "version": "16.0.0",
12
+ "version": "16.1.0",
13
13
  "repository": {
14
14
  "type": "git",
15
15
  "url": "git@github.com:plone/volto.git"
@@ -85,6 +85,7 @@
85
85
  },
86
86
  "moduleNameMapper": {
87
87
  "@plone/volto/package.json": "<rootDir>/package.json",
88
+ "@plone/volto/babel": "<rootDir>/babel.js",
88
89
  "@plone/volto/(.*)$": "<rootDir>/src/$1",
89
90
  "@plone/volto-slate": "<rootDir>/packages/volto-slate/src",
90
91
  "~/config": "<rootDir>/src/config",
@@ -9,7 +9,6 @@ const HeroImageLeftBlockData = (props) => {
9
9
  const schema = schemaHero({ ...props, intl });
10
10
  return (
11
11
  <BlockDataForm
12
- block={block}
13
12
  schema={schema}
14
13
  title={schema.title}
15
14
  onChangeField={(id, value) => {
@@ -18,7 +17,9 @@ const HeroImageLeftBlockData = (props) => {
18
17
  [id]: value,
19
18
  });
20
19
  }}
20
+ onChangeBlock={onChangeBlock}
21
21
  formData={data}
22
+ block={block}
22
23
  />
23
24
  );
24
25
  };
@@ -52,6 +52,7 @@ const ImageSidebar = (props) => {
52
52
  [id]: value,
53
53
  });
54
54
  }}
55
+ onChangeBlock={onChangeBlock}
55
56
  formData={data}
56
57
  block={block}
57
58
  />
@@ -19,6 +19,7 @@ const ListingData = (props) => {
19
19
  [id]: value,
20
20
  });
21
21
  }}
22
+ onChangeBlock={onChangeBlock}
22
23
  formData={data}
23
24
  block={block}
24
25
  />
@@ -39,6 +39,7 @@ const MapsSidebar = (props) => {
39
39
  [id]: value,
40
40
  });
41
41
  }}
42
+ onChangeBlock={onChangeBlock}
42
43
  formData={data}
43
44
  block={block}
44
45
  />
@@ -80,6 +80,7 @@ const SearchBlockEdit = (props) => {
80
80
  [id]: value,
81
81
  });
82
82
  }}
83
+ onChangeBlock={onChangeBlock}
83
84
  formData={data}
84
85
  />
85
86
  </SidebarPortal>
@@ -24,6 +24,7 @@ class Edit extends Component {
24
24
  [id]: value,
25
25
  });
26
26
  }}
27
+ onChangeBlock={this.props.onChangeBlock}
27
28
  formData={this.props.data}
28
29
  />
29
30
  </SidebarPortal>
@@ -39,8 +39,8 @@ const VideoSidebar = (props) => {
39
39
  [id]: value,
40
40
  });
41
41
  }}
42
+ onChangeBlock={onChangeBlock}
42
43
  formData={data}
43
- fieldIndex={data.index}
44
44
  block={block}
45
45
  />
46
46
  )}
@@ -140,7 +140,7 @@ class UsersControlpanel extends Component {
140
140
  (this.props.createRequest.loading && nextProps.createRequest.loaded)
141
141
  ) {
142
142
  this.props.listUsers({
143
- query: this.state.search,
143
+ search: this.state.search,
144
144
  });
145
145
  }
146
146
  if (this.props.createRequest.loading && nextProps.createRequest.loaded) {
@@ -172,7 +172,7 @@ class UsersControlpanel extends Component {
172
172
  onSearch(event) {
173
173
  event.preventDefault();
174
174
  this.props.listUsers({
175
- query: this.state.search,
175
+ search: this.state.search,
176
176
  });
177
177
  }
178
178
 
@@ -57,7 +57,9 @@ const messages = defineMessages({
57
57
  * }
58
58
  * mutated.fieldsets[0].fields.push('extraField');
59
59
  * return mutated;
60
- * }
60
+ * },
61
+ * activeObject: 0, // Current active object drilled down from the schema (if present)
62
+ * setActiveObject: () => {} // The current active object state updater function drilled down from the schema (if present)
61
63
  * },
62
64
  * ```
63
65
  */
@@ -71,7 +73,22 @@ const ObjectListWidget = (props) => {
71
73
  onChange,
72
74
  schemaExtender,
73
75
  } = props;
74
- const [activeObject, setActiveObject] = React.useState(value.length - 1);
76
+ const [localActiveObject, setLocalActiveObject] = React.useState(
77
+ props.activeObject ?? value.length - 1,
78
+ );
79
+
80
+ let activeObject, setActiveObject;
81
+ if (
82
+ (props.activeObject || props.activeObject === 0) &&
83
+ props.setActiveObject
84
+ ) {
85
+ activeObject = props.activeObject;
86
+ setActiveObject = props.setActiveObject;
87
+ } else {
88
+ activeObject = localActiveObject;
89
+ setActiveObject = setLocalActiveObject;
90
+ }
91
+
75
92
  const intl = useIntl();
76
93
 
77
94
  function handleChangeActiveObject(e, blockProps) {
@@ -0,0 +1,9 @@
1
+ export const styleClassNameConverters = {
2
+ default: (name, value, prefix = '') => {
3
+ return value
4
+ ? `has--${prefix}${name}--${(value || '').toString().replace(/^#/, '')}`
5
+ : null;
6
+ },
7
+ noprefix: (name, value) => value,
8
+ bool: (name, value) => (value ? name : ''),
9
+ };
@@ -24,6 +24,7 @@ import { loadables } from './Loadables';
24
24
  import { workflowMapping } from './Workflows';
25
25
 
26
26
  import { contentIcons } from './ContentIcons';
27
+ import { styleClassNameConverters } from './Style';
27
28
  import {
28
29
  controlPanelsIcons,
29
30
  filterControlPanels,
@@ -167,6 +168,7 @@ let config = {
167
168
  addonsInfo: addonsInfo,
168
169
  workflowMapping,
169
170
  errorHandlers: [], // callables for unhandled errors
171
+ styleClassNameConverters,
170
172
  },
171
173
  experimental: {
172
174
  addBlockButton: {
@@ -3,16 +3,7 @@
3
3
  * @module helpers/Blocks
4
4
  */
5
5
 
6
- import {
7
- omit,
8
- without,
9
- endsWith,
10
- find,
11
- isObject,
12
- keys,
13
- toPairs,
14
- merge,
15
- } from 'lodash';
6
+ import { omit, without, endsWith, find, isObject, keys, merge } from 'lodash';
16
7
  import move from 'lodash-move';
17
8
  import { v4 as uuid } from 'uuid';
18
9
  import config from '@plone/volto/registry';
@@ -450,35 +441,43 @@ export function applyBlockDefaults({ data, intl, ...rest }, blocksConfig) {
450
441
  return applySchemaDefaults({ data, schema, intl });
451
442
  }
452
443
 
453
- export const buildStyleClassNamesFromData = (styles) => {
454
- // styles has the form
444
+ /**
445
+ * Converts a name+value style pair (ex: color/red) to a classname,
446
+ * such as "has--color--red"
447
+ *
448
+ * This can be expanded via the style names, by suffixing them with special
449
+ * converters. See config.settings.styleClassNameConverters. Examples:
450
+ *
451
+ * styleToClassName('theme:noprefix', 'primary') returns "primary"
452
+ * styleToClassName('inverted:bool', true) returns 'inverted'
453
+ * styleToClassName('inverted:bool', false) returns ''
454
+ */
455
+ export const styleToClassName = (key, value, prefix = '') => {
456
+ const converters = config.settings.styleClassNameConverters;
457
+ const [name, ...convIds] = key.split(':');
458
+
459
+ return (convIds.length ? convIds : ['default'])
460
+ .map((id) => converters[id])
461
+ .reduce((acc, conv) => conv(acc, value, prefix), name);
462
+ };
463
+
464
+ export const buildStyleClassNamesFromData = (obj = {}, prefix = '') => {
465
+ // styles has the form:
455
466
  // const styles = {
456
- // color: 'red',
457
- // backgroundColor: '#AABBCC',
467
+ // color: 'red',
468
+ // backgroundColor: '#AABBCC',
458
469
  // }
459
470
  // Returns: ['has--color--red', 'has--backgroundColor--AABBCC']
460
- let styleArray = [];
461
- const pairedStyles = toPairs(styles);
462
- pairedStyles.forEach((item) => {
463
- if (isObject(item[1])) {
464
- const flattenedNestedStyles = toPairs(item[1]).map((nested) => [
465
- item[0],
466
- ...nested,
467
- ]);
468
- flattenedNestedStyles.forEach((sub) => styleArray.push(sub));
469
- } else {
470
- styleArray.push(item);
471
- }
472
- });
473
- return styleArray.map((item) => {
474
- const classname = item.map((item) => {
475
- const str_item = item ? item.toString() : '';
476
- return str_item && str_item.startsWith('#')
477
- ? str_item.replace('#', '')
478
- : str_item;
479
- });
480
- return `has--${classname[0]}--${classname[1]}${
481
- classname[2] ? `--${classname[2]}` : ''
482
- }`;
483
- });
471
+
472
+ return Object.entries(obj)
473
+ .reduce(
474
+ (acc, [k, v]) => [
475
+ ...acc,
476
+ ...(isObject(v)
477
+ ? buildStyleClassNamesFromData(v, `${prefix}${k}--`)
478
+ : [styleToClassName(k, v, prefix)]),
479
+ ],
480
+ [],
481
+ )
482
+ .filter((v) => !!v);
484
483
  };
@@ -819,6 +819,7 @@ describe('Blocks', () => {
819
819
  expect(applyBlockDefaults({ data })).toEqual({});
820
820
  });
821
821
  });
822
+
822
823
  describe('buildStyleClassNamesFromData', () => {
823
824
  it('Sets styles classname array according to style values', () => {
824
825
  const styles = {
@@ -830,6 +831,7 @@ describe('Blocks', () => {
830
831
  'has--backgroundColor--AABBCC',
831
832
  ]);
832
833
  });
834
+
833
835
  it('Sets styles classname array according to style values with nested', () => {
834
836
  const styles = {
835
837
  color: 'red',
@@ -846,6 +848,7 @@ describe('Blocks', () => {
846
848
  'has--nested--bar--black',
847
849
  ]);
848
850
  });
851
+
849
852
  it('Sets styles classname array according to style values with nested and colors', () => {
850
853
  const styles = {
851
854
  color: 'red',
@@ -863,6 +866,27 @@ describe('Blocks', () => {
863
866
  ]);
864
867
  });
865
868
 
869
+ it('Supports multiple nested level', () => {
870
+ const styles = {
871
+ color: 'red',
872
+ backgroundColor: '#AABBCC',
873
+ nested: {
874
+ l1: 'white',
875
+ level2: {
876
+ foo: '#fff',
877
+ bar: '#000',
878
+ },
879
+ },
880
+ };
881
+ expect(buildStyleClassNamesFromData(styles)).toEqual([
882
+ 'has--color--red',
883
+ 'has--backgroundColor--AABBCC',
884
+ 'has--nested--l1--white',
885
+ 'has--nested--level2--foo--fff',
886
+ 'has--nested--level2--bar--000',
887
+ ]);
888
+ });
889
+
866
890
  it('Sets styles classname array according to style values with int values', () => {
867
891
  const styles = {
868
892
  color: 'red',
@@ -873,5 +897,45 @@ describe('Blocks', () => {
873
897
  'has--borderRadius--8',
874
898
  ]);
875
899
  });
900
+
901
+ it('Understands noprefix converter for style values', () => {
902
+ const styles = {
903
+ color: 'red',
904
+ 'theme:noprefix': 'primary',
905
+ };
906
+ expect(buildStyleClassNamesFromData(styles)).toEqual([
907
+ 'has--color--red',
908
+ 'primary',
909
+ ]);
910
+ });
911
+
912
+ it('Understands bool converter for trueish value', () => {
913
+ const styles = {
914
+ color: 'red',
915
+ 'inverted:bool': true,
916
+ };
917
+ expect(buildStyleClassNamesFromData(styles)).toEqual([
918
+ 'has--color--red',
919
+ 'inverted',
920
+ ]);
921
+ });
922
+
923
+ it('Understands bool converter for false value', () => {
924
+ const styles = {
925
+ color: 'red',
926
+ 'inverted:bool': false,
927
+ };
928
+ expect(buildStyleClassNamesFromData(styles)).toEqual(['has--color--red']);
929
+ });
930
+
931
+ it('Ugly edge cases', () => {
932
+ const styles = {
933
+ color: undefined,
934
+ nested: {
935
+ l1: {},
936
+ },
937
+ };
938
+ expect(buildStyleClassNamesFromData(styles)).toEqual([]);
939
+ });
876
940
  });
877
941
  });
@@ -20,6 +20,7 @@ import {
20
20
  } from '@plone/volto/config/RichTextEditor/Blocks';
21
21
  import FromHTMLCustomBlockFn from '@plone/volto/config/RichTextEditor/FromHTML';
22
22
  import { contentIcons } from '@plone/volto/config/ContentIcons';
23
+ import { styleClassNameConverters } from '@plone/volto/config/Style';
23
24
 
24
25
  import {
25
26
  controlPanelsIcons,
@@ -74,6 +75,7 @@ config.set('settings', {
74
75
  apiExpanders: [],
75
76
  downloadableObjects: ['File'],
76
77
  viewableInBrowserObjects: [],
78
+ styleClassNameConverters,
77
79
  });
78
80
  config.set('blocks', {
79
81
  blocksConfig: {