@plone/volto 17.0.1 → 17.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.
Files changed (30) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +36 -252
  3. package/addon-registry.js +6 -8
  4. package/cypress/support/commands.js +1 -2
  5. package/cypress/support/reset-fixture.js +2 -4
  6. package/jest-extender-plugin.js +3 -1
  7. package/locales/pt_BR/LC_MESSAGES/volto.po +1 -1
  8. package/locales/pt_BR.json +1 -1
  9. package/package.json +5 -4
  10. package/packages/volto-slate/package.json +1 -1
  11. package/pre-build-transpiling.js +1 -1
  12. package/razzle.config.js +6 -10
  13. package/src/components/manage/Blocks/LeadImage/View.jsx +1 -7
  14. package/src/components/manage/Blocks/LeadImage/View.test.jsx +22 -0
  15. package/src/components/manage/Blocks/LeadImage/utils.js +9 -0
  16. package/src/components/manage/Blocks/Listing/ListingData.jsx +3 -3
  17. package/src/components/manage/Blocks/Listing/ListingData.test.jsx +2 -0
  18. package/src/components/manage/UniversalLink/UniversalLink.jsx +3 -1
  19. package/src/components/theme/Comments/CommentEditModal.stories.jsx +64 -0
  20. package/src/components/theme/EventDetails/EventDetails.stories.jsx +88 -0
  21. package/src/components/theme/Header/Header.stories.jsx +47 -0
  22. package/src/components/theme/Image/Image.jsx +0 -4
  23. package/src/components/theme/Login/Login.jsx +1 -1
  24. package/src/components/theme/Login/Login.stories.jsx +1 -1
  25. package/src/components/theme/Logo/Logo.Multilingual.test.jsx +5 -25
  26. package/src/components/theme/Logo/Logo.jsx +8 -6
  27. package/src/components/theme/Logo/Logo.stories.jsx +25 -1
  28. package/src/components/theme/Logo/Logo.test.jsx +6 -19
  29. package/src/components/theme/SearchWidget/SearchWidget.stories.jsx +36 -0
  30. package/src/config/Blocks.jsx +2 -0
