@plone/volto 17.0.0-alpha.20 → 17.0.0-alpha.21
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/.yarn/install-state.gz +0 -0
- package/CHANGELOG.md +19 -0
- package/cypress/support/commands.js +2 -1
- package/cypress/support/e2e.js +1 -2
- package/locales/ca/LC_MESSAGES/volto.po +10 -1
- package/locales/ca.json +1 -1
- package/locales/de/LC_MESSAGES/volto.po +10 -1
- package/locales/de.json +1 -1
- package/locales/en/LC_MESSAGES/volto.po +10 -1
- package/locales/en.json +1 -1
- package/locales/es/LC_MESSAGES/volto.po +10 -1
- package/locales/es.json +1 -1
- package/locales/eu/LC_MESSAGES/volto.po +10 -1
- package/locales/eu.json +1 -1
- package/locales/fi/LC_MESSAGES/volto.po +10 -1
- package/locales/fi.json +1 -1
- package/locales/fr/LC_MESSAGES/volto.po +10 -1
- package/locales/fr.json +1 -1
- package/locales/it/LC_MESSAGES/volto.po +11 -2
- package/locales/it.json +1 -1
- package/locales/ja/LC_MESSAGES/volto.po +10 -1
- package/locales/ja.json +1 -1
- package/locales/nl/LC_MESSAGES/volto.po +10 -1
- package/locales/nl.json +1 -1
- package/locales/pt/LC_MESSAGES/volto.po +10 -1
- package/locales/pt.json +1 -1
- package/locales/pt_BR/LC_MESSAGES/volto.po +10 -1
- package/locales/pt_BR.json +1 -1
- package/locales/ro/LC_MESSAGES/volto.po +10 -1
- package/locales/ro.json +1 -1
- package/locales/volto.pot +10 -1
- package/locales/zh_CN/LC_MESSAGES/volto.po +10 -1
- package/locales/zh_CN.json +1 -1
- package/package.json +4 -4
- package/packages/volto-slate/package.json +1 -1
- package/src/components/index.js +1 -0
- package/src/components/manage/Blocks/Image/Edit.jsx +40 -5
- package/src/components/manage/Blocks/Image/Edit.test.jsx +2 -0
- package/src/components/manage/Blocks/Image/ImageSidebar.jsx +64 -15
- package/src/components/manage/Blocks/Image/View.jsx +25 -5
- package/src/components/manage/Blocks/Image/View.test.jsx +20 -0
- package/src/components/manage/Blocks/Image/schema.js +1 -9
- package/src/components/manage/Blocks/Image/utils.js +14 -0
- package/src/components/manage/Blocks/LeadImage/Edit.jsx +32 -10
- package/src/components/manage/Blocks/LeadImage/Edit.test.jsx +11 -1
- package/src/components/manage/Blocks/LeadImage/LeadImageSidebar.jsx +28 -9
- package/src/components/manage/Blocks/LeadImage/LeadImageSidebar.test.jsx +8 -2
- package/src/components/manage/Blocks/LeadImage/View.jsx +50 -38
- package/src/components/manage/Blocks/LeadImage/View.test.jsx +11 -1
- package/src/components/manage/Blocks/Listing/SummaryTemplate.jsx +1 -1
- package/src/components/manage/Blocks/Teaser/DefaultBody.jsx +13 -23
- package/src/components/manage/Contents/Contents.jsx +8 -6
- package/src/components/manage/Sidebar/AlignBlock.jsx +1 -1
- package/src/components/theme/Image/Image.jsx +96 -0
- package/src/components/theme/Image/Image.test.jsx +125 -0
- package/src/components/theme/Logo/Logo.jsx +2 -0
- package/src/components/theme/PreviewImage/PreviewImage.jsx +25 -14
- package/src/components/theme/PreviewImage/PreviewImage.test.js +39 -16
- package/src/components/theme/View/AlbumView.jsx +11 -15
- package/src/components/theme/View/EventView.jsx +30 -23
- package/src/components/theme/View/ImageView.jsx +5 -2
- package/src/components/theme/View/ImageView.test.jsx +4 -0
- package/src/components/theme/View/ListingView.jsx +5 -3
- package/src/components/theme/View/NewsItemView.jsx +7 -13
- package/src/components/theme/View/SummaryView.jsx +4 -3
- package/src/config/Blocks.jsx +2 -0
- package/src/config/Components.jsx +2 -1
- package/src/helpers/Url/Url.js +22 -1
- package/src/helpers/Url/Url.test.js +41 -0
- package/test-setup-config.js +9 -1
- package/theme/themes/pastanaga/extras/blocks.less +2 -0
- package/theme/themes/pastanaga/extras/main.less +5 -0
- package/src/components/manage/Blocks/Teaser/utils.js +0 -44
- package/src/components/manage/Blocks/Teaser/utils.test.jsx +0 -229
|
@@ -4,6 +4,8 @@ import { render } from '@testing-library/react';
|
|
|
4
4
|
import { MemoryRouter } from 'react-router-dom';
|
|
5
5
|
import { Provider } from 'react-intl-redux';
|
|
6
6
|
import configureStore from 'redux-mock-store';
|
|
7
|
+
import { getImageBlockSizes } from './utils';
|
|
8
|
+
import config from '@plone/volto/registry';
|
|
7
9
|
import { View } from './View';
|
|
8
10
|
|
|
9
11
|
const mockStore = configureStore();
|
|
@@ -15,6 +17,24 @@ const store = mockStore({
|
|
|
15
17
|
},
|
|
16
18
|
});
|
|
17
19
|
|
|
20
|
+
config.blocks.blocksConfig = {
|
|
21
|
+
image: {
|
|
22
|
+
id: 'image',
|
|
23
|
+
title: 'Image',
|
|
24
|
+
group: 'media',
|
|
25
|
+
extensions: {},
|
|
26
|
+
variations: [],
|
|
27
|
+
restricted: false,
|
|
28
|
+
mostUsed: true,
|
|
29
|
+
sidebarTab: 1,
|
|
30
|
+
security: {
|
|
31
|
+
addPermission: [],
|
|
32
|
+
view: [],
|
|
33
|
+
},
|
|
34
|
+
getSizes: getImageBlockSizes,
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
|
|
18
38
|
describe('Image View Component', () => {
|
|
19
39
|
test('renders a view image component with a local image', () => {
|
|
20
40
|
const { getByRole } = render(<View data={{ url: '/image.jpg' }} />);
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import { defineMessages } from 'react-intl';
|
|
2
2
|
|
|
3
3
|
const messages = defineMessages({
|
|
4
|
-
Source: {
|
|
5
|
-
id: 'Source',
|
|
6
|
-
defaultMessage: 'Source',
|
|
7
|
-
},
|
|
8
4
|
Image: {
|
|
9
5
|
id: 'Image',
|
|
10
6
|
defaultMessage: 'Image',
|
|
@@ -45,7 +41,7 @@ export function ImageSchema({ formData, intl }) {
|
|
|
45
41
|
{
|
|
46
42
|
id: 'default',
|
|
47
43
|
title: 'Default',
|
|
48
|
-
fields: [...(formData.url ? ['
|
|
44
|
+
fields: [...(formData.url ? ['alt', 'align', 'size'] : [])],
|
|
49
45
|
},
|
|
50
46
|
...(formData.url
|
|
51
47
|
? [
|
|
@@ -58,10 +54,6 @@ export function ImageSchema({ formData, intl }) {
|
|
|
58
54
|
: []),
|
|
59
55
|
],
|
|
60
56
|
properties: {
|
|
61
|
-
url: {
|
|
62
|
-
title: intl.formatMessage(messages.Source),
|
|
63
|
-
widget: 'url',
|
|
64
|
-
},
|
|
65
57
|
alt: {
|
|
66
58
|
title: intl.formatMessage(messages.AltText),
|
|
67
59
|
description: (
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function getImageBlockSizes(data) {
|
|
2
|
+
if (data.align === 'full') return '100vw';
|
|
3
|
+
if (data.align === 'center') {
|
|
4
|
+
if (data.size === 'l') return '100vw';
|
|
5
|
+
if (data.size === 'm') return '50vw';
|
|
6
|
+
if (data.size === 's') return '25vw';
|
|
7
|
+
}
|
|
8
|
+
if (data.align === 'left' || data.align === 'right') {
|
|
9
|
+
if (data.size === 'l') return '50vw';
|
|
10
|
+
if (data.size === 'm') return '25vw';
|
|
11
|
+
if (data.size === 's') return '15vw';
|
|
12
|
+
}
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
@@ -12,7 +12,7 @@ import { Message } from 'semantic-ui-react';
|
|
|
12
12
|
import { isEqual } from 'lodash';
|
|
13
13
|
|
|
14
14
|
import { LeadImageSidebar, SidebarPortal } from '@plone/volto/components';
|
|
15
|
-
import
|
|
15
|
+
import config from '@plone/volto/registry';
|
|
16
16
|
|
|
17
17
|
import imageBlockSVG from '@plone/volto/components/manage/Blocks/Image/block-image.svg';
|
|
18
18
|
|
|
@@ -79,11 +79,17 @@ class Edit extends Component {
|
|
|
79
79
|
* @returns {string} Markup for the component.
|
|
80
80
|
*/
|
|
81
81
|
render() {
|
|
82
|
+
const Image = config.getComponent({ name: 'Image' }).component;
|
|
82
83
|
const { data, properties } = this.props;
|
|
83
84
|
const placeholder =
|
|
84
85
|
this.props.data.placeholder ||
|
|
85
86
|
this.props.intl.formatMessage(messages.ImageBlockInputPlaceholder);
|
|
86
87
|
|
|
88
|
+
const hasImage = !!properties.image;
|
|
89
|
+
const hasImageData = hasImage && !!properties.image.data;
|
|
90
|
+
const className = cx('responsive', { 'full-image': data.align === 'full' });
|
|
91
|
+
const altText = data.image_caption || properties.image_caption || '';
|
|
92
|
+
|
|
87
93
|
return (
|
|
88
94
|
<div
|
|
89
95
|
className={cx(
|
|
@@ -94,7 +100,7 @@ class Edit extends Component {
|
|
|
94
100
|
data.align,
|
|
95
101
|
)}
|
|
96
102
|
>
|
|
97
|
-
{!
|
|
103
|
+
{!hasImage && (
|
|
98
104
|
<Message>
|
|
99
105
|
<center>
|
|
100
106
|
<img src={imageBlockSVG} alt="" />
|
|
@@ -102,15 +108,31 @@ class Edit extends Component {
|
|
|
102
108
|
</center>
|
|
103
109
|
</Message>
|
|
104
110
|
)}
|
|
105
|
-
{
|
|
111
|
+
{hasImage && hasImageData && (
|
|
106
112
|
<img
|
|
107
|
-
className={
|
|
108
|
-
src={
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
113
|
+
className={className}
|
|
114
|
+
src={`data:${properties.image['content-type']};base64,${properties.image.data}`}
|
|
115
|
+
width={properties.image.width}
|
|
116
|
+
height={properties.image.height}
|
|
117
|
+
alt={altText}
|
|
118
|
+
style={{
|
|
119
|
+
aspectRatio: `${properties.image.width}/${properties.image.height}`,
|
|
120
|
+
}}
|
|
121
|
+
/>
|
|
122
|
+
)}
|
|
123
|
+
{hasImage && !hasImageData && (
|
|
124
|
+
<Image
|
|
125
|
+
className={className}
|
|
126
|
+
item={properties}
|
|
127
|
+
imageField="image"
|
|
128
|
+
sizes={(() => {
|
|
129
|
+
if (data.align === 'full' || data.align === 'center')
|
|
130
|
+
return '100vw';
|
|
131
|
+
if (data.align === 'left' || data.align === 'right')
|
|
132
|
+
return '50vw';
|
|
133
|
+
return undefined;
|
|
134
|
+
})()}
|
|
135
|
+
alt={altText}
|
|
114
136
|
/>
|
|
115
137
|
)}
|
|
116
138
|
<SidebarPortal selected={this.props.selected}>
|
|
@@ -20,7 +20,17 @@ test('renders an edit Lead Image block component', () => {
|
|
|
20
20
|
data={{}}
|
|
21
21
|
properties={{
|
|
22
22
|
image: {
|
|
23
|
-
download: 'image.png',
|
|
23
|
+
download: 'http://localhost:3000/image.png',
|
|
24
|
+
width: 400,
|
|
25
|
+
height: 400,
|
|
26
|
+
scales: {
|
|
27
|
+
preview: {
|
|
28
|
+
download:
|
|
29
|
+
'http://localhost:3000/image.png/@@images/image/image-400.png',
|
|
30
|
+
width: 400,
|
|
31
|
+
height: 400,
|
|
32
|
+
},
|
|
33
|
+
},
|
|
24
34
|
},
|
|
25
35
|
}}
|
|
26
36
|
selected={false}
|
|
@@ -3,7 +3,12 @@ import PropTypes from 'prop-types';
|
|
|
3
3
|
import { Form } from 'semantic-ui-react';
|
|
4
4
|
import { Accordion, Grid, Segment } from 'semantic-ui-react';
|
|
5
5
|
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
CheckboxWidget,
|
|
8
|
+
Icon,
|
|
9
|
+
Image,
|
|
10
|
+
TextWidget,
|
|
11
|
+
} from '@plone/volto/components';
|
|
7
12
|
import { flattenToAppURL } from '@plone/volto/helpers';
|
|
8
13
|
import AlignBlock from '@plone/volto/components/manage/Sidebar/AlignBlock';
|
|
9
14
|
|
|
@@ -90,14 +95,28 @@ const LeadImageSidebar = ({
|
|
|
90
95
|
<>
|
|
91
96
|
<Segment className="sidebar-metadata-container" secondary>
|
|
92
97
|
{properties.image.filename}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
{properties.image.data && (
|
|
99
|
+
<img
|
|
100
|
+
// TODO understand when this actually happens
|
|
101
|
+
src={`data:${properties.image['content-type']};base64,${properties.image.data}`}
|
|
102
|
+
width={properties.image.width}
|
|
103
|
+
height={properties.image.height}
|
|
104
|
+
alt={data.image_caption || properties.image_caption || ''}
|
|
105
|
+
className="responsive"
|
|
106
|
+
style={{
|
|
107
|
+
aspectRatio: `${properties.image.width} / ${properties.image.height}`,
|
|
108
|
+
}}
|
|
109
|
+
/>
|
|
110
|
+
)}
|
|
111
|
+
{!properties.image.data && (
|
|
112
|
+
<Image
|
|
113
|
+
item={properties}
|
|
114
|
+
imageField="image"
|
|
115
|
+
alt={data.image_caption || properties.image_caption || ''}
|
|
116
|
+
responsive={true}
|
|
117
|
+
sizes="188px"
|
|
118
|
+
/>
|
|
119
|
+
)}
|
|
101
120
|
</Segment>
|
|
102
121
|
<Segment className="form sidebar-image-data">
|
|
103
122
|
<TextWidget
|
|
@@ -20,9 +20,15 @@ test('renders a Lead Image block Sidebar component', () => {
|
|
|
20
20
|
data={{}}
|
|
21
21
|
properties={{
|
|
22
22
|
image: {
|
|
23
|
+
download: 'http://localhost:3000/image.png',
|
|
24
|
+
width: 400,
|
|
25
|
+
height: 400,
|
|
23
26
|
scales: {
|
|
24
|
-
|
|
25
|
-
download:
|
|
27
|
+
preview: {
|
|
28
|
+
download:
|
|
29
|
+
'http://localhost:3000/image.png/@@images/image/image-400.png',
|
|
30
|
+
width: 400,
|
|
31
|
+
height: 400,
|
|
26
32
|
},
|
|
27
33
|
},
|
|
28
34
|
},
|
|
@@ -7,51 +7,63 @@ import React from 'react';
|
|
|
7
7
|
import PropTypes from 'prop-types';
|
|
8
8
|
import { UniversalLink } from '@plone/volto/components';
|
|
9
9
|
import cx from 'classnames';
|
|
10
|
-
|
|
11
|
-
import { flattenToAppURL } from '@plone/volto/helpers';
|
|
10
|
+
import config from '@plone/volto/registry';
|
|
12
11
|
|
|
13
12
|
/**
|
|
14
13
|
* View image block class.
|
|
15
14
|
* @class View
|
|
16
15
|
* @extends Component
|
|
17
16
|
*/
|
|
18
|
-
const View = ({ data, properties }) =>
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
17
|
+
const View = ({ data, properties }) => {
|
|
18
|
+
const Image = config.getComponent({ name: 'Image' }).component;
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<p
|
|
22
|
+
className={cx(
|
|
23
|
+
'block image align',
|
|
24
|
+
{
|
|
25
|
+
center: !Boolean(data.align),
|
|
26
|
+
},
|
|
27
|
+
data.align,
|
|
28
|
+
)}
|
|
29
|
+
>
|
|
30
|
+
{properties.image && (
|
|
31
|
+
<>
|
|
32
|
+
{(() => {
|
|
33
|
+
const image = (
|
|
34
|
+
<Image
|
|
35
|
+
className={cx({ 'full-width': data.align === 'full' })}
|
|
36
|
+
item={properties}
|
|
37
|
+
imageField="image"
|
|
38
|
+
sizes={(() => {
|
|
39
|
+
if (data.align === 'full' || data.align === 'center')
|
|
40
|
+
return '100vw';
|
|
41
|
+
if (data.align === 'left' || data.align === 'right')
|
|
42
|
+
return '50vw';
|
|
43
|
+
return undefined;
|
|
44
|
+
})()}
|
|
45
|
+
alt={properties.image_caption || ''}
|
|
46
|
+
responsive={true}
|
|
47
|
+
/>
|
|
46
48
|
);
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
49
|
+
if (data.href) {
|
|
50
|
+
return (
|
|
51
|
+
<UniversalLink
|
|
52
|
+
href={data.href}
|
|
53
|
+
openLinkInNewTab={data.openLinkInNewTab}
|
|
54
|
+
>
|
|
55
|
+
{image}
|
|
56
|
+
</UniversalLink>
|
|
57
|
+
);
|
|
58
|
+
} else {
|
|
59
|
+
return image;
|
|
60
|
+
}
|
|
61
|
+
})()}
|
|
62
|
+
</>
|
|
63
|
+
)}
|
|
64
|
+
</p>
|
|
65
|
+
);
|
|
66
|
+
};
|
|
55
67
|
|
|
56
68
|
/**
|
|
57
69
|
* Property types.
|
|
@@ -8,7 +8,17 @@ test('renders a view image component', () => {
|
|
|
8
8
|
data={{}}
|
|
9
9
|
properties={{
|
|
10
10
|
image: {
|
|
11
|
-
download: 'image.png',
|
|
11
|
+
download: 'http://localhost:3000/image.png',
|
|
12
|
+
width: 400,
|
|
13
|
+
height: 400,
|
|
14
|
+
scales: {
|
|
15
|
+
preview: {
|
|
16
|
+
download:
|
|
17
|
+
'http://localhost:3000/image.png/@@images/image/image-400.png',
|
|
18
|
+
width: 400,
|
|
19
|
+
height: 400,
|
|
20
|
+
},
|
|
21
|
+
},
|
|
12
22
|
},
|
|
13
23
|
}}
|
|
14
24
|
/>,
|
|
@@ -27,7 +27,7 @@ const SummaryTemplate = ({ items, linkTitle, linkHref, isEditMode }) => {
|
|
|
27
27
|
<ConditionalLink item={item} condition={!isEditMode}>
|
|
28
28
|
<Component componentName="PreviewImage" item={item} alt="" />
|
|
29
29
|
<div className="listing-body">
|
|
30
|
-
<h3>{item.title
|
|
30
|
+
<h3>{item.title || item.id}</h3>
|
|
31
31
|
<p>{item.description}</p>
|
|
32
32
|
</div>
|
|
33
33
|
</ConditionalLink>
|
|
@@ -3,8 +3,7 @@ import PropTypes from 'prop-types';
|
|
|
3
3
|
import { Message } from 'semantic-ui-react';
|
|
4
4
|
import { defineMessages, useIntl } from 'react-intl';
|
|
5
5
|
import imageBlockSVG from '@plone/volto/components/manage/Blocks/Image/block-image.svg';
|
|
6
|
-
import {
|
|
7
|
-
import { getTeaserImageURL } from './utils';
|
|
6
|
+
import { isInternalURL } from '@plone/volto/helpers';
|
|
8
7
|
import { MaybeWrap } from '@plone/volto/components';
|
|
9
8
|
import { UniversalLink } from '@plone/volto/components';
|
|
10
9
|
import cx from 'classnames';
|
|
@@ -22,28 +21,11 @@ const TeaserDefaultTemplate = (props) => {
|
|
|
22
21
|
const { className, data, isEditMode } = props;
|
|
23
22
|
const intl = useIntl();
|
|
24
23
|
const href = data.href?.[0];
|
|
25
|
-
const
|
|
26
|
-
const align = data?.styles?.align;
|
|
24
|
+
const image = data.preview_image?.[0];
|
|
27
25
|
|
|
26
|
+
const Image = config.getComponent('Image').component;
|
|
28
27
|
const { openExternalLinkInNewTab } = config.settings;
|
|
29
28
|
|
|
30
|
-
let renderedImage = null;
|
|
31
|
-
if (href && (imageOverride || href.hasPreviewImage || href.image_field)) {
|
|
32
|
-
let Image = config.getComponent('Image').component;
|
|
33
|
-
if (Image) {
|
|
34
|
-
// custom image component expects item summary as src
|
|
35
|
-
renderedImage = (
|
|
36
|
-
<Image src={imageOverride || href} alt="" loading="lazy" />
|
|
37
|
-
);
|
|
38
|
-
} else {
|
|
39
|
-
// default img expects string src
|
|
40
|
-
const src = flattenToAppURL(
|
|
41
|
-
getTeaserImageURL({ href, imageOverride, align }),
|
|
42
|
-
);
|
|
43
|
-
renderedImage = <img src={src} alt="" loading="lazy" />;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
29
|
return (
|
|
48
30
|
<div className={cx('block teaser', className)}>
|
|
49
31
|
<>
|
|
@@ -68,8 +50,16 @@ const TeaserDefaultTemplate = (props) => {
|
|
|
68
50
|
}
|
|
69
51
|
>
|
|
70
52
|
<div className="teaser-item default">
|
|
71
|
-
{
|
|
72
|
-
<div className="image-wrapper">
|
|
53
|
+
{(href.hasPreviewImage || href.image_field || image) && (
|
|
54
|
+
<div className="image-wrapper">
|
|
55
|
+
<Image
|
|
56
|
+
item={image || href}
|
|
57
|
+
imageField={image ? image.image_field : href.image_field}
|
|
58
|
+
alt=""
|
|
59
|
+
loading="lazy"
|
|
60
|
+
responsive={true}
|
|
61
|
+
/>
|
|
62
|
+
</div>
|
|
73
63
|
)}
|
|
74
64
|
<div className="content">
|
|
75
65
|
{data?.head_title && (
|
|
@@ -450,7 +450,7 @@ class Contents extends Component {
|
|
|
450
450
|
sort_on: this.props.sort?.on || 'getObjPositionInParent',
|
|
451
451
|
sort_order: this.props.sort?.order || 'ascending',
|
|
452
452
|
isClient: false,
|
|
453
|
-
linkIntegrityBreakages:
|
|
453
|
+
linkIntegrityBreakages: [],
|
|
454
454
|
};
|
|
455
455
|
this.filterTimeout = null;
|
|
456
456
|
}
|
|
@@ -1236,7 +1236,10 @@ class Contents extends Component {
|
|
|
1236
1236
|
Show all items
|
|
1237
1237
|
</Button>
|
|
1238
1238
|
)}
|
|
1239
|
-
{this.state.linkIntegrityBreakages.
|
|
1239
|
+
{this.state.linkIntegrityBreakages.reduce(
|
|
1240
|
+
(a, b) => a + b.breaches.length,
|
|
1241
|
+
0,
|
|
1242
|
+
) ? (
|
|
1240
1243
|
<div>
|
|
1241
1244
|
<h3>
|
|
1242
1245
|
{this.props.intl.formatMessage(
|
|
@@ -1249,9 +1252,8 @@ class Contents extends Component {
|
|
|
1249
1252
|
)}
|
|
1250
1253
|
</p>
|
|
1251
1254
|
<ul className="content">
|
|
1252
|
-
{map(
|
|
1253
|
-
|
|
1254
|
-
(item) => (
|
|
1255
|
+
{map(this.state.linkIntegrityBreakages, (item) =>
|
|
1256
|
+
item.breaches.length ? (
|
|
1255
1257
|
<li key={item['@id']}>
|
|
1256
1258
|
<a href={item['@id']}>{item.title}</a>
|
|
1257
1259
|
<p>
|
|
@@ -1269,7 +1271,7 @@ class Contents extends Component {
|
|
|
1269
1271
|
))}
|
|
1270
1272
|
</ul>
|
|
1271
1273
|
</li>
|
|
1272
|
-
),
|
|
1274
|
+
) : null,
|
|
1273
1275
|
)}
|
|
1274
1276
|
</ul>
|
|
1275
1277
|
</div>
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import PropTypes from 'prop-types';
|
|
2
|
+
import cx from 'classnames';
|
|
3
|
+
import { flattenToAppURL } from '@plone/volto/helpers';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Image component
|
|
7
|
+
* @param {object} item - Context item that has the image field (can also be a catalog brain or summary)
|
|
8
|
+
* @param {string} imageField - Key of the image field inside the item, or inside the image_scales object of the item if it is a catalog brain or summary
|
|
9
|
+
* @param {string} src - URL of the image to be used if the item field is not available
|
|
10
|
+
* @param {string} alt - Alternative text for the image
|
|
11
|
+
* @param {boolean} loading - (default: eager) set to `lazy` to lazy load the image
|
|
12
|
+
* @param {boolean} responsive - (default: false) set to `true` to add the `responsive` class to the image
|
|
13
|
+
* @param {string} className - Additional classes to add to the image
|
|
14
|
+
*/
|
|
15
|
+
export default function Image({
|
|
16
|
+
item,
|
|
17
|
+
imageField,
|
|
18
|
+
src,
|
|
19
|
+
alt = '',
|
|
20
|
+
loading = 'eager',
|
|
21
|
+
responsive = false,
|
|
22
|
+
className = '',
|
|
23
|
+
...imageProps
|
|
24
|
+
}) {
|
|
25
|
+
if (!item && !src) return null;
|
|
26
|
+
|
|
27
|
+
// TypeScript hints for editor autocomplete :)
|
|
28
|
+
/** @type {React.ImgHTMLAttributes<HTMLImageElement>} */
|
|
29
|
+
const attrs = {};
|
|
30
|
+
|
|
31
|
+
if (!item && src) {
|
|
32
|
+
attrs.src = src;
|
|
33
|
+
attrs.className = cx(className, { responsive });
|
|
34
|
+
} else {
|
|
35
|
+
const isFromRealObject = !item.image_scales;
|
|
36
|
+
const imageFieldWithDefault = imageField || item.image_field || 'image';
|
|
37
|
+
|
|
38
|
+
const image = isFromRealObject
|
|
39
|
+
? item[imageFieldWithDefault]
|
|
40
|
+
: item.image_scales[imageFieldWithDefault]?.[0];
|
|
41
|
+
|
|
42
|
+
if (!image) return null;
|
|
43
|
+
|
|
44
|
+
const isSvg = image['content-type'] === 'image/svg+xml';
|
|
45
|
+
|
|
46
|
+
const baseUrl = isFromRealObject ? '' : flattenToAppURL(item['@id'] + '/');
|
|
47
|
+
|
|
48
|
+
attrs.src = `${baseUrl}${flattenToAppURL(image.download)}`;
|
|
49
|
+
attrs.width = image.width;
|
|
50
|
+
attrs.height = image.height;
|
|
51
|
+
attrs.style = {
|
|
52
|
+
aspectRatio: `${image.width} / ${image.height}`,
|
|
53
|
+
...imageProps.style,
|
|
54
|
+
};
|
|
55
|
+
attrs.className = cx(className, { responsive });
|
|
56
|
+
|
|
57
|
+
if (!isSvg && image.scales && Object.keys(image.scales).length > 0) {
|
|
58
|
+
const sortedScales = Object.values(image.scales).sort((a, b) => {
|
|
59
|
+
if (a.width > b.width) return 1;
|
|
60
|
+
else if (a.width < b.width) return -1;
|
|
61
|
+
else return 0;
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
attrs.srcSet = sortedScales
|
|
65
|
+
.map(
|
|
66
|
+
(scale) =>
|
|
67
|
+
`${baseUrl}${flattenToAppURL(scale.download)} ${scale.width}w`,
|
|
68
|
+
)
|
|
69
|
+
.join(', ');
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (loading === 'lazy') {
|
|
74
|
+
attrs.loading = 'lazy';
|
|
75
|
+
attrs.decoding = 'async';
|
|
76
|
+
} else {
|
|
77
|
+
attrs.fetchpriority = 'high';
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return <img {...attrs} alt={alt} {...imageProps} />;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
Image.propTypes = {
|
|
84
|
+
item: PropTypes.shape({
|
|
85
|
+
'@id': PropTypes.string,
|
|
86
|
+
image_field: PropTypes.string,
|
|
87
|
+
image_scales: PropTypes.object,
|
|
88
|
+
image: PropTypes.object,
|
|
89
|
+
}),
|
|
90
|
+
imageField: PropTypes.string,
|
|
91
|
+
src: PropTypes.string,
|
|
92
|
+
alt: PropTypes.string.isRequired,
|
|
93
|
+
loading: PropTypes.string,
|
|
94
|
+
responsive: PropTypes.bool,
|
|
95
|
+
className: PropTypes.string,
|
|
96
|
+
};
|