@plone/volto 17.0.0-alpha.20 → 17.0.0-alpha.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/.gitignore~ +71 -0
  2. package/.yarn/install-state.gz +0 -0
  3. package/CHANGELOG.md +49 -0
  4. package/cypress/support/commands.js +2 -1
  5. package/cypress/support/e2e.js +1 -2
  6. package/locales/ca/LC_MESSAGES/volto.po +24 -5
  7. package/locales/ca.json +1 -1
  8. package/locales/de/LC_MESSAGES/volto.po +37 -18
  9. package/locales/de.json +1 -1
  10. package/locales/en/LC_MESSAGES/volto.po +25 -6
  11. package/locales/en.json +1 -1
  12. package/locales/es/LC_MESSAGES/volto.po +25 -6
  13. package/locales/es.json +1 -1
  14. package/locales/eu/LC_MESSAGES/volto.po +24 -5
  15. package/locales/eu.json +1 -1
  16. package/locales/fi/LC_MESSAGES/volto.po +24 -5
  17. package/locales/fi.json +1 -1
  18. package/locales/fr/LC_MESSAGES/volto.po +24 -5
  19. package/locales/fr.json +1 -1
  20. package/locales/it/LC_MESSAGES/volto.po +250 -231
  21. package/locales/it.json +1 -1
  22. package/locales/ja/LC_MESSAGES/volto.po +24 -5
  23. package/locales/ja.json +1 -1
  24. package/locales/nl/LC_MESSAGES/volto.po +24 -5
  25. package/locales/nl.json +1 -1
  26. package/locales/pt/LC_MESSAGES/volto.po +24 -5
  27. package/locales/pt.json +1 -1
  28. package/locales/pt_BR/LC_MESSAGES/volto.po +25 -6
  29. package/locales/pt_BR.json +1 -1
  30. package/locales/ro/LC_MESSAGES/volto.po +24 -5
  31. package/locales/ro.json +1 -1
  32. package/locales/volto.pot +25 -6
  33. package/locales/zh_CN/LC_MESSAGES/volto.po +24 -5
  34. package/locales/zh_CN.json +1 -1
  35. package/news/4547.breaking~ +1 -0
  36. package/package.json +6 -6
  37. package/packages/volto-slate/package.json +1 -1
  38. package/src/actions/relations/rebuild.js +7 -7
  39. package/src/components/index.js +1 -0
  40. package/src/components/manage/Actions/Actions.jsx +133 -243
  41. package/src/components/manage/Blocks/Container/Edit.jsx +4 -1
  42. package/src/components/manage/Blocks/Container/EditBlockWrapper.jsx +1 -0
  43. package/src/components/manage/Blocks/Grid/Edit.jsx +13 -1
  44. package/src/components/manage/Blocks/Image/Edit.jsx +40 -5
  45. package/src/components/manage/Blocks/Image/Edit.test.jsx +2 -0
  46. package/src/components/manage/Blocks/Image/ImageSidebar.jsx +64 -15
  47. package/src/components/manage/Blocks/Image/View.jsx +26 -5
  48. package/src/components/manage/Blocks/Image/View.test.jsx +20 -0
  49. package/src/components/manage/Blocks/Image/schema.js +1 -9
  50. package/src/components/manage/Blocks/Image/utils.js +14 -0
  51. package/src/components/manage/Blocks/LeadImage/Edit.jsx +32 -10
  52. package/src/components/manage/Blocks/LeadImage/Edit.test.jsx +11 -1
  53. package/src/components/manage/Blocks/LeadImage/LeadImageSidebar.jsx +28 -9
  54. package/src/components/manage/Blocks/LeadImage/LeadImageSidebar.test.jsx +8 -2
  55. package/src/components/manage/Blocks/LeadImage/View.jsx +50 -38
  56. package/src/components/manage/Blocks/LeadImage/View.test.jsx +11 -1
  57. package/src/components/manage/Blocks/Listing/SummaryTemplate.jsx +1 -1
  58. package/src/components/manage/Blocks/Maps/Edit.jsx +135 -209
  59. package/src/components/manage/Blocks/Search/SearchBlockView.jsx +3 -2
  60. package/src/components/manage/Blocks/Teaser/DefaultBody.jsx +13 -23
  61. package/src/components/manage/Contents/Contents.jsx +8 -6
  62. package/src/components/manage/Contents/ContentsPropertiesModal.jsx +1 -13
  63. package/src/components/manage/Controlpanels/Groups/RenderGroups.jsx +2 -2
  64. package/src/components/manage/Controlpanels/Relations/BrokenRelations.jsx +30 -7
  65. package/src/components/manage/Controlpanels/Relations/Relations.jsx +2 -2
  66. package/src/components/manage/Controlpanels/Relations/RelationsMatrix.jsx +53 -59
  67. package/src/components/manage/Controlpanels/Users/RenderUsers.jsx +2 -2
  68. package/src/components/manage/Delete/Delete.jsx +96 -171
  69. package/src/components/manage/Sidebar/AlignBlock.jsx +1 -1
  70. package/src/components/manage/Widgets/SelectUtils.js +1 -1
  71. package/src/components/manage/Workflow/Workflow.jsx +75 -184
  72. package/src/components/theme/Image/Image.jsx +96 -0
  73. package/src/components/theme/Image/Image.test.jsx +125 -0
  74. package/src/components/theme/Logo/Logo.jsx +2 -0
  75. package/src/components/theme/PasswordReset/RequestPasswordReset.jsx +95 -170
  76. package/src/components/theme/PreviewImage/PreviewImage.jsx +25 -14
  77. package/src/components/theme/PreviewImage/PreviewImage.test.js +39 -16
  78. package/src/components/theme/View/AlbumView.jsx +11 -15
  79. package/src/components/theme/View/EventView.jsx +30 -23
  80. package/src/components/theme/View/ImageView.jsx +5 -2
  81. package/src/components/theme/View/ImageView.test.jsx +4 -0
  82. package/src/components/theme/View/ListingView.jsx +5 -3
  83. package/src/components/theme/View/NewsItemView.jsx +7 -13
  84. package/src/components/theme/View/SummaryView.jsx +4 -3
  85. package/src/config/Blocks.jsx +2 -0
  86. package/src/config/Components.jsx +3 -1
  87. package/src/config/index.js~ +223 -0
  88. package/src/express-middleware/files.js +8 -6
  89. package/src/express-middleware/images.js +7 -1
  90. package/src/helpers/MessageLabels/MessageLabels.js +6 -0
  91. package/src/helpers/Url/Url.js +22 -1
  92. package/src/helpers/Url/Url.test.js +41 -0
  93. package/src/reducers/relations/relations.js +1 -1
  94. package/test-setup-config.js +9 -1
  95. package/theme/themes/pastanaga/extras/blocks.less +3 -1
  96. package/theme/themes/pastanaga/extras/main.less +5 -0
  97. package/packages/volto-slate/build/messages/src/blocks/Table/TableBlockEdit.json +0 -90
  98. package/packages/volto-slate/build/messages/src/blocks/Text/DefaultTextBlockEditor.json +0 -6
  99. package/packages/volto-slate/build/messages/src/blocks/Text/DetachedTextBlockEditor.json +0 -6
  100. package/packages/volto-slate/build/messages/src/blocks/Text/SlashMenu.json +0 -6
  101. package/packages/volto-slate/build/messages/src/editor/plugins/AdvancedLink/index.json +0 -10
  102. package/packages/volto-slate/build/messages/src/editor/plugins/Link/index.json +0 -10
  103. package/packages/volto-slate/build/messages/src/editor/plugins/Table/index.json +0 -30
  104. package/packages/volto-slate/build/messages/src/elementEditor/messages.json +0 -10
  105. package/packages/volto-slate/build/messages/src/widgets/HtmlSlateWidget.json +0 -6
  106. package/packages/volto-slate/build/messages/src/widgets/RichTextWidgetView.json +0 -6
  107. package/src/components/manage/Blocks/Teaser/utils.js +0 -44
  108. package/src/components/manage/Blocks/Teaser/utils.test.jsx +0 -229
