@plone/volto 18.5.0 → 18.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.
@@ -3,7 +3,7 @@
3
3
  * @module components/manage/Widgets/RegistryImageWidget
4
4
  */
5
5
 
6
- import React from 'react';
6
+ import React, { useState } from 'react';
7
7
  import PropTypes from 'prop-types';
8
8
  import { Button, Image, Dimmer } from 'semantic-ui-react';
9
9
  import { readAsDataURL } from 'promise-file-reader';
@@ -76,12 +76,15 @@ const RegistryImageWidget = (props) => {
76
76
  const { id, value, onChange, isDisabled } = props;
77
77
  const intl = useIntl();
78
78
 
79
- const fileName = value?.split(';')[0];
80
- const imgsrc = fileName
81
- ? `${toPublicURL('/')}@@site-logo/${atob(
82
- fileName.replace('filenameb64:', ''),
83
- )}`
84
- : '';
79
+ // State to manage the preview image source
80
+ const [previewSrc, setPreviewSrc] = useState(() => {
81
+ const fileName = value?.split(';')[0];
82
+ return fileName
83
+ ? `${toPublicURL('/')}@@site-logo/${atob(
84
+ fileName.replace('filenameb64:', ''),
85
+ )}`
86
+ : '';
87
+ });
85
88
 
86
89
  /**
87
90
  * Drop handler
@@ -102,8 +105,7 @@ const RegistryImageWidget = (props) => {
102
105
  reader.onload = function () {
103
106
  const fields = reader.result.match(/^data:(.*);(.*),(.*)$/);
104
107
  if (imageMimetypes.includes(fields[1])) {
105
- let imagePreview = document.getElementById(`field-${id}-image`);
106
- imagePreview.src = reader.result;
108
+ setPreviewSrc(reader.result);
107
109
  }
108
110
  };
109
111
  reader.readAsDataURL(files[0]);
@@ -115,12 +117,12 @@ const RegistryImageWidget = (props) => {
115
117
  {({ getRootProps, getInputProps, isDragActive }) => (
116
118
  <div className="file-widget-dropzone" {...getRootProps()}>
117
119
  {isDragActive && <Dimmer active></Dimmer>}
118
- {imgsrc ? (
120
+ {previewSrc ? (
119
121
  <Image
120
122
  className="image-preview"
121
123
  id={`field-${id}-image`}
122
124
  size="small"
123
- src={imgsrc}
125
+ src={previewSrc}
124
126
  />
125
127
  ) : (
126
128
  <div className="dropzone-placeholder">
@@ -139,7 +141,6 @@ const RegistryImageWidget = (props) => {
139
141
  )}
140
142
  </div>
141
143
  )}
142
-
143
144
  <label className="label-file-widget-input">
144
145
  {value
145
146
  ? intl.formatMessage(messages.replaceFile)
@@ -168,6 +169,7 @@ const RegistryImageWidget = (props) => {
168
169
  disabled={isDisabled}
169
170
  onClick={() => {
170
171
  onChange(id, '');
172
+ setPreviewSrc(''); // Clear the preview image
171
173
  }}
172
174
  >
173
175
  <Icon name={deleteSVG} size="20px" />
@@ -189,10 +191,7 @@ RegistryImageWidget.propTypes = {
189
191
  description: PropTypes.string,
190
192
  required: PropTypes.bool,
191
193
  error: PropTypes.arrayOf(PropTypes.string),
192
- value: PropTypes.shape({
193
- '@type': PropTypes.string,
194
- title: PropTypes.string,
195
- }),
194
+ value: PropTypes.string,
196
195
  onChange: PropTypes.func.isRequired,
197
196
  wrapped: PropTypes.bool,
198
197
  };
@@ -0,0 +1,23 @@
1
+ import config from '@plone/volto/registry';
2
+ import Helmet from '@plone/volto/helpers/Helmet/Helmet';
3
+
4
+ const AlternateHrefLangs = (props) => {
5
+ const { content } = props;
6
+ return (
7
+ <Helmet>
8
+ {config.settings.isMultilingual &&
9
+ content['@components']?.translations?.items?.map((item, key) => {
10
+ return (
11
+ <link
12
+ key={key}
13
+ rel="alternate"
14
+ hrefLang={item.language}
15
+ href={item['@id']}
16
+ />
17
+ );
18
+ })}
19
+ </Helmet>
20
+ );
21
+ };
22
+
23
+ export { AlternateHrefLangs };
@@ -0,0 +1,135 @@
1
+ import React from 'react';
2
+ import Helmet from '@plone/volto/helpers/Helmet/Helmet';
3
+
4
+ import renderer from 'react-test-renderer';
5
+ import configureStore from 'redux-mock-store';
6
+ import { Provider } from 'react-intl-redux';
7
+ import config from '@plone/volto/registry';
8
+
9
+ import { AlternateHrefLangs } from './AlternateHrefLangs';
10
+
11
+ const mockStore = configureStore();
12
+
13
+ describe('AlternateHrefLangs', () => {
14
+ beforeEach(() => {});
15
+ it('non multilingual site, renders nothing', () => {
16
+ config.settings.isMultilingual = false;
17
+ const content = {
18
+ '@id': '/',
19
+ '@components': {},
20
+ };
21
+ const store = mockStore({
22
+ intl: {
23
+ locale: 'en',
24
+ messages: {},
25
+ },
26
+ });
27
+ // We need to force the component rendering
28
+ // to fill the Helmet
29
+ renderer.create(
30
+ <Provider store={store}>
31
+ <AlternateHrefLangs content={content} />
32
+ </Provider>,
33
+ );
34
+
35
+ const helmetLinks = Helmet.peek().linkTags;
36
+ expect(helmetLinks.length).toBe(0);
37
+ });
38
+ it('multilingual site, with some translations', () => {
39
+ config.settings.isMultilingual = true;
40
+ config.settings.supportedLanguages = ['en', 'es', 'eu'];
41
+
42
+ const content = {
43
+ '@components': {
44
+ translations: {
45
+ items: [
46
+ { '@id': '/en', language: 'en' },
47
+ { '@id': '/es', language: 'es' },
48
+ ],
49
+ },
50
+ },
51
+ };
52
+
53
+ const store = mockStore({
54
+ intl: {
55
+ locale: 'en',
56
+ messages: {},
57
+ },
58
+ });
59
+
60
+ // We need to force the component rendering
61
+ // to fill the Helmet
62
+ renderer.create(
63
+ <Provider store={store}>
64
+ <>
65
+ <AlternateHrefLangs content={content} />
66
+ </>
67
+ </Provider>,
68
+ );
69
+ const helmetLinks = Helmet.peek().linkTags;
70
+
71
+ expect(helmetLinks.length).toBe(2);
72
+
73
+ expect(helmetLinks).toContainEqual({
74
+ rel: 'alternate',
75
+ href: '/es',
76
+ hrefLang: 'es',
77
+ });
78
+ expect(helmetLinks).toContainEqual({
79
+ rel: 'alternate',
80
+ href: '/en',
81
+ hrefLang: 'en',
82
+ });
83
+ });
84
+ it('multilingual site, with all available translations', () => {
85
+ config.settings.isMultilingual = true;
86
+ config.settings.supportedLanguages = ['en', 'es', 'eu'];
87
+ const store = mockStore({
88
+ intl: {
89
+ locale: 'en',
90
+ messages: {},
91
+ },
92
+ });
93
+
94
+ const content = {
95
+ '@components': {
96
+ translations: {
97
+ items: [
98
+ { '@id': '/en', language: 'en' },
99
+ { '@id': '/eu', language: 'eu' },
100
+ { '@id': '/es', language: 'es' },
101
+ ],
102
+ },
103
+ },
104
+ };
105
+
106
+ // We need to force the component rendering
107
+ // to fill the Helmet
108
+ renderer.create(
109
+ <Provider store={store}>
110
+ <AlternateHrefLangs content={content} />
111
+ </Provider>,
112
+ );
113
+
114
+ const helmetLinks = Helmet.peek().linkTags;
115
+
116
+ // We expect having 3 links
117
+ expect(helmetLinks.length).toBe(3);
118
+
119
+ expect(helmetLinks).toContainEqual({
120
+ rel: 'alternate',
121
+ href: '/eu',
122
+ hrefLang: 'eu',
123
+ });
124
+ expect(helmetLinks).toContainEqual({
125
+ rel: 'alternate',
126
+ href: '/es',
127
+ hrefLang: 'es',
128
+ });
129
+ expect(helmetLinks).toContainEqual({
130
+ rel: 'alternate',
131
+ href: '/en',
132
+ hrefLang: 'en',
133
+ });
134
+ });
135
+ });
@@ -21,6 +21,7 @@ import BodyClass from '@plone/volto/helpers/BodyClass/BodyClass';
21
21
  import { getBaseUrl, flattenToAppURL } from '@plone/volto/helpers/Url/Url';
22
22
  import { getLayoutFieldname } from '@plone/volto/helpers/Content/Content';
23
23
  import { hasApiExpander } from '@plone/volto/helpers/Utils/Utils';
24
+ import { AlternateHrefLangs } from '@plone/volto/components/theme/AlternateHrefLangs/AlternateHrefLangs';
24
25
 
25
26
  import config from '@plone/volto/registry';
26
27
  import SlotRenderer from '../SlotRenderer/SlotRenderer';
@@ -234,6 +235,7 @@ class View extends Component {
234
235
  return (
235
236
  <div id="view" tabIndex="-1">
236
237
  <ContentMetadataTags content={this.props.content} />
238
+ <AlternateHrefLangs content={this.props.content} />
237
239
  {/* Body class if displayName in component is set */}
