@plone/volto 18.0.0-alpha.4 → 18.0.0-alpha.6

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 (47) hide show
  1. package/.eslintignore +1 -0
  2. package/.release-it.json +1 -0
  3. package/CHANGELOG.md +55 -0
  4. package/package.json +8 -5
  5. package/src/components/manage/Add/Add.jsx +6 -0
  6. package/src/components/manage/BlockChooser/BlockChooser.jsx +3 -1
  7. package/src/components/manage/BlockChooser/BlockChooserButton.jsx +5 -0
  8. package/src/components/manage/Blocks/Block/BlocksForm.jsx +4 -0
  9. package/src/components/manage/Blocks/Block/DefaultEdit.jsx +3 -1
  10. package/src/components/manage/Blocks/Block/EditBlockWrapper.jsx +10 -2
  11. package/src/components/manage/Blocks/Block/Settings.jsx +10 -1
  12. package/src/components/manage/Blocks/Block/StyleWrapper.jsx +7 -1
  13. package/src/components/manage/Blocks/Container/Data.jsx +4 -1
  14. package/src/components/manage/Blocks/Grid/View.jsx +2 -1
  15. package/src/components/manage/Blocks/HeroImageLeft/Data.jsx +3 -1
  16. package/src/components/manage/Blocks/Image/ImageSidebar.jsx +4 -1
  17. package/src/components/manage/Blocks/Image/View.jsx +2 -1
  18. package/src/components/manage/Blocks/Listing/ListingData.jsx +4 -1
  19. package/src/components/manage/Blocks/Listing/View.jsx +2 -1
  20. package/src/components/manage/Blocks/Maps/MapsSidebar.jsx +3 -1
  21. package/src/components/manage/Blocks/Search/SearchBlockEdit.jsx +4 -0
  22. package/src/components/manage/Blocks/Search/hocs/withSearch.jsx +46 -4
  23. package/src/components/manage/Blocks/Teaser/Data.jsx +4 -1
  24. package/src/components/manage/Blocks/Teaser/DefaultBody.jsx +2 -2
  25. package/src/components/manage/Blocks/ToC/Edit.jsx +2 -0
  26. package/src/components/manage/Blocks/Video/VideoSidebar.jsx +3 -1
  27. package/src/components/manage/Edit/Edit.jsx +1 -0
  28. package/src/components/manage/Form/Form.jsx +9 -1
  29. package/src/components/manage/Sidebar/SidebarPopup.jsx +1 -1
  30. package/src/components/theme/Comments/CommentEditModal.jsx +1 -0
  31. package/src/components/theme/Image/Image.jsx +4 -2
  32. package/src/components/theme/Image/Image.test.jsx +32 -0
  33. package/src/components/theme/Login/Login.jsx +12 -2
  34. package/src/components/theme/Navigation/Navigation.jsx +34 -32
  35. package/src/components/theme/PreviewImage/PreviewImage.jsx +7 -2
  36. package/src/components/theme/Sitemap/Sitemap.jsx +4 -4
  37. package/src/components/theme/Sitemap/Sitemap.test.jsx +52 -0
  38. package/src/components/theme/Unauthorized/Unauthorized.jsx +12 -11
  39. package/src/helpers/Blocks/Blocks.js +67 -3
  40. package/src/helpers/Blocks/Blocks.test.js +57 -0
  41. package/src/helpers/Extensions/withBlockSchemaEnhancer.js +20 -9
  42. package/src/helpers/Url/Url.js +5 -4
  43. package/src/helpers/Url/Url.test.js +52 -0
  44. package/src/helpers/index.js +1 -0
  45. package/theme/themes/pastanaga/extras/blocks.less +2 -0
  46. package/theme/themes/pastanaga/extras/sidebar.less +2 -1
  47. package/types/helpers/Extensions/withBlockSchemaEnhancer.d.ts +3 -1