@@ -1,11 +1,12 @@
1
1
  import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
- import { Segment } from 'semantic-ui-react';
4
- import { useIntl, FormattedMessage } from 'react-intl';
5
- import { BlockDataForm, Icon } from '@plone/volto/components';
6
- import { isInternalURL, flattenToAppURL } from '@plone/volto/helpers';
3
+ import { Segment, Button } from 'semantic-ui-react';
4
+ import { useIntl, FormattedMessage, defineMessages } from 'react-intl';
5
+ import { flattenToAppURL, isInternalURL } from '@plone/volto/helpers';
6
+ import { BlockDataForm, Icon, Image } from '@plone/volto/components';
7
7
  import { ImageSchema } from './schema';
8
8
  import imageSVG from '@plone/volto/icons/image.svg';
9
+ import trashSVG from '@plone/volto/icons/delete.svg';
9
10
 
10
11
  const ImageSidebar = (props) => {
11
12
  const { blocksConfig, data, block, onChangeBlock } = props;
@@ -17,21 +18,58 @@ const ImageSidebar = (props) => {
17
18
  <h2>
18
19
  <FormattedMessage id="Image" defaultMessage="Image" />
19
20
  </h2>
21
+ <Button.Group>
22
+ <Button
23
+ title={intl.formatMessage(messages.clear)}
24
+ basic
25
+ disabled={!data.url}
26
+ onClick={() => {
27
+ onChangeBlock(block, {
28
+ ...data,
29
+ url: undefined,
30
+ image_scales: undefined,
31
+ image_field: undefined,
32
+ alt: data.url.title === data.alt ? undefined : data.alt,
33
+ });
34
+ }}
35
+ >
36
+ <Icon name={trashSVG} size="24px" color="red" />
37
+ </Button>
38
+ </Button.Group>
20
39
  </header>
21
40
 
22
- <Segment className="sidebar-metadata-container" secondary attached>
41
+ <Segment
42
+ className="sidebar-metadata-container image-sidebar"
43
+ secondary
44
+ attached
45
+ >
23
46
  {data.url ? (
24
47
  <>
25
- {data.url.split('/').slice(-1)[0]}
26
- {isInternalURL(data.url) && (
27
- <img
28
- src={`${flattenToAppURL(data.url)}/@@images/image/mini`}
29
- alt={data.alt}
30
- />
31
- )}
32
- {!isInternalURL(data.url) && (
33
- <img src={data.url} alt={data.alt} style={{ width: '50%' }} />
34
- )}
48
+ <div>{(data.url?.['@id'] ?? data.url).split('/').slice(-1)[0]}</div>
49
+ <Image
50
+ item={
51
+ data.image_scales
52
+ ? {
53
+ '@id': data.url,
54
+ image_field: data.image_field,
55
+ image_scales: data.image_scales,
56
+ }
57
+ : undefined
58
+ }
59
+ src={
60
+ data.image_scales
61
+ ? undefined
62
+ : isInternalURL(data.url)
63
+ ? // Backwards compat in the case that the block is storing the full server URL
64
+ `${flattenToAppURL(data.url)}/@@images/image/preview`
65
+ : data.url
66
+ }
67
+ sizes="188px"
68
+ alt={intl.formatMessage(messages.preview)}
69
+ loading="lazy"
70
+ responsive={true}
71
+ style={{ width: '50%' }}
72
+ />
35
73
  </>
36
74
  ) : (
37
75
  <>
@@ -68,3 +106,14 @@ ImageSidebar.propTypes = {
68
106
  };
69
107
 
70
108
  export default ImageSidebar;
109
+
110
+ const messages = defineMessages({
111
+ preview: {
112
+ id: 'image_block_preview',
113
+ defaultMessage: 'Image preview',
114
+ },
115
+ clear: {
116
+ id: 'image_block_clear',
117
+ defaultMessage: 'Clear image',
118
+ },
119
+ });
@@ -7,16 +7,23 @@ 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
- import { withBlockExtensions } from '@plone/volto/helpers';
11
- import { flattenToAppURL, isInternalURL } from '@plone/volto/helpers';
10
+ import {
11
+ flattenToAppURL,
12
+ isInternalURL,
13
+ withBlockExtensions,
14
+ } from '@plone/volto/helpers';
15
+ import config from '@plone/volto/registry';
12
16
 
13
17
  /**
14
18
  * View image block class.
15
19
  * @class View
16
20
  * @extends Component
17
21
  */
18
- export const View = ({ data, detached }) => {
22
+ export const View = ({ className, data, detached, properties }) => {
19
23
  const href = data?.href?.[0]?.['@id'] || '';
24
+
25
+ const Image = config.getComponent({ name: 'Image' }).component;
26
+
20
27
  return (
21
28
  <p
22
29
  className={cx(
@@ -26,21 +33,33 @@ export const View = ({ data, detached }) => {
26
33
  detached,
27
34
  },
28
35
  data.align,
36
+ className,
29
37
  )}
30
38
  >
31
39
  {data.url && (
32
40
  <>
33
41
  {(() => {
34
42
  const image = (
35
- <img
43
+ <Image
36
44
  className={cx({
37
45
  'full-width': data.align === 'full',
38
46
  large: data.size === 'l',
39
47
  medium: data.size === 'm',
40
48
  small: data.size === 's',
41
49
  })}
50
+ item={
51
+ data.image_scales
52
+ ? {
53
+ '@id': data.url,
54
+ image_field: data.image_field,
55
+ image_scales: data.image_scales,
56
+ }
57
+ : undefined
58
+ }
42
59
  src={
43
- isInternalURL(data.url)
60
+ data.image_scales
61
+ ? undefined
62
+ : isInternalURL(data.url)
44
63
  ? // Backwards compat in the case that the block is storing the full server URL
45
64
  (() => {
46
65
  if (data.size === 'l')
@@ -57,8 +76,10 @@ export const View = ({ data, detached }) => {
57
76
  })()
58
77
  : data.url
59
78
  }
79
+ sizes={config.blocks.blocksConfig.image.getSizes(data)}
60
80
  alt={data.alt || ''}
61
81
  loading="lazy"
82
+ responsive={true}
62
83
  />
63
84
  );
64
85
  if (href) {
@@ -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 ? ['url', 'alt', 'align', 'size'] : [])],
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 { flattenToAppURL } from '@plone/volto/helpers';
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
- {!properties.image && (
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
- {properties.image && (
111
+ {hasImage && hasImageData && (
106
112
  <img
107
- className={cx({ 'full-width': data.align === 'full' })}
108
- src={
109
- properties.image.data
110
- ? `data:${properties.image['content-type']};base64,${properties.image.data}`
111
- : flattenToAppURL(properties.image.download)
112
- }
113
- alt={data.image_caption || ''}
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 { CheckboxWidget, Icon, TextWidget } from '@plone/volto/components';
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
- <img
94
- src={
95
- properties.image.data
96
- ? `data:${properties.image['content-type']};base64,${properties.image.data}`
97
- : flattenToAppURL(properties.image.scales.mini.download)
98
- }
99
- alt={properties.image_caption || ''}
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
- mini: {
25
- download: 'image.png',
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
- <p
20
- className={cx(
21
- 'block image align',
22
- {
23
- center: !Boolean(data.align),
24
- },
25
- data.align,
26
- )}
27
- >
28
- {properties.image && (
29
- <>
30
- {(() => {
31
- const image = (
32
- <img
33
- className={cx({ 'full-width': data.align === 'full' })}
34
- src={flattenToAppURL(properties.image.download)}
35
- alt={properties.image_caption || ''}
36
- />
37
- );
38
- if (data.href) {
39
- return (
40
- <UniversalLink
41
- href={data.href}
42
- openLinkInNewTab={data.openLinkInNewTab}
43
- >
44
- {image}
45
- </UniversalLink>
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
- } else {
48
- return image;
49
- }
50
- })()}
51
- </>
52
- )}
53
- </p>
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 ? item.title : item.id}</h3>
30
+ <h3>{item.title || item.id}</h3>
31
31
  <p>{item.description}</p>
32
32
  </div>
33
33
  </ConditionalLink>