@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.
- package/CHANGELOG.md +33 -0
- package/README.md +36 -252
- package/addon-registry.js +6 -8
- package/cypress/support/commands.js +1 -2
- package/cypress/support/reset-fixture.js +2 -4
- package/jest-extender-plugin.js +3 -1
- package/locales/pt_BR/LC_MESSAGES/volto.po +1 -1
- package/locales/pt_BR.json +1 -1
- package/package.json +5 -4
- package/packages/volto-slate/package.json +1 -1
- package/pre-build-transpiling.js +1 -1
- package/razzle.config.js +6 -10
- package/src/components/manage/Blocks/LeadImage/View.jsx +1 -7
- package/src/components/manage/Blocks/LeadImage/View.test.jsx +22 -0
- package/src/components/manage/Blocks/LeadImage/utils.js +9 -0
- package/src/components/manage/Blocks/Listing/ListingData.jsx +3 -3
- package/src/components/manage/Blocks/Listing/ListingData.test.jsx +2 -0
- package/src/components/manage/UniversalLink/UniversalLink.jsx +3 -1
- package/src/components/theme/Comments/CommentEditModal.stories.jsx +64 -0
- package/src/components/theme/EventDetails/EventDetails.stories.jsx +88 -0
- package/src/components/theme/Header/Header.stories.jsx +47 -0
- package/src/components/theme/Image/Image.jsx +0 -4
- package/src/components/theme/Login/Login.jsx +1 -1
- package/src/components/theme/Login/Login.stories.jsx +1 -1
- package/src/components/theme/Logo/Logo.Multilingual.test.jsx +5 -25
- package/src/components/theme/Logo/Logo.jsx +8 -6
- package/src/components/theme/Logo/Logo.stories.jsx +25 -1
- package/src/components/theme/Logo/Logo.test.jsx +6 -19
- package/src/components/theme/SearchWidget/SearchWidget.stories.jsx +36 -0
- 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
|
|
77
|
+
qs.parse(props.location?.search ?? location.search).return_url ||
|
|
78
78
|
location.pathname.replace(/\/login\/?$/, '').replace(/\/logout\/?$/, '') ||
|
|
79
79
|
'/';
|
|
80
80
|
useEffect(() => {
|
|
@@ -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 =
|
|
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
|
-
|
|
37
|
-
const
|
|
36
|
+
const navRootPath = flattenToAppURL(navroot?.navroot?.['@id']) || '/';
|
|
37
|
+
const currentURLIsNavRoot = pathname !== navRootPath;
|
|
38
38
|
|
|
39
39
|
return (
|
|
40
40
|
<ConditionalLink
|
|
41
|
-
href={
|
|
41
|
+
href={navRootPath}
|
|
42
42
|
title={navroot?.navroot?.title}
|
|
43
|
-
|
|
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
|
+
};
|
package/src/config/Blocks.jsx
CHANGED
|
@@ -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',
|