@@ -544,7 +544,13 @@ class Form extends Component {
544
544
  */
545
545
  render() {
546
546
  const { settings } = config;
547
- const { schema: originalSchema, onCancel, onSubmit } = this.props;
547
+ const {
548
+ schema: originalSchema,
549
+ onCancel,
550
+ onSubmit,
551
+ navRoot,
552
+ type,
553
+ } = this.props;
548
554
  const { formData } = this.state;
549
555
  const schema = this.removeBlocksLayoutFields(originalSchema);
550
556
  const Container =
@@ -593,6 +599,8 @@ class Form extends Component {
593
599
  onChangeField={this.onChangeField}
594
600
  onSelectBlock={this.onSelectBlock}
595
601
  properties={formData}
602
+ navRoot={navRoot}
603
+ type={type}
596
604
  pathname={this.props.pathname}
597
605
  selectedBlock={this.state.selected}
598
606
  multiSelected={this.state.multiSelected}
@@ -9,7 +9,7 @@ const DEFAULT_TIMEOUT = 500;
9
9
  const SidebarPopup = (props) => {
10
10
  const { children, open, onClose, overlay } = props;
11
11
 
12
- const asideElement = React.createRef();
12
+ const asideElement = React.useRef();
13
13
 
14
14
  const handleClickOutside = (e) => {
15
15
  if (asideElement && doesNodeContainClick(asideElement.current, e)) return;
@@ -60,6 +60,7 @@ const CommentEditModal = (props) => {
60
60
  text: {
61
61
  title: intl.formatMessage(messages.comment),
62
62
  type: 'string',
63
+ widget: 'textarea',
63
64
  description: '',
64
65
  },
65
66
  },
@@ -45,8 +45,10 @@ export default function Image({
45
45
  if (!image) return null;
46
46
 
47
47
  const isSvg = image['content-type'] === 'image/svg+xml';
48
+ // In case `base_path` is present (`preview_image_link`) use it as base path
49
+ const basePath = image.base_path || item['@id'];
48
50
 
49
- attrs.src = `${flattenToAppURL(item['@id'])}/${image.download}`;
51
+ attrs.src = `${flattenToAppURL(basePath)}/${image.download}`;
50
52
  attrs.width = image.width;
51
53
  attrs.height = image.height;
52
54
  attrs.className = cx(className, { responsive });
@@ -61,7 +63,7 @@ export default function Image({
61
63
  attrs.srcSet = sortedScales
62
64
  .map(
63
65
  (scale) =>
64
- `${flattenToAppURL(item['@id'])}/${scale.download} ${scale.width}w`,
66
+ `${flattenToAppURL(basePath)}/${scale.download} ${scale.width}w`,
65
67
  )
66
68
  .join(', ');
67
69
  }
@@ -113,6 +113,38 @@ test('renders an image component from a catalog brain', () => {
113
113
  expect(json).toMatchSnapshot();
114
114
  });
115
115
 
116
+ test('renders an image component from a catalog brain using `preview_image_link`', () => {
117
+ const component = renderer.create(
118
+ <Image
119
+ item={{
120
+ '@id': 'http://localhost:3000/blog/blog-post',
121
+ image_field: 'preview_image_link',
122
+ image_scales: {
123
+ preview_image_link: [
124
+ {
125
+ base_path: '/image.png',
126
+ download: '@@images/image.png',
127
+ width: 400,
128
+ height: 400,
129
+ scales: {
130
+ preview: {
131
+ download: '@@images/image-400.png',
132
+ width: 400,
133
+ height: 400,
134
+ },
135
+ },
136
+ },
137
+ ],
138
+ },
139
+ }}
140
+ imageField="preview_image_link"
141
+ alt="alt text"
142
+ />,
143
+ );
144
+ const json = component.toJSON();
145
+ expect(json).toMatchSnapshot();
146
+ });
147
+
116
148
  test('renders an image component from a string src', () => {
117
149
  const component = renderer.create(
118
150
  <Image
@@ -77,8 +77,9 @@ const Login = (props) => {
77
77
  qs.parse(props.location?.search ?? location.search).return_url ||
78
78
  location.pathname.replace(/\/login\/?$/, '').replace(/\/logout\/?$/, '') ||
79
79
  '/';
80
+
80
81
  useEffect(() => {
81
- if (token && !props.isLogout) {
82
+ if (token && !(props.isLogout || location?.state?.isLogout)) {
82
83
  history.push(returnUrl || '/');
83
84
  if (toast.isActive('loggedOut')) {
84
85
  toast.dismiss('loggedOut');
@@ -108,7 +109,16 @@ const Login = (props) => {
108
109
  dispatch(resetLoginRequest());
109
110
  }
110
111
  };
111
- }, [dispatch, token, error, intl, history, returnUrl, props.isLogout]);
112
+ }, [
113
+ dispatch,
114
+ token,
115
+ error,
116
+ intl,
117
+ history,
118
+ returnUrl,
119
+ props.isLogout,
120
+ location?.state?.isLogout,
121
+ ]);
112
122
 
113
123
  const onLogin = (event) => {
114
124
  dispatch(
@@ -48,40 +48,42 @@ const Navigation = (props) => {
48
48
  }
49
49
  setisMobileMenuOpen(false);
50
50
  };
51
-
52
51
  return (
53
52
  <nav className="navigation" id="navigation" aria-label="Site">
54
- <div className="hamburger-wrapper mobile tablet only">
55
- <button
56
- className={cx('hamburger hamburger--spin', {
57
- 'is-active': isMobileMenuOpen,
58
- })}
59
- aria-label={
60
- isMobileMenuOpen
61
- ? intl.formatMessage(messages.closeMobileMenu, {
62
- type: type,
63
- })
64
- : intl.formatMessage(messages.openMobileMenu, {
65
- type: type,
66
- })
67
- }
68
- title={
69
- isMobileMenuOpen
70
- ? intl.formatMessage(messages.closeMobileMenu, {
71
- type: type,
72
- })
73
- : intl.formatMessage(messages.openMobileMenu, {
74
- type: type,
75
- })
76
- }
77
- type="button"
78
- onClick={toggleMobileMenu}
79
- >
80
- <span className="hamburger-box">
81
- <span className="hamburger-inner" />
82
- </span>
83
- </button>
84
- </div>
53
+ {items?.length ? (
54
+ <div className="hamburger-wrapper mobile tablet only">
55
+ <button
56
+ className={cx('hamburger hamburger--spin', {
57
+ 'is-active': isMobileMenuOpen,
58
+ })}
59
+ aria-label={
60
+ isMobileMenuOpen
61
+ ? intl.formatMessage(messages.closeMobileMenu, {
62
+ type: type,
63
+ })
64
+ : intl.formatMessage(messages.openMobileMenu, {
65
+ type: type,
66
+ })
67
+ }
68
+ title={
69
+ isMobileMenuOpen
70
+ ? intl.formatMessage(messages.closeMobileMenu, {
71
+ type: type,
72
+ })
73
+ : intl.formatMessage(messages.openMobileMenu, {
74
+ type: type,
75
+ })
76
+ }
77
+ type="button"
78
+ onClick={toggleMobileMenu}
79
+ >
80
+ <span className="hamburger-box">
81
+ <span className="hamburger-inner" />
82
+ </span>
83
+ </button>
84
+ </div>
85
+ ) : null}
86
+
85
87
  <Menu
86
88
  stackable
87
89
  pointing
@@ -11,12 +11,17 @@ function PreviewImage({ item, alt, image_field, showDefault = true, ...rest }) {
11
11
  const Image = config.getComponent({ name: 'Image' }).component;
12
12
 
13
13
  const image = (
14
- <Image item={item} image_field={image_field} alt={alt} {...rest} />
14
+ <Image
15
+ item={item}
16
+ image_field={image_field || item.image_field}
17
+ alt={alt}
18
+ {...rest}
19
+ />
15
20
  );
16
21
 
17
22
  if (!image && !showDefault) return null;
18
23
 
19
- if (image) {
24
+ if (image_field || item?.image_field) {
20
25
  return image;
21
26
  } else {
22
27
  return (
@@ -33,16 +33,16 @@ export function getSitemapPath(pathname = '', lang) {
33
33
  function Sitemap(props) {
34
34
  const {
35
35
  location: { pathname },
36
- language,
36
+ lang,
37
37
  getNavigation,
38
38
  } = props;
39
39
 
40
40
  useEffect(() => {
41
41
  const { settings } = config;
42
- const lang = settings.isMultilingual ? `${toBackendLang(language)}` : null;
43
- const path = getSitemapPath(pathname, lang);
42
+ const language = settings.isMultilingual ? `${toBackendLang(lang)}` : null;
43
+ const path = getSitemapPath(pathname, language);
44
44
  getNavigation(path, 4);
45
- }, [pathname, language, getNavigation]);
45
+ }, [pathname, lang, getNavigation]);
46
46
 
47
47
  const renderItems = (items) => {
48
48
  return (
@@ -3,6 +3,7 @@ import renderer from 'react-test-renderer';
3
3
  import configureStore from 'redux-mock-store';
4
4
  import { Provider } from 'react-intl-redux';
5
5
  import { MemoryRouter } from 'react-router-dom';
6
+ import config from '@plone/volto/registry';
6
7
 
7
8
  import { __test__ as Sitemap, getSitemapPath } from './Sitemap';
8
9
 
@@ -55,6 +56,57 @@ describe('Sitemap', () => {
55
56
  });
56
57
  });
57
58
 
59
+ describe('Sitemap in a multilingual site', () => {
60
+ beforeEach(() => {
61
+ config.settings.isMultilingual = true;
62
+ config.settings.supportedLanguages = ['en', 'es'];
63
+ });
64
+ it('renders a sitemap component', () => {
65
+ const store = mockStore({
66
+ navigation: {
67
+ url: 'http://localhost:8080/Plone/en/@navigation',
68
+ items: [
69
+ {
70
+ url: 'http://localhost:8080/Plone/en/page-1',
71
+ description: '',
72
+ items: [
73
+ {
74
+ url: 'http://localhost:8080/Plone/en/page-1/page-1-2',
75
+ description: '',
76
+ title: 'Page 1-2',
77
+ },
78
+ {
79
+ url: 'http://localhost:8080/Plone/en/page-1/page-1-3',
80
+ description: '',
81
+ title: 'Page 1-3',
82
+ },
83
+ ],
84
+ title: 'Page 1-3',
85
+ },
86
+ {
87
+ url: 'http://localhost:8080/Plone/en/page-2',
88
+ description: '',
89
+ title: 'Page 2',
90
+ },
91
+ ],
92
+ },
93
+ intl: {
94
+ locale: 'en',
95
+ messages: {},
96
+ },
97
+ });
98
+ const component = renderer.create(
99
+ <Provider store={store}>
100
+ <MemoryRouter>
101
+ <Sitemap location={{ pathname: '/en/' }} />
102
+ </MemoryRouter>
103
+ </Provider>,
104
+ );
105
+ const json = component.toJSON();
106
+ expect(json).toMatchSnapshot();
107
+ });
108
+ });
109
+
58
110
  describe('getSitemapPath', () => {
59
111
  it('accepts empty path', () => {
60
112
  expect(getSitemapPath('', null)).toBe('');
@@ -1,8 +1,3 @@
1
- /**
2
- * @module components/theme/Unauthorized/Unauthorized
3
- */
4
-
5
- import React from 'react';
6
1
  import { FormattedMessage } from 'react-intl';
7
2
  import { Link } from 'react-router-dom';
8
3
  import { Container } from 'semantic-ui-react';
@@ -11,11 +6,6 @@ import { useLocation } from 'react-router-dom';
11
6
  import { withServerErrorCode } from '@plone/volto/helpers/Utils/Utils';
12
7
  import { getBaseUrl } from '@plone/volto/helpers';
13
8
 
14
- /**
15
- * unauthorized function.
16
- * @function Unauthorized
17
- * @returns {string} Markup of the unauthorized page.
18
- */
19
9
  const Unauthorized = () => {
20
10
  const error_message = useSelector((state) => state.apierror?.message);
21
11
  let location = useLocation();
@@ -32,7 +22,18 @@ const Unauthorized = () => {
32
22
  defaultMessage="You are trying to access a protected resource, please {login} first."
33
23
  values={{
34
24
  login: (
35
- <Link to={`${getBaseUrl(location.pathname)}/login`}>
25
+ <Link
26
+ to={{
27
+ pathname: `${getBaseUrl(location.pathname)}/login`,
28
+ state: {
29
+ // This is needed to cover the use case of being logged in in
30
+ // another backend (eg. in development), having a token for
31
+ // localhost and try to use it, the login route has to know that
32
+ // it's the same as it comes from a logout
33
+ isLogout: true,
34
+ },
35
+ }}
36
+ >
36
37
  <FormattedMessage id="log in" defaultMessage="log in" />
37
38
  </Link>
38
39
  ),
@@ -517,7 +517,10 @@ export function applySchemaDefaults({ data = {}, schema, intl }) {
517
517
  * @param {Object} params An object with data, intl and anything else
518
518
  * @return {Object} Derived data, with the defaults extracted from the schema
519
519
  */
520
- export function applyBlockDefaults({ data, intl, ...rest }, blocksConfig) {
520
+ export function applyBlockDefaults(
521
+ { data, intl, navRoot, contentType, ...rest },
522
+ blocksConfig,
523
+ ) {
521
524
  // We pay attention to not break on a missing (invalid) block.
522
525
  const block_type = data?.['@type'];
523
526
  const { blockSchema } =
@@ -528,7 +531,13 @@ export function applyBlockDefaults({ data, intl, ...rest }, blocksConfig) {
528
531
  typeof blockSchema === 'function'
529
532
  ? blockSchema({ data, intl, ...rest })
530
533
  : blockSchema;
531
- schema = applySchemaEnhancer({ schema, formData: data, intl });
534
+ schema = applySchemaEnhancer({
535
+ schema,
536
+ formData: data,
537
+ intl,
538
+ navRoot,
539
+ contentType,
540
+ });
532
541
 
533
542
  return applySchemaDefaults({ data, schema, intl });
534
543
  }
@@ -554,7 +563,7 @@ export const styleToClassName = (key, value, prefix = '') => {
554
563
  };
555
564
 
556
565
  export const buildStyleClassNamesFromData = (obj = {}, prefix = '') => {
557
- // styles has the form:
566
+ // style wrapper object has the form:
558
567
  // const styles = {
559
568
  // color: 'red',
560
569
  // backgroundColor: '#AABBCC',
@@ -562,6 +571,7 @@ export const buildStyleClassNamesFromData = (obj = {}, prefix = '') => {
562
571
  // Returns: ['has--color--red', 'has--backgroundColor--AABBCC']
563
572
 
564
573
  return Object.entries(obj)
574
+ .filter(([k, v]) => !k.startsWith('--'))
565
575
  .reduce(
566
576
  (acc, [k, v]) => [
567
577
  ...acc,
@@ -593,6 +603,60 @@ export const buildStyleClassNamesExtenders = ({
593
603
  );
594
604
  };
595
605
 
606
+ /**
607
+ * Converts a name+value style pair (ex: color/red) to a pair of [k, v],
608
+ * such as ["color", "red"] so it can be converted back to an object.
609
+ * For now, only covering the 'CSSProperty' use case.
610
+ */
611
+ export const styleDataToStyleObject = (key, value, prefix = '') => {
612
+ if (prefix) {
613
+ return [`--${prefix}${key.replace('--', '')}`, value];
614
+ } else {
615
+ return [key, value];
616
+ }
617
+ };
618
+
619
+ /**
620
+ * Generate styles object from data
621
+ *
622
+ * @function buildStyleObjectFromData
623
+ * @param {Object} obj A style wrapper object data
624
+ * @param {string} prefix The prefix (could be dragged from a recursive call, initially empty)
625
+ * @return {Object} The style object ready to be passed as prop
626
+ */
627
+ export const buildStyleObjectFromData = (obj = {}, prefix = '') => {
628
+ // style wrapper object has the form:
629
+ // const styles = {
630
+ // color: 'red',
631
+ // '--background-color': '#AABBCC',
632
+ // }
633
+ // Returns: {'--background-color: '#AABBCC'}
634
+
635
+ return Object.fromEntries(
636
+ Object.entries(obj)
637
+ .filter(([k, v]) => k.startsWith('--') || isObject(v))
638
+ .reduce(
639
+ (acc, [k, v]) => [
640
+ ...acc,
641
+ // Kept for easy debugging
642
+ // ...(() => {
643
+ // if (isObject(v)) {
644
+ // return Object.entries(
645
+ // buildStyleObjectFromData(v, `${prefix}${k}--`),
646
+ // );
647
+ // }
648
+ // return [styleDataToStyleObject(k, v, prefix)];
649
+ // })(),
650
+ ...(isObject(v)
651
+ ? Object.entries(buildStyleObjectFromData(v, `${prefix}${k}--`))
652
+ : [styleDataToStyleObject(k, v, prefix)]),
653
+ ],
654
+ [],
655
+ )
656
+ .filter((v) => !!v),
657
+ );
658
+ };
659
+
596
660
  /**
597
661
  * Return previous/next blocks given the content object and the current block id
598
662
  *
@@ -18,6 +18,7 @@ import {
18
18
  applySchemaDefaults,
19
19
  buildStyleClassNamesFromData,
20
20
  buildStyleClassNamesExtenders,
21
+ buildStyleObjectFromData,
21
22
  getPreviousNextBlock,
22
23
  blocksFormGenerator,
23
24
  findBlocks,
@@ -1066,6 +1067,62 @@ describe('Blocks', () => {
1066
1067
  };
1067
1068
  expect(buildStyleClassNamesFromData(styles)).toEqual([]);
1068
1069
  });
1070
+
1071
+ it('It does not output any className for style converter values', () => {
1072
+ const styles = {
1073
+ color: 'red',
1074
+ '--background-color': '#FFF',
1075
+ };
1076
+ expect(buildStyleClassNamesFromData(styles)).toEqual(['has--color--red']);
1077
+ });
1078
+
1079
+ it.skip('It does not output any className for unknown converter values', () => {
1080
+ const styles = {
1081
+ color: 'red',
1082
+ 'backgroundColor:style': '#FFF',
1083
+ };
1084
+ expect(buildStyleClassNamesFromData(styles)).toEqual(['has--color--red']);
1085
+ });
1086
+ });
1087
+
1088
+ describe('buildStyleObjectFromData', () => {
1089
+ it('Understands style converter for style values, no styles found', () => {
1090
+ const styles = {
1091
+ color: 'red',
1092
+ backgroundColor: '#FFF',
1093
+ };
1094
+ expect(buildStyleObjectFromData(styles)).toEqual({});
1095
+ });
1096
+
1097
+ it('Understands style converter for style values', () => {
1098
+ const styles = {
1099
+ color: 'red',
1100
+ '--background-color': '#FFF',
1101
+ };
1102
+ expect(buildStyleObjectFromData(styles)).toEqual({
1103
+ '--background-color': '#FFF',
1104
+ });
1105
+ });
1106
+
1107
+ it('Supports multiple nested levels', () => {
1108
+ const styles = {
1109
+ '--color': 'red',
1110
+ backgroundColor: '#AABBCC',
1111
+ nested: {
1112
+ l1: 'white',
1113
+ '--foo': 'white',
1114
+ level2: {
1115
+ '--foo': '#fff',
1116
+ bar: '#000',
1117
+ },
1118
+ },
1119
+ };
1120
+ expect(buildStyleObjectFromData(styles)).toEqual({
1121
+ '--color': 'red',
1122
+ '--nested--foo': 'white',
1123
+ '--nested--level2--foo': '#fff',
1124
+ });
1125
+ });
1069
1126
  });
1070
1127
 
1071
1128
  describe('getPreviousNextBlock', () => {
@@ -199,6 +199,8 @@ export const applySchemaEnhancer = ({
199
199
  formData,
200
200
  intl,
201
201
  blocksConfig = config.blocks.blocksConfig,
202
+ navRoot,
203
+ contentType,
202
204
  }) => {
203
205
  let schema, schemaEnhancer;
204
206
 
@@ -215,6 +217,8 @@ export const applySchemaEnhancer = ({
215
217
  schema: cloneDeepSchema(originalSchema),
216
218
  formData,
217
219
  intl,
220
+ navRoot,
221
+ contentType,
218
222
  });
219
223
  return schema || originalSchema;
220
224
  }
@@ -230,12 +234,15 @@ export const applySchemaEnhancer = ({
230
234
  schema: cloneDeepSchema(originalSchema),
231
235
  formData,
232
236
  intl,
237
+ navRoot,
238
+ contentType,
233
239
  })
234
240
  : cloneDeepSchema(originalSchema);
235
241
 
236
242
  // Finalize the schema with a schemaEnhancer in the block config;
237
243
  schemaEnhancer = blocksConfig?.[blockType]?.schemaEnhancer;
238
- if (schemaEnhancer) schema = schemaEnhancer({ schema, formData, intl });
244
+ if (schemaEnhancer)
245
+ schema = schemaEnhancer({ schema, formData, intl, navRoot, contentType });
239
246
 
240
247
  return schema || originalSchema;
241
248
  };
@@ -248,7 +255,7 @@ export const applySchemaEnhancer = ({
248
255
  * - adds the variation selection input (as a choice widget)
249
256
  */
250
257
  export const withVariationSchemaEnhancer = (FormComponent) => (props) => {
251
- const { formData, schema: originalSchema } = props;
258
+ const { formData, schema: originalSchema, navRoot, contentType } = props;
252
259
  const intl = useIntl();
253
260
 
254
261
  const blocksConfig = getBlocksConfig(props);
@@ -256,15 +263,10 @@ export const withVariationSchemaEnhancer = (FormComponent) => (props) => {
256
263
  const blockType = formData['@type'];
257
264
  const variations = blocksConfig[blockType]?.variations || [];
258
265
 
259
- let schema = applySchemaEnhancer({
260
- schema: originalSchema,
261
- formData,
262
- intl,
263
- blocksConfig,
264
- });
266
+ let schema = cloneDeepSchema(originalSchema);
265
267
 
266
268
  if (variations.length > 1) {
267
- addExtensionFieldToSchema({
269
+ schema = addExtensionFieldToSchema({
268
270
  schema,
269
271
  name: 'variation',
270
272
  items: variations,
@@ -274,6 +276,15 @@ export const withVariationSchemaEnhancer = (FormComponent) => (props) => {
274
276
  });
275
277
  }
276
278
 
279
+ schema = applySchemaEnhancer({
280
+ schema,
281
+ formData,
282
+ intl,
283
+ blocksConfig,
284
+ navRoot,
285
+ contentType,
286
+ });
287
+
277
288
  return <FormComponent {...props} schema={schema} />;
278
289
  };
279
290
 
@@ -366,19 +366,20 @@ export const URLUtils = {
366
366
  * @returns {object} New object with the flattened scale URLs
367
367
  */
368
368
  export function flattenScales(path, image) {
369
- function removeObjectIdFromURL(path, scale) {
370
- return scale.replace(`${path}/`, '');
369
+ function removeObjectIdFromURL(basePath, scale) {
370
+ return scale.replace(`${basePath}/`, '');
371
371
  }
372
372
  if (!image) return;
373
373
 
374
+ const basePath = image.base_path || path;
374
375
  const imageInfo = {
375
376
  ...image,
376
- download: flattenToAppURL(removeObjectIdFromURL(path, image.download)),
377
+ download: flattenToAppURL(removeObjectIdFromURL(basePath, image.download)),
377
378
  };
378
379
 
379
380
  Object.keys(imageInfo.scales).forEach((key) => {
380
381
  imageInfo.scales[key].download = flattenToAppURL(
381
- removeObjectIdFromURL(path, image.scales[key].download),
382
+ removeObjectIdFromURL(basePath, image.scales[key].download),
382
383
  );
383
384
  });
384
385