@@ -0,0 +1,88 @@
1
+ import { injectIntl } from 'react-intl';
2
+ import React from 'react';
3
+ import EventDetailsComponent from './EventDetails';
4
+ import { RealStoreWrapper as Wrapper } from '@plone/volto/storybook';
5
+
6
+ const IntlEventDetailsComponent = injectIntl(EventDetailsComponent);
7
+
8
+ function StoryComponent(args) {
9
+ return (
10
+ <Wrapper
11
+ customStore={{
12
+ intl: {
13
+ locale: 'en',
14
+ messages: {},
15
+ },
16
+ }}
17
+ >
18
+ <div id="toolbar" style={{ display: 'none' }} />
19
+ <IntlEventDetailsComponent
20
+ content={{
21
+ '@id': 'http://localhost:8080/Plone/my-page',
22
+ title: 'Hello World!',
23
+ description: 'Hi',
24
+ text: {
25
+ data: '<p>Hello World!</p>',
26
+ },
27
+ ...args,
28
+ }}
29
+ />
30
+ </Wrapper>
31
+ );
32
+ }
33
+
34
+ export const EventDetails = StoryComponent.bind({});
35
+ EventDetails.args = {
36
+ attendees: ['John Doe', 'Mario Rossi'],
37
+ contact_email: 'test@example.com',
38
+ contact_name: 'John Doe',
39
+ contact_phone: '0123456789',
40
+ end: '2019-06-24T15:20:00+00:00',
41
+ event_url: 'https://www.example.com',
42
+ location: 'Volto, Plone',
43
+ open_end: false,
44
+ recurrence: 'RRULE:FREQ=DAILY;INTERVAL=7;COUNT=7',
45
+ start: '2019-06-23T15:20:00+00:00',
46
+ subjects: ['Guillotina', 'Volto'],
47
+ whole_day: false,
48
+ };
49
+ export default {
50
+ title: 'Public components/EventDetails',
51
+ component: EventDetails,
52
+ decorators: [
53
+ (Story) => (
54
+ <div className="ui segment form attached" style={{ width: '400px' }}>
55
+ <Story />
56
+ </div>
57
+ ),
58
+ ],
59
+ argTypes: {
60
+ contact_email: {
61
+ description: 'email address',
62
+ },
63
+ contact_name: {
64
+ description: 'name of the attendee',
65
+ },
66
+ contact_phone: {
67
+ description: 'contact phone of the attendee',
68
+ },
69
+ end: {
70
+ control: { type: 'date' },
71
+ description: 'end date/time of the event',
72
+ },
73
+ event_url: {
74
+ description: 'event url',
75
+ },
76
+ start: {
77
+ control: { type: 'date' },
78
+ context: 'time',
79
+ description: 'start date/time of the event',
80
+ },
81
+ whole_day: {
82
+ description: 'whether the event will be whole day or not',
83
+ },
84
+ subjects: {
85
+ description: 'subjects covered in the event meetings',
86
+ },
87
+ },
88
+ };
@@ -0,0 +1,47 @@
1
+ import { injectIntl } from 'react-intl';
2
+ import React from 'react';
3
+ import HeaderComponent from './Header';
4
+ import { RealStoreWrapper as Wrapper } from '@plone/volto/storybook';
5
+
6
+ const IntlHeaderComponent = injectIntl(HeaderComponent);
7
+
8
+ function StoryComponent(args) {
9
+ return (
10
+ <Wrapper
11
+ customStore={{
12
+ userSession: { ...args },
13
+ intl: {
14
+ locale: 'en',
15
+ messages: {},
16
+ },
17
+ }}
18
+ >
19
+ <div id="toolbar" style={{ display: 'none' }} />
20
+ <IntlHeaderComponent pathname="/blog" />
21
+ </Wrapper>
22
+ );
23
+ }
24
+
25
+ export const Default = StoryComponent.bind({});
26
+ Default.args = {
27
+ token: '',
28
+ };
29
+ export const Auth = StoryComponent.bind({});
30
+ Auth.args = {
31
+ token: '1234567890',
32
+ };
33
+
34
+ export default {
35
+ title: 'Public components/Header',
36
+ component: HeaderComponent,
37
+ decorators: [
38
+ (Story) => (
39
+ <div className="ui segment form attached" style={{ width: '900px' }}>
40
+ <Story />
41
+ </div>
42
+ ),
43
+ ],
44
+ argTypes: {
45
+ token: 'Token for userSession',
46
+ },
47
+ };
@@ -48,10 +48,6 @@ export default function Image({
48
48
  attrs.src = `${baseUrl}${flattenToAppURL(image.download)}`;
49
49
  attrs.width = image.width;
50
50
  attrs.height = image.height;
51
- attrs.style = {
52
- aspectRatio: `${image.width} / ${image.height}`,
53
- ...imageProps.style,
54
- };
55
51
  attrs.className = cx(className, { responsive });
56
52
 
57
53
  if (!isSvg && image.scales && Object.keys(image.scales).length > 0) {
@@ -74,7 +74,7 @@ const Login = (props) => {
74
74
  const error = useSelector((state) => state.userSession.login.error);
75
75
  const loading = useSelector((state) => state.userSession.login.loading);
76
76
  const returnUrl =
77
- qs.parse(props.location.search ?? location.search).return_url ||
77
+ qs.parse(props.location?.search ?? location.search).return_url ||
78
78
  location.pathname.replace(/\/login\/?$/, '').replace(/\/logout\/?$/, '') ||
79
79
  '/';
80
80
  useEffect(() => {
@@ -18,7 +18,7 @@ const StoryComponent = (args) => {
18
18
  return (
19
19
  <Provider store={store}>
20
20
  <Wrapper>
21
- <Login />
21
+ <Login location={{ search: {} }} />
22
22
  </Wrapper>
23
23
  </Provider>
24
24
  );
@@ -24,23 +24,18 @@ describe('Multilingual Logo', () => {
24
24
  data: {
25
25
  id: 'http://localhost:3000/@navroot',
26
26
  navroot: {
27
- '@id': 'http://localhost:3000',
27
+ '@id': 'http://localhost:3000/en',
28
28
  title: 'Plone Site',
29
29
  },
30
30
  },
31
31
  },
32
- router: {
33
- location: {
34
- pathname: '/',
35
- },
36
- },
37
32
  site: {
38
33
  data: {},
39
34
  },
40
35
  });
41
36
  const component = renderer.create(
42
37
  <Provider store={store}>
43
- <MemoryRouter>
38
+ <MemoryRouter initialEntries={[{ pathname: '/' }]}>
44
39
  <Logo />
45
40
  </MemoryRouter>
46
41
  </Provider>,
@@ -64,11 +59,6 @@ describe('Multilingual Logo', () => {
64
59
  },
65
60
  },
66
61
  },
67
- router: {
68
- location: {
69
- pathname: '/en',
70
- },
71
- },
72
62
  site: {
73
63
  data: {
74
64
  'plone.site_title': 'Plone Site',
@@ -77,7 +67,7 @@ describe('Multilingual Logo', () => {
77
67
  });
78
68
  const component = renderer.create(
79
69
  <Provider store={store}>
80
- <MemoryRouter>
70
+ <MemoryRouter initialEntries={[{ pathname: '/en' }]}>
81
71
  <Logo />
82
72
  </MemoryRouter>
83
73
  </Provider>,
@@ -101,11 +91,6 @@ describe('Multilingual Logo', () => {
101
91
  },
102
92
  },
103
93
  },
104
- router: {
105
- location: {
106
- pathname: '/en',
107
- },
108
- },
109
94
  site: {
110
95
  data: {
111
96
  'plone.site_logo':
@@ -115,7 +100,7 @@ describe('Multilingual Logo', () => {
115
100
  });
116
101
  const component = renderer.create(
117
102
  <Provider store={store}>
118
- <MemoryRouter>
103
+ <MemoryRouter initialEntries={[{ pathname: '/en' }]}>
119
104
  <Logo />
120
105
  </MemoryRouter>
121
106
  </Provider>,
@@ -139,11 +124,6 @@ describe('Multilingual Logo', () => {
139
124
  },
140
125
  },
141
126
  },
142
- router: {
143
- location: {
144
- pathname: '/en/my/path',
145
- },
146
- },
147
127
  site: {
148
128
  data: {
149
129
  'plone.site_logo':
@@ -153,7 +133,7 @@ describe('Multilingual Logo', () => {
153
133
  });
154
134
  const component = renderer.create(
155
135
  <Provider store={store}>
156
- <MemoryRouter>
136
+ <MemoryRouter initialEntries={[{ pathname: '/en/my/path' }]}>
157
137
  <Logo />
158
138
  </MemoryRouter>
159
139
  </Provider>,
@@ -7,12 +7,12 @@ import { Image } from 'semantic-ui-react';
7
7
  import { ConditionalLink } from '@plone/volto/components';
8
8
  import LogoImage from '@plone/volto/components/theme/Logo/Logo.svg';
9
9
  import { useSelector, useDispatch } from 'react-redux';
10
+ import { useLocation } from 'react-router-dom';
10
11
  import { getNavroot } from '@plone/volto/actions';
11
12
  import {
12
13
  flattenToAppURL,
13
14
  hasApiExpander,
14
15
  getBaseUrl,
15
- toPublicURL,
16
16
  } from '@plone/volto/helpers';
17
17
 
18
18
  /**
@@ -22,7 +22,7 @@ import {
22
22
  * @returns {string} Markup of the component.
23
23
  */
24
24
  const Logo = () => {
25
- const pathname = useSelector((state) => state.router.location.pathname);
25
+ const pathname = useLocation().pathname;
26
26
  const site = useSelector((state) => state.site.data);
27
27
  const navroot = useSelector((state) => state.navroot.data);
28
28
  const dispatch = useDispatch();
@@ -33,14 +33,16 @@ const Logo = () => {
33
33
  }
34
34
  }, [dispatch, pathname]);
35
35
 
36
- // remove trailing slash
37
- const currentURL = toPublicURL(pathname).replace(/\/$/, '');
36
+ const navRootPath = flattenToAppURL(navroot?.navroot?.['@id']) || '/';
37
+ const currentURLIsNavRoot = pathname !== navRootPath;
38
38
 
39
39
  return (
40
40
  <ConditionalLink
41
- href={navroot?.navroot?.['@id']}
41
+ href={navRootPath}
42
42
  title={navroot?.navroot?.title}
43
- condition={currentURL !== navroot?.navroot?.['@id']}
43
+ // In case that the content returns 404, there is no information about the portal
44
+ // then render the link anyways to get out of the Unauthorized page
45
+ condition={!navroot || currentURLIsNavRoot}
44
46
  >
45
47
  <Image
46
48
  src={
@@ -4,7 +4,31 @@ import Wrapper from '@plone/volto/storybook';
4
4
 
5
5
  const StoryComponent = (args) => {
6
6
  return (
7
- <Wrapper>
7
+ <Wrapper
8
+ customStore={{
9
+ intl: {
10
+ locale: 'en',
11
+ messages: {},
12
+ },
13
+ navroot: {
14
+ data: {
15
+ id: 'http://localhost:3000/@navroot',
16
+ navroot: {
17
+ '@id': 'http://localhost:3000',
18
+ title: 'Plone Site',
19
+ },
20
+ },
21
+ },
22
+ router: {
23
+ location: {
24
+ pathname: '/',
25
+ },
26
+ },
27
+ site: {
28
+ data: {},
29
+ },
30
+ }}
31
+ >
8
32
  <Logo />
9
33
  </Wrapper>
10
34
  );
@@ -30,18 +30,13 @@ describe('Logo', () => {
30
30
  },
31
31
  },
32
32
  },
33
- router: {
34
- location: {
35
- pathname: '/',
36
- },
37
- },
38
33
  site: {
39
34
  data: {},
40
35
  },
41
36
  });
42
37
  const component = renderer.create(
43
38
  <Provider store={store}>
44
- <MemoryRouter>
39
+ <MemoryRouter initialEntries={[{ pathname: '/' }]}>
45
40
  <Logo />
46
41
  </MemoryRouter>
47
42
  </Provider>,
@@ -64,11 +59,6 @@ describe('Logo', () => {
64
59
  },
65
60
  },
66
61
  },
67
- router: {
68
- location: {
69
- pathname: '/',
70
- },
71
- },
72
62
  site: {
73
63
  data: {
74
64
  'plone.site_logo':
@@ -78,7 +68,7 @@ describe('Logo', () => {
78
68
  });
79
69
  const component = renderer.create(
80
70
  <Provider store={store}>
81
- <MemoryRouter>
71
+ <MemoryRouter initialEntries={[{ pathname: '/' }]}>
82
72
  <Logo />
83
73
  </MemoryRouter>
84
74
  </Provider>,
@@ -86,6 +76,7 @@ describe('Logo', () => {
86
76
  const json = component.toJSON();
87
77
  expect(json).toMatchSnapshot();
88
78
  });
79
+
89
80
  it('renders a logo component with default config in a non-root url', () => {
90
81
  const store = mockStore({
91
82
  intl: {
@@ -115,7 +106,7 @@ describe('Logo', () => {
115
106
  });
116
107
  const component = renderer.create(
117
108
  <Provider store={store}>
118
- <MemoryRouter>
109
+ <MemoryRouter initialEntries={[{ pathname: '/some-page' }]}>
119
110
  <Logo />
120
111
  </MemoryRouter>
121
112
  </Provider>,
@@ -123,6 +114,7 @@ describe('Logo', () => {
123
114
  const json = component.toJSON();
124
115
  expect(json).toMatchSnapshot();
125
116
  });
117
+
126
118
  it('renders a logo component with a custom logo in a non-root url', () => {
127
119
  const store = mockStore({
128
120
  intl: {
@@ -138,11 +130,6 @@ describe('Logo', () => {
138
130
  },
139
131
  },
140
132
  },
141
- router: {
142
- location: {
143
- pathname: '/some-page',
144
- },
145
- },
146
133
  site: {
147
134
  data: {
148
135
  'plone.site_logo':
@@ -152,7 +139,7 @@ describe('Logo', () => {
152
139
  });
153
140
  const component = renderer.create(
154
141
  <Provider store={store}>
155
- <MemoryRouter>
142
+ <MemoryRouter initialEntries={[{ pathname: '/some-page' }]}>
156
143
  <Logo />
157
144
  </MemoryRouter>
158
145
  </Provider>,
@@ -0,0 +1,36 @@
1
+ import { injectIntl } from 'react-intl';
2
+ import React from 'react';
3
+ import SearchWidgetComponent from './SearchWidget';
4
+ import { RealStoreWrapper as Wrapper } from '@plone/volto/storybook';
5
+
6
+ const IntlSearchWidgetComponent = injectIntl(SearchWidgetComponent);
7
+
8
+ function StoryComponent(args) {
9
+ return (
10
+ <Wrapper
11
+ customStore={{
12
+ intl: {
13
+ locale: 'en',
14
+ messages: {},
15
+ },
16
+ }}
17
+ >
18
+ <div id="toolbar" style={{ display: 'none' }} />
19
+ <IntlSearchWidgetComponent {...args} pathname="/blog" />
20
+ </Wrapper>
21
+ );
22
+ }
23
+
24
+ export const SearchWidget = StoryComponent.bind({});
25
+
26
+ export default {
27
+ title: 'Public components/SearchWidget',
28
+ component: SearchWidget,
29
+ decorators: [
30
+ (Story) => (
31
+ <div className="ui segment form attached" style={{ width: '400px' }}>
32
+ <Story />
33
+ </div>
34
+ ),
35
+ ],
36
+ };
@@ -76,6 +76,7 @@ import {
76
76
  } from '@plone/volto/components/manage/Blocks/Search/components';
77
77
  import getListingBlockAsyncData from '@plone/volto/components/manage/Blocks/Listing/getAsyncData';
78
78
  import { getImageBlockSizes } from '@plone/volto/components/manage/Blocks/Image/utils';
79
+ import { getLeadImageBlockSizes } from '@plone/volto/components/manage/Blocks/LeadImage/utils';
79
80
 
80
81
  // block sidebar schemas (not the Dexterity Layout block settings schemas)
81
82
  import HeroImageLeftBlockSchema from '@plone/volto/components/manage/Blocks/HeroImageLeft/schema';
@@ -272,6 +273,7 @@ const blocksConfig = {
272
273
  restricted: ({ properties }) => !properties.hasOwnProperty('image'),
273
274
  mostUsed: false,
274
275
  sidebarTab: 1,
276
+ getSizes: getLeadImageBlockSizes,
275
277
  },
276
278
  listing: {
277
279
  id: 'listing',