@plone/volto 17.6.1 → 17.7.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 +15 -0
- 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 +5 -0
- package/src/components/manage/Blocks/Block/Settings.jsx +10 -1
- package/src/components/manage/Blocks/Container/Data.jsx +4 -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/Listing/ListingData.jsx +4 -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/Teaser/Data.jsx +4 -1
- 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/theme/Image/Image.jsx +4 -2
- package/src/components/theme/Image/Image.test.jsx +32 -0
- package/src/helpers/Blocks/Blocks.js +11 -2
- 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/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 +1 -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
package/CHANGELOG.md
CHANGED
|
@@ -17,6 +17,21 @@ myst:
|
|
|
17
17
|
|
|
18
18
|
<!-- towncrier release notes start -->
|
|
19
19
|
|
|
20
|
+
## 17.7.0 (2023-12-13)
|
|
21
|
+
|
|
22
|
+
### Feature
|
|
23
|
+
|
|
24
|
+
- Added conditional variations support. @sneridagh @robgietema [#5424](https://github.com/plone/volto/issues/5424)
|
|
25
|
+
- Added `navRoot` and `contentType` to `restricted` key in blocks configuration. @sneridagh [#5517](https://github.com/plone/volto/issues/5517)
|
|
26
|
+
- Add support for `preview_image_link` behavior in Volto Image component @sneridagh [#5523](https://github.com/plone/volto/issues/5523)
|
|
27
|
+
|
|
28
|
+
### Bugfix
|
|
29
|
+
|
|
30
|
+
- Fix the right order of parameters in normalizeExternalData.js @dobri1408 [#5347](https://github.com/plone/volto/issues/5347)
|
|
31
|
+
- Refactoring the code for extraction of videoDetails from the video URL, adding code for extracting videoDetails from youtube video URLs with '/live/' in its URL which previously used to throw an error and adding jest tests for same. @IshaanDasgupta [#5416](https://github.com/plone/volto/issues/5416)
|
|
32
|
+
- Initialize data in form before the checks for the `blocks` and `blocks_layout` are done This fix an edge case when the data from the server content is empty, then the fields are populated, but the initialized data is snapshot after the check (and amendments) are done. @sneridagh [#5445](https://github.com/plone/volto/issues/5445)
|
|
33
|
+
- Replaced `toNumber` with `parseFloat` to avoid an error when validating the `plone.restapi` version. @shibbu264 [#5448](https://github.com/plone/volto/issues/5448)
|
|
34
|
+
|
|
20
35
|
## 17.6.1 (2023-11-27)
|
|
21
36
|
|
|
22
37
|
### Bugfix
|
package/package.json
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Fix the right order of parameters in normalizeExternalData.js @dobri1408
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Added `navRoot` and `contentType` to `restricted` key in blocks configuration. @sneridagh
|
|
@@ -70,6 +70,8 @@ export const DefaultTextBlockEditor = (props) => {
|
|
|
70
70
|
allowedBlocks,
|
|
71
71
|
formTitle,
|
|
72
72
|
formDescription,
|
|
73
|
+
navRoot,
|
|
74
|
+
contentType,
|
|
73
75
|
} = props;
|
|
74
76
|
|
|
75
77
|
const { slate } = config.settings;
|
|
@@ -267,6 +269,8 @@ export const DefaultTextBlockEditor = (props) => {
|
|
|
267
269
|
blocksConfig={blocksConfig}
|
|
268
270
|
size="24px"
|
|
269
271
|
properties={properties}
|
|
272
|
+
navRoot={navRoot}
|
|
273
|
+
contentType={contentType}
|
|
270
274
|
/>
|
|
271
275
|
)}
|
|
272
276
|
|
|
@@ -98,7 +98,9 @@ export const insertData = (editor) => {
|
|
|
98
98
|
if (Editor.string(editor, [])) {
|
|
99
99
|
if (
|
|
100
100
|
Array.isArray(fragment) &&
|
|
101
|
-
fragment.findIndex(
|
|
101
|
+
fragment.findIndex(
|
|
102
|
+
(b) => Editor.isInline(editor, b) || Text.isText(b),
|
|
103
|
+
) > -1
|
|
102
104
|
) {
|
|
103
105
|
// console.log('insert fragment', fragment);
|
|
104
106
|
// TODO: we want normalization also when dealing with fragments
|
|
@@ -2,7 +2,7 @@ import { normalizeExternalData as normalize } from '@plone/volto-slate/utils';
|
|
|
2
2
|
|
|
3
3
|
export function normalizeExternalData(editor) {
|
|
4
4
|
editor.normalizeExternalData = (fragment) => {
|
|
5
|
-
return normalize(fragment);
|
|
5
|
+
return normalize(editor, fragment);
|
|
6
6
|
};
|
|
7
7
|
return editor;
|
|
8
8
|
}
|
|
@@ -73,7 +73,10 @@ export const normalizeExternalData = (editor, nodes) => {
|
|
|
73
73
|
// put all the non-blocks (e.g. images which are inline Elements) inside p-s
|
|
74
74
|
Editor.withoutNormalizing(fakeEditor, () => {
|
|
75
75
|
//for htmlSlateWidget compatibility
|
|
76
|
-
if (
|
|
76
|
+
if (
|
|
77
|
+
nodes &&
|
|
78
|
+
(!Editor.isBlock(fakeEditor, nodes[0]) || Text.isText(nodes[0]))
|
|
79
|
+
)
|
|
77
80
|
Transforms.wrapNodes(
|
|
78
81
|
fakeEditor,
|
|
79
82
|
{ type: 'p' },
|
|
@@ -81,7 +84,8 @@ export const normalizeExternalData = (editor, nodes) => {
|
|
|
81
84
|
at: [],
|
|
82
85
|
match: (node, path) =>
|
|
83
86
|
(!Editor.isEditor(node) && !Editor.isBlock(fakeEditor, node)) ||
|
|
84
|
-
fakeEditor.isInline(node)
|
|
87
|
+
fakeEditor.isInline(node) ||
|
|
88
|
+
Text.isText(node),
|
|
85
89
|
mode: 'highest',
|
|
86
90
|
},
|
|
87
91
|
);
|
|
@@ -315,6 +315,9 @@ class Add extends Component {
|
|
|
315
315
|
<Form
|
|
316
316
|
ref={this.form}
|
|
317
317
|
key="translated-or-new-content-form"
|
|
318
|
+
navRoot={
|
|
319
|
+
this.props.content?.['@components']?.navroot?.navroot || {}
|
|
320
|
+
}
|
|
318
321
|
schema={this.props.schema}
|
|
319
322
|
type={this.props.type}
|
|
320
323
|
formData={{
|
|
@@ -334,6 +337,9 @@ class Add extends Component {
|
|
|
334
337
|
// Copy the Language Independent Fields values from the to-be translated content
|
|
335
338
|
// into the default values of the translated content Add form.
|
|
336
339
|
...lifData(),
|
|
340
|
+
parent: {
|
|
341
|
+
'@id': this.props.content?.['@id'] || '',
|
|
342
|
+
},
|
|
337
343
|
}}
|
|
338
344
|
requestError={this.state.error}
|
|
339
345
|
onSubmit={this.onSubmit}
|
|
@@ -31,6 +31,8 @@ const BlockChooser = ({
|
|
|
31
31
|
blocksConfig = config.blocks.blocksConfig,
|
|
32
32
|
blockChooserRef,
|
|
33
33
|
properties = {},
|
|
34
|
+
navRoot,
|
|
35
|
+
contentType,
|
|
34
36
|
}) => {
|
|
35
37
|
const intl = useIntl();
|
|
36
38
|
const hasAllowedBlocks = !isEmpty(allowedBlocks);
|
|
@@ -55,7 +57,7 @@ const BlockChooser = ({
|
|
|
55
57
|
// depending on this function, given properties (current present blocks) and the
|
|
56
58
|
// block being evaluated
|
|
57
59
|
return typeof item.restricted === 'function'
|
|
58
|
-
? !item.restricted({ properties, block: item })
|
|
60
|
+
? !item.restricted({ properties, block: item, navRoot, contentType })
|
|
59
61
|
: !item.restricted;
|
|
60
62
|
}
|
|
61
63
|
}
|
|
@@ -54,7 +54,10 @@ const BlockChooserButton = (props) => {
|
|
|
54
54
|
blocksConfig,
|
|
55
55
|
buttonComponent,
|
|
56
56
|
properties,
|
|
57
|
+
navRoot,
|
|
58
|
+
contentType,
|
|
57
59
|
} = props;
|
|
60
|
+
|
|
58
61
|
const { disableNewBlocks } = data;
|
|
59
62
|
const [addNewBlockOpened, setAddNewBlockOpened] = React.useState(false);
|
|
60
63
|
|
|
@@ -142,6 +145,8 @@ const BlockChooserButton = (props) => {
|
|
|
142
145
|
properties={properties}
|
|
143
146
|
showRestricted={showRestricted}
|
|
144
147
|
ref={blockChooserRef}
|
|
148
|
+
navRoot={navRoot}
|
|
149
|
+
contentType={contentType}
|
|
145
150
|
/>
|
|
146
151
|
</div>
|
|
147
152
|
</Portal>
|
|
@@ -28,6 +28,8 @@ const BlocksForm = (props) => {
|
|
|
28
28
|
pathname,
|
|
29
29
|
onChangeField,
|
|
30
30
|
properties,
|
|
31
|
+
type,
|
|
32
|
+
navRoot,
|
|
31
33
|
onChangeFormData,
|
|
32
34
|
selectedBlock,
|
|
33
35
|
multiSelected,
|
|
@@ -260,6 +262,8 @@ const BlocksForm = (props) => {
|
|
|
260
262
|
pathname,
|
|
261
263
|
metadata,
|
|
262
264
|
properties,
|
|
265
|
+
contentType: type,
|
|
266
|
+
navRoot,
|
|
263
267
|
blocksConfig,
|
|
264
268
|
selected: selectedBlock === childId,
|
|
265
269
|
multiSelected: multiSelected?.includes(childId),
|
|
@@ -7,7 +7,7 @@ import DefaultBlockView from './DefaultView';
|
|
|
7
7
|
|
|
8
8
|
const DefaultBlockEdit = (props) => {
|
|
9
9
|
const { blocksConfig = config.blocks.blocksConfig } = props;
|
|
10
|
-
const { data, onChangeBlock, block, selected } = props;
|
|
10
|
+
const { data, onChangeBlock, block, selected, navRoot, contentType } = props;
|
|
11
11
|
const intl = useIntl();
|
|
12
12
|
const blockSchema = blocksConfig?.[data['@type']]?.blockSchema;
|
|
13
13
|
const schema =
|
|
@@ -32,6 +32,8 @@ const DefaultBlockEdit = (props) => {
|
|
|
32
32
|
});
|
|
33
33
|
}}
|
|
34
34
|
formData={data}
|
|
35
|
+
navRoot={navRoot}
|
|
36
|
+
contentType={contentType}
|
|
35
37
|
/>
|
|
36
38
|
</SidebarPortal>
|
|
37
39
|
) : (
|
|
@@ -47,7 +47,10 @@ const EditBlockWrapper = (props) => {
|
|
|
47
47
|
editable,
|
|
48
48
|
properties,
|
|
49
49
|
showBlockChooser,
|
|
50
|
+
navRoot,
|
|
51
|
+
contentType,
|
|
50
52
|
} = blockProps;
|
|
53
|
+
|
|
51
54
|
const visible = selected && !hideHandler(data);
|
|
52
55
|
|
|
53
56
|
const required = isBoolean(data.required)
|
|
@@ -107,6 +110,8 @@ const EditBlockWrapper = (props) => {
|
|
|
107
110
|
blocksConfig={blocksConfig}
|
|
108
111
|
size="24px"
|
|
109
112
|
properties={properties}
|
|
113
|
+
navRoot={navRoot}
|
|
114
|
+
contentType={contentType}
|
|
110
115
|
/>
|
|
111
116
|
)}
|
|
112
117
|
</div>
|
|
@@ -3,7 +3,14 @@ import PropTypes from 'prop-types';
|
|
|
3
3
|
import { injectIntl } from 'react-intl';
|
|
4
4
|
import BlockDataForm from '@plone/volto/components/manage/Form/BlockDataForm';
|
|
5
5
|
|
|
6
|
-
const Settings = ({
|
|
6
|
+
const Settings = ({
|
|
7
|
+
data,
|
|
8
|
+
block,
|
|
9
|
+
onChangeBlock,
|
|
10
|
+
schema,
|
|
11
|
+
navRoot,
|
|
12
|
+
contentType,
|
|
13
|
+
}) => {
|
|
7
14
|
return (
|
|
8
15
|
<BlockDataForm
|
|
9
16
|
schema={schema}
|
|
@@ -17,6 +24,8 @@ const Settings = ({ data, block, onChangeBlock, schema }) => {
|
|
|
17
24
|
onChangeBlock={onChangeBlock}
|
|
18
25
|
formData={data}
|
|
19
26
|
applySchemaEnhancers={false}
|
|
27
|
+
navRoot={navRoot}
|
|
28
|
+
contentType={contentType}
|
|
20
29
|
/>
|
|
21
30
|
);
|
|
22
31
|
};
|
|
@@ -2,7 +2,8 @@ import { useIntl } from 'react-intl';
|
|
|
2
2
|
import { BlockDataForm } from '@plone/volto/components';
|
|
3
3
|
|
|
4
4
|
const ContainerData = (props) => {
|
|
5
|
-
const { block, blocksConfig, data, onChangeBlock } =
|
|
5
|
+
const { block, blocksConfig, data, onChangeBlock, navRoot, contentType } =
|
|
6
|
+
props;
|
|
6
7
|
const intl = useIntl();
|
|
7
8
|
|
|
8
9
|
const schema = blocksConfig[data['@type']].blockSchema({ intl });
|
|
@@ -25,6 +26,8 @@ const ContainerData = (props) => {
|
|
|
25
26
|
block={block}
|
|
26
27
|
onChangeBlock={onChangeBlock}
|
|
27
28
|
blocksConfig={blocksConfig}
|
|
29
|
+
navRoot={navRoot}
|
|
30
|
+
contentType={contentType}
|
|
28
31
|
/>
|
|
29
32
|
);
|
|
30
33
|
};
|
|
@@ -4,7 +4,7 @@ import schemaHero from './schema.js';
|
|
|
4
4
|
import { BlockDataForm } from '@plone/volto/components';
|
|
5
5
|
|
|
6
6
|
const HeroImageLeftBlockData = (props) => {
|
|
7
|
-
const { block, data, onChangeBlock } = props;
|
|
7
|
+
const { block, data, onChangeBlock, navRoot, contentType } = props;
|
|
8
8
|
const intl = useIntl();
|
|
9
9
|
const schema = schemaHero({ ...props, intl });
|
|
10
10
|
return (
|
|
@@ -20,6 +20,8 @@ const HeroImageLeftBlockData = (props) => {
|
|
|
20
20
|
onChangeBlock={onChangeBlock}
|
|
21
21
|
formData={data}
|
|
22
22
|
block={block}
|
|
23
|
+
navRoot={navRoot}
|
|
24
|
+
contentType={contentType}
|
|
23
25
|
/>
|
|
24
26
|
);
|
|
25
27
|
};
|
|
@@ -9,7 +9,8 @@ import imageSVG from '@plone/volto/icons/image.svg';
|
|
|
9
9
|
import trashSVG from '@plone/volto/icons/delete.svg';
|
|
10
10
|
|
|
11
11
|
const ImageSidebar = (props) => {
|
|
12
|
-
const { blocksConfig, data, block, onChangeBlock } =
|
|
12
|
+
const { blocksConfig, data, block, onChangeBlock, navRoot, contentType } =
|
|
13
|
+
props;
|
|
13
14
|
const intl = useIntl();
|
|
14
15
|
const schema = ImageSchema({ formData: data, intl });
|
|
15
16
|
return (
|
|
@@ -94,6 +95,8 @@ const ImageSidebar = (props) => {
|
|
|
94
95
|
formData={data}
|
|
95
96
|
block={block}
|
|
96
97
|
blocksConfig={blocksConfig}
|
|
98
|
+
navRoot={navRoot}
|
|
99
|
+
contentType={contentType}
|
|
97
100
|
/>
|
|
98
101
|
</>
|
|
99
102
|
);
|
|
@@ -4,7 +4,8 @@ import { useIntl } from 'react-intl';
|
|
|
4
4
|
import { BlockDataForm } from '@plone/volto/components';
|
|
5
5
|
|
|
6
6
|
const ListingData = (props) => {
|
|
7
|
-
const { data, block, blocksConfig, onChangeBlock } =
|
|
7
|
+
const { data, block, blocksConfig, onChangeBlock, navRoot, contentType } =
|
|
8
|
+
props;
|
|
8
9
|
const intl = useIntl();
|
|
9
10
|
const schema = blocksConfig.listing.blockSchema({
|
|
10
11
|
...props,
|
|
@@ -25,6 +26,8 @@ const ListingData = (props) => {
|
|
|
25
26
|
formData={data}
|
|
26
27
|
blocksConfig={blocksConfig}
|
|
27
28
|
block={block}
|
|
29
|
+
navRoot={navRoot}
|
|
30
|
+
contentType={contentType}
|
|
28
31
|
/>
|
|
29
32
|
);
|
|
30
33
|
};
|
|
@@ -18,7 +18,7 @@ const messages = defineMessages({
|
|
|
18
18
|
});
|
|
19
19
|
|
|
20
20
|
const MapsSidebar = (props) => {
|
|
21
|
-
const { data, block, onChangeBlock } = props;
|
|
21
|
+
const { data, block, onChangeBlock, navRoot, contentType } = props;
|
|
22
22
|
const intl = useIntl();
|
|
23
23
|
const schema = MapsSchema({ ...props, intl });
|
|
24
24
|
|
|
@@ -42,6 +42,8 @@ const MapsSidebar = (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
|
</>
|
|
@@ -26,6 +26,8 @@ const SearchBlockEdit = (props) => {
|
|
|
26
26
|
data,
|
|
27
27
|
selected,
|
|
28
28
|
intl,
|
|
29
|
+
navRoot,
|
|
30
|
+
contentType,
|
|
29
31
|
onTriggerSearch,
|
|
30
32
|
querystring = {},
|
|
31
33
|
} = props;
|
|
@@ -83,6 +85,8 @@ const SearchBlockEdit = (props) => {
|
|
|
83
85
|
}}
|
|
84
86
|
onChangeBlock={onChangeBlock}
|
|
85
87
|
formData={data}
|
|
88
|
+
navRoot={navRoot}
|
|
89
|
+
contentType={contentType}
|
|
86
90
|
/>
|
|
87
91
|
</SidebarPortal>
|
|
88
92
|
</>
|
|
@@ -14,7 +14,8 @@ const messages = defineMessages({
|
|
|
14
14
|
});
|
|
15
15
|
|
|
16
16
|
const TeaserData = (props) => {
|
|
17
|
-
const { block, blocksConfig, data, onChangeBlock } =
|
|
17
|
+
const { block, blocksConfig, data, onChangeBlock, navRoot, contentType } =
|
|
18
|
+
props;
|
|
18
19
|
const intl = useIntl();
|
|
19
20
|
|
|
20
21
|
const reset = () => {
|
|
@@ -64,6 +65,8 @@ const TeaserData = (props) => {
|
|
|
64
65
|
block={block}
|
|
65
66
|
blocksConfig={blocksConfig}
|
|
66
67
|
headerActions={HeaderActions}
|
|
68
|
+
navRoot={navRoot}
|
|
69
|
+
contentType={contentType}
|
|
67
70
|
/>
|
|
68
71
|
);
|
|
69
72
|
};
|
|
@@ -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
|
+
});
|