@plone/volto 17.6.1 → 17.8.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 +37 -0
- package/addon-registry.js +34 -1
- package/package.json +1 -1
- package/packages/volto-slate/news/5347.bugfix +1 -0
- package/packages/volto-slate/news/5517.feature +1 -0
- package/packages/volto-slate/package.json +1 -1
- package/packages/volto-slate/src/blocks/Text/DefaultTextBlockEditor.jsx +4 -0
- package/packages/volto-slate/src/editor/extensions/insertData.js +3 -1
- package/packages/volto-slate/src/editor/extensions/normalizeExternalData.js +1 -1
- package/packages/volto-slate/src/utils/blocks.js +6 -2
- package/src/components/manage/Add/Add.jsx +6 -0
- package/src/components/manage/BlockChooser/BlockChooser.jsx +3 -1
- package/src/components/manage/BlockChooser/BlockChooserButton.jsx +5 -0
- package/src/components/manage/Blocks/Block/BlocksForm.jsx +4 -0
- package/src/components/manage/Blocks/Block/DefaultEdit.jsx +3 -1
- package/src/components/manage/Blocks/Block/EditBlockWrapper.jsx +10 -2
- package/src/components/manage/Blocks/Block/Settings.jsx +10 -1
- package/src/components/manage/Blocks/Block/StyleWrapper.jsx +7 -1
- package/src/components/manage/Blocks/Container/Data.jsx +4 -1
- package/src/components/manage/Blocks/Grid/View.jsx +2 -1
- package/src/components/manage/Blocks/HeroImageLeft/Data.jsx +3 -1
- package/src/components/manage/Blocks/Image/ImageSidebar.jsx +4 -1
- package/src/components/manage/Blocks/Image/View.jsx +2 -1
- package/src/components/manage/Blocks/Listing/ListingData.jsx +4 -1
- package/src/components/manage/Blocks/Listing/View.jsx +2 -1
- package/src/components/manage/Blocks/Maps/MapsSidebar.jsx +3 -1
- package/src/components/manage/Blocks/Search/SearchBlockEdit.jsx +4 -0
- package/src/components/manage/Blocks/Search/hocs/withSearch.jsx +46 -4
- package/src/components/manage/Blocks/Teaser/Data.jsx +4 -1
- package/src/components/manage/Blocks/Teaser/DefaultBody.jsx +2 -2
- package/src/components/manage/Blocks/ToC/Edit.jsx +2 -0
- package/src/components/manage/Blocks/Video/Body.jsx +52 -22
- package/src/components/manage/Blocks/Video/Body.test.jsx +167 -0
- package/src/components/manage/Blocks/Video/VideoSidebar.jsx +3 -1
- package/src/components/manage/Controlpanels/Users/UserGroupMembershipControlPanel.jsx +2 -2
- package/src/components/manage/Edit/Edit.jsx +1 -0
- package/src/components/manage/Form/Form.jsx +14 -3
- package/src/components/manage/Sidebar/SidebarPopup.jsx +1 -1
- package/src/components/manage/Widgets/RecurrenceWidget/EndField.jsx +7 -1
- package/src/components/manage/Widgets/RecurrenceWidget/RecurrenceWidget.jsx +79 -31
- package/src/components/theme/Image/Image.jsx +4 -2
- package/src/components/theme/Image/Image.test.jsx +32 -0
- package/src/components/theme/Login/Login.jsx +12 -2
- package/src/components/theme/Navigation/Navigation.jsx +34 -32
- package/src/components/theme/PreviewImage/PreviewImage.jsx +7 -2
- package/src/components/theme/Sitemap/Sitemap.jsx +4 -4
- package/src/components/theme/Sitemap/Sitemap.test.jsx +52 -0
- package/src/components/theme/Unauthorized/Unauthorized.jsx +12 -11
- package/src/helpers/Blocks/Blocks.js +67 -3
- package/src/helpers/Blocks/Blocks.test.js +57 -0
- package/src/helpers/Extensions/withBlockSchemaEnhancer.js +20 -9
- package/src/helpers/Url/Url.js +5 -4
- package/src/helpers/Url/Url.test.js +52 -0
- package/src/helpers/index.js +1 -0
- package/types/components/manage/Blocks/Image/View.d.ts +2 -1
- package/types/components/manage/Blocks/Video/Body.d.ts +5 -0
- package/types/components/manage/Blocks/Video/Body.test.d.ts +1 -0
- package/types/helpers/Blocks/Blocks.d.ts +3 -1
- package/types/helpers/Extensions/withBlockSchemaEnhancer.d.ts +3 -1
- package/apps/plone/node_modules/.bin/acorn +0 -17
- package/apps/plone/node_modules/.bin/addon +0 -17
- package/apps/plone/node_modules/.bin/autoprefixer +0 -17
- package/apps/plone/node_modules/.bin/browserslist +0 -17
- package/apps/plone/node_modules/.bin/build-storybook +0 -17
- package/apps/plone/node_modules/.bin/changelogupdater +0 -17
- package/apps/plone/node_modules/.bin/eslint +0 -17
- package/apps/plone/node_modules/.bin/eslint-config-prettier +0 -17
- package/apps/plone/node_modules/.bin/i18n +0 -17
- package/apps/plone/node_modules/.bin/jest +0 -17
- package/apps/plone/node_modules/.bin/lessc +0 -17
- package/apps/plone/node_modules/.bin/missdev +0 -17
- package/apps/plone/node_modules/.bin/prettier +0 -17
- package/apps/plone/node_modules/.bin/razzle +0 -17
- package/apps/plone/node_modules/.bin/server-test +0 -17
- package/apps/plone/node_modules/.bin/start-server-and-test +0 -17
- package/apps/plone/node_modules/.bin/start-storybook +0 -17
- package/apps/plone/node_modules/.bin/start-test +0 -17
- package/apps/plone/node_modules/.bin/storybook-server +0 -17
- package/apps/plone/node_modules/.bin/stylelint +0 -17
- package/apps/plone/node_modules/.bin/tlds +0 -17
- package/apps/plone/node_modules/.bin/ts-jest +0 -17
- package/apps/plone/node_modules/.bin/tsc +0 -17
- package/apps/plone/node_modules/.bin/tsserver +0 -17
- package/apps/plone/node_modules/.bin/uuid +0 -17
- package/apps/plone/node_modules/.bin/webpack +0 -17
- package/apps/plone/node_modules/.bin/webpack-dev-server +0 -17
- package/apps/plone/src/addons/volto-volto-project/node_modules/.bin/acorn +0 -17
- package/apps/plone/src/addons/volto-volto-project/node_modules/.bin/addon +0 -17
- package/apps/plone/src/addons/volto-volto-project/node_modules/.bin/browserslist +0 -17
- package/apps/plone/src/addons/volto-volto-project/node_modules/.bin/changelogupdater +0 -17
- package/apps/plone/src/addons/volto-volto-project/node_modules/.bin/eslint +0 -17
- package/apps/plone/src/addons/volto-volto-project/node_modules/.bin/eslint-config-prettier +0 -17
- package/apps/plone/src/addons/volto-volto-project/node_modules/.bin/i18n +0 -17
- package/apps/plone/src/addons/volto-volto-project/node_modules/.bin/prettier +0 -17
- package/apps/plone/src/addons/volto-volto-project/node_modules/.bin/release-it +0 -17
- package/apps/plone/src/addons/volto-volto-project/node_modules/.bin/stylelint +0 -17
- package/apps/plone/src/addons/volto-volto-project/node_modules/.bin/tsc +0 -17
- package/apps/plone/src/addons/volto-volto-project/node_modules/.bin/tsserver +0 -17
- package/packages/volto-slate/build/messages/src/blocks/Table/TableBlockEdit.json +0 -90
- package/packages/volto-slate/build/messages/src/blocks/Text/DefaultTextBlockEditor.json +0 -6
- package/packages/volto-slate/build/messages/src/blocks/Text/DetachedTextBlockEditor.json +0 -6
- package/packages/volto-slate/build/messages/src/blocks/Text/SlashMenu.json +0 -6
- package/packages/volto-slate/build/messages/src/editor/plugins/AdvancedLink/index.json +0 -10
- package/packages/volto-slate/build/messages/src/editor/plugins/Link/index.json +0 -10
- package/packages/volto-slate/build/messages/src/editor/plugins/Table/index.json +0 -30
- package/packages/volto-slate/build/messages/src/elementEditor/messages.json +0 -10
- package/packages/volto-slate/build/messages/src/widgets/HtmlSlateWidget.json +0 -6
- package/packages/volto-slate/build/messages/src/widgets/RichTextWidgetView.json +0 -6
|
@@ -18,7 +18,7 @@ const messages = defineMessages({
|
|
|
18
18
|
});
|
|
19
19
|
|
|
20
20
|
const TeaserDefaultTemplate = (props) => {
|
|
21
|
-
const { className, data, isEditMode } = props;
|
|
21
|
+
const { className, data, isEditMode, style } = props;
|
|
22
22
|
const intl = useIntl();
|
|
23
23
|
const href = data.href?.[0];
|
|
24
24
|
const image = data.preview_image?.[0];
|
|
@@ -27,7 +27,7 @@ const TeaserDefaultTemplate = (props) => {
|
|
|
27
27
|
const { openExternalLinkInNewTab } = config.settings;
|
|
28
28
|
|
|
29
29
|
return (
|
|
30
|
-
<div className={cx('block teaser', className)}>
|
|
30
|
+
<div className={cx('block teaser', className)} style={style}>
|
|
31
31
|
<>
|
|
32
32
|
{!href && isEditMode && (
|
|
33
33
|
<Message>
|
|
@@ -5,39 +5,68 @@ import { Embed, Message } from 'semantic-ui-react';
|
|
|
5
5
|
import cx from 'classnames';
|
|
6
6
|
import { isInternalURL, flattenToAppURL } from '@plone/volto/helpers';
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
? isInternalURL(data.preview_image)
|
|
11
|
-
? `${flattenToAppURL(data.preview_image)}/@@images/image`
|
|
12
|
-
: data.preview_image
|
|
13
|
-
: null;
|
|
14
|
-
|
|
8
|
+
//Extracting videoID, listID and thumbnailURL from the video URL
|
|
9
|
+
const getVideoIDAndPlaceholder = (url) => {
|
|
15
10
|
let videoID = null;
|
|
16
11
|
let listID = null;
|
|
12
|
+
let thumbnailURL = null;
|
|
17
13
|
|
|
18
|
-
if (
|
|
19
|
-
if (
|
|
20
|
-
if (
|
|
21
|
-
const matches =
|
|
14
|
+
if (url) {
|
|
15
|
+
if (url.match('youtu')) {
|
|
16
|
+
if (url.match('list')) {
|
|
17
|
+
const matches = url.match(/^.*\?list=(.*)|^.*&list=(.*)$/);
|
|
22
18
|
listID = matches[1] || matches[2];
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
19
|
+
|
|
20
|
+
let thumbnailID = null;
|
|
21
|
+
if (url.match(/\?v=(.*)&list/)) {
|
|
22
|
+
thumbnailID = url.match(/^.*\?v=(.*)&list(.*)/)[1];
|
|
23
|
+
}
|
|
24
|
+
if (url.match(/\?v=(.*)\?list/)) {
|
|
25
|
+
thumbnailID = url.match(/^.*\?v=(.*)\?list(.*)/)[1];
|
|
26
|
+
}
|
|
27
|
+
thumbnailURL =
|
|
28
|
+
'https://img.youtube.com/vi/' + thumbnailID + '/sddefault.jpg';
|
|
29
|
+
} else if (url.match('live')) {
|
|
30
|
+
videoID = url.match(/^.*\/live\/(.*)/)[1];
|
|
31
|
+
} else if (url.match(/\.be\//)) {
|
|
32
|
+
videoID = url.match(/^.*\.be\/(.*)/)[1];
|
|
33
|
+
} else if (url.match(/\?v=/)) {
|
|
34
|
+
videoID = url.match(/^.*\?v=(.*)$/)[1];
|
|
27
35
|
}
|
|
28
36
|
|
|
29
|
-
if (
|
|
37
|
+
if (videoID) {
|
|
38
|
+
let thumbnailID = videoID;
|
|
39
|
+
if (videoID.match(/\?si=/)) {
|
|
40
|
+
thumbnailID = videoID.match(/(.*)\?si=(.*)/)[1];
|
|
41
|
+
}
|
|
30
42
|
//load video preview image from youtube
|
|
31
|
-
|
|
32
|
-
'https://img.youtube.com/vi/' +
|
|
43
|
+
thumbnailURL =
|
|
44
|
+
'https://img.youtube.com/vi/' + thumbnailID + '/sddefault.jpg';
|
|
33
45
|
}
|
|
34
|
-
} else if (
|
|
35
|
-
videoID =
|
|
36
|
-
if (
|
|
37
|
-
|
|
46
|
+
} else if (url.match('vimeo')) {
|
|
47
|
+
videoID = url.match(/^.*\.com\/(.*)/)[1];
|
|
48
|
+
if (videoID) {
|
|
49
|
+
let thumbnailID = videoID;
|
|
50
|
+
if (videoID.match(/\?si=/)) {
|
|
51
|
+
thumbnailID = videoID.match(/(.*)\?si=(.*)/)[1];
|
|
52
|
+
}
|
|
53
|
+
thumbnailURL = 'https://vumbnail.com/' + thumbnailID + '.jpg';
|
|
38
54
|
}
|
|
39
55
|
}
|
|
40
56
|
}
|
|
57
|
+
return { videoID, listID, thumbnailURL };
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const Body = ({ data, isEditMode }) => {
|
|
61
|
+
let placeholder = data.preview_image
|
|
62
|
+
? isInternalURL(data.preview_image)
|
|
63
|
+
? `${flattenToAppURL(data.preview_image)}/@@images/image`
|
|
64
|
+
: data.preview_image
|
|
65
|
+
: null;
|
|
66
|
+
|
|
67
|
+
const { videoID, listID, thumbnailURL } = getVideoIDAndPlaceholder(data.url);
|
|
68
|
+
|
|
69
|
+
placeholder = !placeholder ? thumbnailURL : placeholder;
|
|
41
70
|
|
|
42
71
|
const ref = React.createRef();
|
|
43
72
|
const onKeyDown = (e) => {
|
|
@@ -130,3 +159,4 @@ Body.propTypes = {
|
|
|
130
159
|
};
|
|
131
160
|
|
|
132
161
|
export default Body;
|
|
162
|
+
export { getVideoIDAndPlaceholder };
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import renderer from 'react-test-renderer';
|
|
3
|
+
import configureStore from 'redux-mock-store';
|
|
4
|
+
import { Provider } from 'react-intl-redux';
|
|
5
|
+
import Body from './Body';
|
|
6
|
+
import { getVideoIDAndPlaceholder } from './Body';
|
|
7
|
+
import config from '@plone/volto/registry';
|
|
8
|
+
|
|
9
|
+
config.blocks.blocksConfig = {
|
|
10
|
+
video: {
|
|
11
|
+
id: 'video',
|
|
12
|
+
title: 'Video',
|
|
13
|
+
group: 'media',
|
|
14
|
+
extensions: {},
|
|
15
|
+
variations: [],
|
|
16
|
+
restricted: false,
|
|
17
|
+
mostUsed: true,
|
|
18
|
+
sidebarTab: 1,
|
|
19
|
+
security: {
|
|
20
|
+
addPermission: [],
|
|
21
|
+
view: [],
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const mockStore = configureStore();
|
|
27
|
+
|
|
28
|
+
test('renders a youtube video component with "list" in its url', () => {
|
|
29
|
+
const url =
|
|
30
|
+
'https://www.youtube.com/watch?v=KwRSRRyuk-Q&list=PLGN9BI-OAQkQmEqf6O8jeyoFY1b2hD1uL&index=1';
|
|
31
|
+
const videoDetails = getVideoIDAndPlaceholder(url);
|
|
32
|
+
expect(videoDetails).toEqual({
|
|
33
|
+
videoID: null,
|
|
34
|
+
listID: 'PLGN9BI-OAQkQmEqf6O8jeyoFY1b2hD1uL&index=1',
|
|
35
|
+
thumbnailURL: 'https://img.youtube.com/vi/KwRSRRyuk-Q/sddefault.jpg',
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('extracts video details from a youtube video with "/live/" in its url', () => {
|
|
40
|
+
const url = 'https://www.youtube.com/live/ISdHvS6Ck3k?si=COeVakmC1lI6jQy3';
|
|
41
|
+
const videoDetails = getVideoIDAndPlaceholder(url);
|
|
42
|
+
expect(videoDetails).toEqual({
|
|
43
|
+
videoID: 'ISdHvS6Ck3k?si=COeVakmC1lI6jQy3',
|
|
44
|
+
listID: null,
|
|
45
|
+
thumbnailURL: 'https://img.youtube.com/vi/ISdHvS6Ck3k/sddefault.jpg',
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('extracts video details from a youtube video with ".be/" in its url', () => {
|
|
50
|
+
const url = 'https://youtu.be/P9j-xYdWT28?si=zZ2putStJbPBLCdt';
|
|
51
|
+
const videoDetails = getVideoIDAndPlaceholder(url);
|
|
52
|
+
expect(videoDetails).toEqual({
|
|
53
|
+
videoID: 'P9j-xYdWT28?si=zZ2putStJbPBLCdt',
|
|
54
|
+
listID: null,
|
|
55
|
+
thumbnailURL: 'https://img.youtube.com/vi/P9j-xYdWT28/sddefault.jpg',
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('extracts video details from a youtube video with "?v=" in its url', () => {
|
|
60
|
+
const url = 'https://www.youtube.com/watch?v=KUd6e105u_I';
|
|
61
|
+
const videoDetails = getVideoIDAndPlaceholder(url);
|
|
62
|
+
expect(videoDetails).toEqual({
|
|
63
|
+
videoID: 'KUd6e105u_I',
|
|
64
|
+
listID: null,
|
|
65
|
+
thumbnailURL: 'https://img.youtube.com/vi/KUd6e105u_I/sddefault.jpg',
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('extracts video details from a vimeo video url', () => {
|
|
70
|
+
const url = 'https://vimeo.com/639449679';
|
|
71
|
+
const videoDetails = getVideoIDAndPlaceholder(url);
|
|
72
|
+
expect(videoDetails).toEqual({
|
|
73
|
+
videoID: '639449679',
|
|
74
|
+
listID: null,
|
|
75
|
+
thumbnailURL: 'https://vumbnail.com/639449679.jpg',
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('renders a youtube video body component', () => {
|
|
80
|
+
const store = mockStore({
|
|
81
|
+
intl: {
|
|
82
|
+
locale: 'en',
|
|
83
|
+
messages: {},
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const component = renderer.create(
|
|
88
|
+
<Provider store={store}>
|
|
89
|
+
<Body
|
|
90
|
+
data={{
|
|
91
|
+
'@type': 'video',
|
|
92
|
+
url: 'https://www.youtube.com/watch?v=KwRSRRyuk-Q&list=PLGN9BI-OAQkQmEqf6O8jeyoFY1b2hD1uL&index=1',
|
|
93
|
+
}}
|
|
94
|
+
/>
|
|
95
|
+
</Provider>,
|
|
96
|
+
);
|
|
97
|
+
const json = component.toJSON();
|
|
98
|
+
expect(json).toMatchSnapshot();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test('renders a youtube video body component in edit mode', () => {
|
|
102
|
+
const store = mockStore({
|
|
103
|
+
intl: {
|
|
104
|
+
locale: 'en',
|
|
105
|
+
messages: {},
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const component = renderer.create(
|
|
110
|
+
<Provider store={store}>
|
|
111
|
+
<Body
|
|
112
|
+
data={{
|
|
113
|
+
'@type': 'video',
|
|
114
|
+
url: 'https://www.youtube.com/watch?v=KwRSRRyuk-Q&list=PLGN9BI-OAQkQmEqf6O8jeyoFY1b2hD1uL&index=1',
|
|
115
|
+
}}
|
|
116
|
+
isEditMode={true}
|
|
117
|
+
/>
|
|
118
|
+
</Provider>,
|
|
119
|
+
);
|
|
120
|
+
const json = component.toJSON();
|
|
121
|
+
expect(json).toMatchSnapshot();
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test('renders invalid video body component with invalid URL', () => {
|
|
125
|
+
const store = mockStore({
|
|
126
|
+
intl: {
|
|
127
|
+
locale: 'en',
|
|
128
|
+
messages: {},
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const component = renderer.create(
|
|
133
|
+
<Provider store={store}>
|
|
134
|
+
<Body
|
|
135
|
+
data={{
|
|
136
|
+
'@type': 'video',
|
|
137
|
+
url: 'https://www.google.com',
|
|
138
|
+
}}
|
|
139
|
+
/>
|
|
140
|
+
</Provider>,
|
|
141
|
+
);
|
|
142
|
+
const json = component.toJSON();
|
|
143
|
+
expect(json).toMatchSnapshot();
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test('renders a error message for invalid video URL in edit mode', () => {
|
|
147
|
+
const store = mockStore({
|
|
148
|
+
intl: {
|
|
149
|
+
locale: 'en',
|
|
150
|
+
messages: {},
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
const component = renderer.create(
|
|
155
|
+
<Provider store={store}>
|
|
156
|
+
<Body
|
|
157
|
+
data={{
|
|
158
|
+
'@type': 'video',
|
|
159
|
+
url: 'https://www.google.com',
|
|
160
|
+
}}
|
|
161
|
+
isEditMode={true}
|
|
162
|
+
/>
|
|
163
|
+
</Provider>,
|
|
164
|
+
);
|
|
165
|
+
const json = component.toJSON();
|
|
166
|
+
expect(json).toMatchSnapshot();
|
|
167
|
+
});
|
|
@@ -18,7 +18,7 @@ const messages = defineMessages({
|
|
|
18
18
|
});
|
|
19
19
|
|
|
20
20
|
const VideoSidebar = (props) => {
|
|
21
|
-
const { data, block, onChangeBlock } = props;
|
|
21
|
+
const { data, block, onChangeBlock, navRoot, contentType } = props;
|
|
22
22
|
const intl = useIntl();
|
|
23
23
|
const schema = VideoBlockSchema({ ...props, intl });
|
|
24
24
|
|
|
@@ -42,6 +42,8 @@ const VideoSidebar = (props) => {
|
|
|
42
42
|
onChangeBlock={onChangeBlock}
|
|
43
43
|
formData={data}
|
|
44
44
|
block={block}
|
|
45
|
+
navRoot={navRoot}
|
|
46
|
+
contentType={contentType}
|
|
45
47
|
/>
|
|
46
48
|
)}
|
|
47
49
|
</>
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* TODO Enrich with features of user control panel. Then replace user control panel.
|
|
4
4
|
*/
|
|
5
5
|
import React, { useEffect } from 'react';
|
|
6
|
-
import { find
|
|
6
|
+
import { find } from 'lodash';
|
|
7
7
|
import { Portal } from 'react-portal';
|
|
8
8
|
import { useHistory } from 'react-router';
|
|
9
9
|
import { Link, useLocation } from 'react-router-dom';
|
|
@@ -38,7 +38,7 @@ const UserGroupMembershipPanel = () => {
|
|
|
38
38
|
(state) => state.controlpanels.systeminformation,
|
|
39
39
|
);
|
|
40
40
|
const can_use_group_membership_panel = systeminformation
|
|
41
|
-
?
|
|
41
|
+
? parseFloat(systeminformation?.plone_restapi_version.slice(0, 4)) >= 8.24
|
|
42
42
|
: false;
|
|
43
43
|
const actions = useSelector((state) => state.actions?.actions ?? {});
|
|
44
44
|
const ploneSetupAction = find(actions.user, {
|
|
@@ -286,6 +286,7 @@ class Edit extends Component {
|
|
|
286
286
|
<Form
|
|
287
287
|
isEditForm
|
|
288
288
|
ref={this.form}
|
|
289
|
+
navRoot={this.props.content?.['@components']?.navroot?.navroot || {}}
|
|
289
290
|
schema={this.props.schema}
|
|
290
291
|
type={this.props.content?.['@type']}
|
|
291
292
|
formData={this.props.content}
|
|
@@ -148,7 +148,10 @@ class Form extends Component {
|
|
|
148
148
|
...formData,
|
|
149
149
|
};
|
|
150
150
|
}
|
|
151
|
-
|
|
151
|
+
|
|
152
|
+
// We initialize the formData snapshot in here, before the initial data checks
|
|
153
|
+
const initialFormData = cloneDeep(formData);
|
|
154
|
+
|
|
152
155
|
// Adding fallback in case the fields are empty, so we are sure that the edit form
|
|
153
156
|
// shows at least the default blocks
|
|
154
157
|
if (
|
|
@@ -200,7 +203,7 @@ class Form extends Component {
|
|
|
200
203
|
|
|
201
204
|
this.state = {
|
|
202
205
|
formData,
|
|
203
|
-
initialFormData
|
|
206
|
+
initialFormData,
|
|
204
207
|
errors: {},
|
|
205
208
|
selected: selectedBlock,
|
|
206
209
|
multiSelected: [],
|
|
@@ -541,7 +544,13 @@ class Form extends Component {
|
|
|
541
544
|
*/
|
|
542
545
|
render() {
|
|
543
546
|
const { settings } = config;
|
|
544
|
-
const {
|
|
547
|
+
const {
|
|
548
|
+
schema: originalSchema,
|
|
549
|
+
onCancel,
|
|
550
|
+
onSubmit,
|
|
551
|
+
navRoot,
|
|
552
|
+
type,
|
|
553
|
+
} = this.props;
|
|
545
554
|
const { formData } = this.state;
|
|
546
555
|
const schema = this.removeBlocksLayoutFields(originalSchema);
|
|
547
556
|
const Container =
|
|
@@ -590,6 +599,8 @@ class Form extends Component {
|
|
|
590
599
|
onChangeField={this.onChangeField}
|
|
591
600
|
onSelectBlock={this.onSelectBlock}
|
|
592
601
|
properties={formData}
|
|
602
|
+
navRoot={navRoot}
|
|
603
|
+
type={type}
|
|
593
604
|
pathname={this.props.pathname}
|
|
594
605
|
selectedBlock={this.state.selected}
|
|
595
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.
|
|
12
|
+
const asideElement = React.useRef();
|
|
13
13
|
|
|
14
14
|
const handleClickOutside = (e) => {
|
|
15
15
|
if (asideElement && doesNodeContainClick(asideElement.current, e)) return;
|
|
@@ -81,7 +81,13 @@ const EndField = ({ value, count, until, onChange, intl }) => {
|
|
|
81
81
|
id="until"
|
|
82
82
|
title={intl.formatMessage(messages.recurrenceEndsUntil)}
|
|
83
83
|
dateOnly={true}
|
|
84
|
-
value={
|
|
84
|
+
value={
|
|
85
|
+
until
|
|
86
|
+
? typeof until === 'string'
|
|
87
|
+
? until
|
|
88
|
+
: until?.toISOString()
|
|
89
|
+
: ''
|
|
90
|
+
}
|
|
85
91
|
resettable={false}
|
|
86
92
|
onChange={(id, value) => {
|
|
87
93
|
onChange(id, value === '' ? undefined : value);
|
|
@@ -36,7 +36,6 @@ import {
|
|
|
36
36
|
FREQUENCES,
|
|
37
37
|
WEEKLY_DAYS,
|
|
38
38
|
MONDAYFRIDAY_DAYS,
|
|
39
|
-
toISOString,
|
|
40
39
|
rrulei18n,
|
|
41
40
|
} from './Utils';
|
|
42
41
|
|
|
@@ -218,26 +217,41 @@ class RecurrenceWidget extends Component {
|
|
|
218
217
|
|
|
219
218
|
componentDidUpdate(prevProps) {
|
|
220
219
|
if (this.props.value) {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
this.
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
220
|
+
const changedStart =
|
|
221
|
+
prevProps.formData?.start !== this.props.formData?.start;
|
|
222
|
+
const changedEnd = prevProps.formData?.end !== this.props.formData?.end;
|
|
223
|
+
|
|
224
|
+
if (changedStart || changedEnd) {
|
|
225
|
+
let start = this.getUTCDate(this.props.formData?.start).toDate();
|
|
226
|
+
// let end = this.getUTCDate(this.props.formData?.end).toDate();
|
|
227
|
+
|
|
228
|
+
let changeFormValues = {};
|
|
229
|
+
if (changedEnd) {
|
|
230
|
+
changeFormValues.until = this.getUTCDate(
|
|
231
|
+
this.props.formData?.end,
|
|
232
|
+
).toDate();
|
|
233
|
+
}
|
|
234
|
+
this.setState(
|
|
235
|
+
(prevState) => {
|
|
236
|
+
let rruleSet = prevState.rruleSet;
|
|
237
|
+
|
|
238
|
+
rruleSet = this.updateRruleSet(
|
|
239
|
+
rruleSet,
|
|
240
|
+
{ ...prevState.formValues, ...changeFormValues },
|
|
241
|
+
'dtstart',
|
|
242
|
+
start,
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
return {
|
|
246
|
+
...prevState,
|
|
247
|
+
rruleSet,
|
|
248
|
+
};
|
|
249
|
+
},
|
|
250
|
+
() => {
|
|
251
|
+
//then, after set state, set recurrence rrule value
|
|
252
|
+
this.saveRrule();
|
|
253
|
+
},
|
|
254
|
+
);
|
|
241
255
|
}
|
|
242
256
|
}
|
|
243
257
|
}
|
|
@@ -249,7 +263,7 @@ class RecurrenceWidget extends Component {
|
|
|
249
263
|
setRecurrenceStartEnd = () => {
|
|
250
264
|
const start = this.props.formData?.start;
|
|
251
265
|
|
|
252
|
-
|
|
266
|
+
const _start = new Date(start); //The date is already in utc from plone, so this is not necessary: this.getUTCDate(start).startOf('day').toDate();
|
|
253
267
|
|
|
254
268
|
this.setState((prevState) => {
|
|
255
269
|
let rruleSet = prevState.rruleSet;
|
|
@@ -338,7 +352,7 @@ class RecurrenceWidget extends Component {
|
|
|
338
352
|
case 'until':
|
|
339
353
|
if (value != null) {
|
|
340
354
|
formValues['recurrenceEnds'] = option;
|
|
341
|
-
formValues[option] =
|
|
355
|
+
formValues[option] = value;
|
|
342
356
|
}
|
|
343
357
|
break;
|
|
344
358
|
case 'byweekday':
|
|
@@ -421,7 +435,24 @@ class RecurrenceWidget extends Component {
|
|
|
421
435
|
}
|
|
422
436
|
break;
|
|
423
437
|
case 'until':
|
|
424
|
-
|
|
438
|
+
let mDate = null;
|
|
439
|
+
if (value) {
|
|
440
|
+
mDate = this.moment(new Date(value));
|
|
441
|
+
if (typeof value === 'string') {
|
|
442
|
+
mDate = this.moment(new Date(value));
|
|
443
|
+
} else {
|
|
444
|
+
//object-->Date()
|
|
445
|
+
mDate = this.moment(value);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
if (this.props.formData.end) {
|
|
449
|
+
//set time from formData.end
|
|
450
|
+
const mEnd = this.moment(new Date(this.props.formData.end));
|
|
451
|
+
mDate.set('hour', mEnd.get('hour'));
|
|
452
|
+
mDate.set('minute', mEnd.get('minute'));
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
value = value ? mDate.toDate() : null;
|
|
425
456
|
break;
|
|
426
457
|
default:
|
|
427
458
|
break;
|
|
@@ -446,7 +477,7 @@ class RecurrenceWidget extends Component {
|
|
|
446
477
|
? value
|
|
447
478
|
: rruleSet.dtstart()
|
|
448
479
|
? rruleSet.dtstart()
|
|
449
|
-
:
|
|
480
|
+
: new Date();
|
|
450
481
|
var exdates =
|
|
451
482
|
field === 'exdates' ? value : Object.assign([], rruleSet.exdates());
|
|
452
483
|
|
|
@@ -470,12 +501,12 @@ class RecurrenceWidget extends Component {
|
|
|
470
501
|
getDefaultUntil = (freq) => {
|
|
471
502
|
const moment = this.moment;
|
|
472
503
|
var end = this.props.formData?.end
|
|
473
|
-
?
|
|
504
|
+
? moment(new Date(this.props.formData.end))
|
|
474
505
|
: null;
|
|
475
|
-
var tomorrow =
|
|
476
|
-
var nextWeek =
|
|
477
|
-
var nextMonth =
|
|
478
|
-
var nextYear =
|
|
506
|
+
var tomorrow = moment().add(1, 'days');
|
|
507
|
+
var nextWeek = moment().add(7, 'days');
|
|
508
|
+
var nextMonth = moment().add(1, 'months');
|
|
509
|
+
var nextYear = moment().add(1, 'years');
|
|
479
510
|
|
|
480
511
|
var until = end;
|
|
481
512
|
switch (freq) {
|
|
@@ -500,6 +531,19 @@ class RecurrenceWidget extends Component {
|
|
|
500
531
|
default:
|
|
501
532
|
break;
|
|
502
533
|
}
|
|
534
|
+
if (this.props.formData.end) {
|
|
535
|
+
//set default end time
|
|
536
|
+
until.set('hour', end.get('hour'));
|
|
537
|
+
until.set('minute', end.get('minute'));
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
until = new Date(
|
|
541
|
+
until.get('year'),
|
|
542
|
+
until.get('month'),
|
|
543
|
+
until.get('date'),
|
|
544
|
+
until.get('hour'),
|
|
545
|
+
until.get('minute'),
|
|
546
|
+
);
|
|
503
547
|
|
|
504
548
|
return until;
|
|
505
549
|
};
|
|
@@ -715,9 +759,13 @@ class RecurrenceWidget extends Component {
|
|
|
715
759
|
}
|
|
716
760
|
};
|
|
717
761
|
|
|
718
|
-
|
|
762
|
+
saveRrule = () => {
|
|
719
763
|
var value = this.state.rruleSet.toString();
|
|
720
764
|
this.props.onChange(this.props.id, value);
|
|
765
|
+
};
|
|
766
|
+
|
|
767
|
+
save = () => {
|
|
768
|
+
this.saveRrule();
|
|
721
769
|
this.close();
|
|
722
770
|
};
|
|
723
771
|
|
|
@@ -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(
|
|
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(
|
|
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
|
-
}, [
|
|
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(
|