238
240
  <BodyClass
239
241
  className={
@@ -19,7 +19,6 @@ module.exports = {
19
19
  pt: 'Português',
20
20
  pt_BR: 'Português (Brasil)',
21
21
  zh_CN: '中文',
22
- sv: 'Svenska',
23
22
  };
24
23
 
25
24
  // export default languages;
@@ -13,9 +13,13 @@ export default function useClipboard(clipboardText = '') {
13
13
  }
14
14
  };
15
15
 
16
- const copyAction = useCallback(() => {
17
- const copiedString = copyToClipboard(stringToCopy.current);
18
- setCopied(copiedString);
16
+ const copyAction = useCallback(async () => {
17
+ try {
18
+ await copyToClipboard(stringToCopy.current);
19
+ setCopied(true);
20
+ } catch (error) {
21
+ setCopied(false);
22
+ }
19
23
  }, [stringToCopy]);
20
24
 
21
25
  useEffect(() => {
@@ -43,6 +43,7 @@ export class ObjectBrowserWidgetComponent extends React.Component<any, any, any>
43
43
  state: {
44
44
  manualLinkInput: string;
45
45
  validURL: boolean;
46
+ errors: any[];
46
47
  };
47
48
  selectedItemsRef: React.RefObject<any>;
48
49
  placeholderRef: React.RefObject<any>;
@@ -0,0 +1 @@
1
+ export function AlternateHrefLangs(props: any): import("react/jsx-runtime").JSX.Element;
@@ -13,4 +13,3 @@ export let ja: string;
13
13
  export let pt: string;
14
14
  export let pt_BR: string;
15
15
  export let zh_CN: string;
16
- export let sv: string;