@plone/volto 19.0.0-alpha.1 → 19.0.0-alpha.11
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/.eslintignore +1 -0
- package/.eslintrc +37 -2
- package/CHANGELOG.md +234 -0
- package/README.md +18 -14
- package/cypress/docker/prefixed-rules.yml +26 -0
- package/cypress/docker/prefixed.yml +24 -0
- package/cypress/support/commands.js +12 -6
- package/cypress/support/guillotina.js +1 -0
- package/cypress.config.js +1 -0
- package/locales/af.json +1 -0
- package/locales/ar.json +1 -0
- package/locales/bg.json +1 -0
- package/locales/bn.json +1 -0
- package/locales/ca/LC_MESSAGES/volto.po +96 -17
- package/locales/ca.json +1 -1
- package/locales/cs.json +1 -0
- package/locales/cy.json +1 -0
- package/locales/da.json +1 -0
- package/locales/de/LC_MESSAGES/volto.po +104 -25
- package/locales/de.json +1 -1
- package/locales/el.json +1 -0
- package/locales/en/LC_MESSAGES/volto.po +97 -18
- package/locales/en.json +1 -1
- package/locales/en_AU.json +1 -0
- package/locales/en_GB.json +1 -0
- package/locales/eo.json +1 -0
- package/locales/es/LC_MESSAGES/volto.po +97 -18
- package/locales/es.json +1 -1
- package/locales/et.json +1 -0
- package/locales/eu/LC_MESSAGES/volto.po +96 -17
- package/locales/eu.json +1 -1
- package/locales/fa.json +1 -0
- package/locales/fi/LC_MESSAGES/volto.po +96 -17
- package/locales/fi.json +1 -1
- package/locales/fr/LC_MESSAGES/volto.po +97 -18
- package/locales/fr.json +1 -1
- package/locales/fu.json +1 -0
- package/locales/gl.json +1 -0
- package/locales/he.json +1 -0
- package/locales/hi/LC_MESSAGES/volto.po +100 -21
- package/locales/hi.json +1 -1
- package/locales/hr.json +1 -0
- package/locales/hu.json +1 -0
- package/locales/hy.json +1 -0
- package/locales/id.json +1 -0
- package/locales/it/LC_MESSAGES/volto.po +101 -22
- package/locales/it.json +1 -1
- package/locales/ja/LC_MESSAGES/volto.po +96 -17
- package/locales/ja.json +1 -1
- package/locales/ka.json +1 -0
- package/locales/kn.json +1 -0
- package/locales/ko.json +1 -0
- package/locales/lt.json +1 -0
- package/locales/lv.json +1 -0
- package/locales/mi.json +1 -0
- package/locales/mk.json +1 -0
- package/locales/my.json +1 -0
- package/locales/nb_NO.json +1 -0
- package/locales/nl/LC_MESSAGES/volto.po +100 -21
- package/locales/nl.json +1 -1
- package/locales/nn.json +1 -0
- package/locales/pl.json +1 -0
- package/locales/pt/LC_MESSAGES/volto.po +96 -17
- package/locales/pt.json +1 -1
- package/locales/pt_BR/LC_MESSAGES/volto.po +116 -37
- package/locales/pt_BR.json +1 -1
- package/locales/rm.json +1 -0
- package/locales/ro/LC_MESSAGES/volto.po +100 -21
- package/locales/ro.json +1 -1
- package/locales/ru/LC_MESSAGES/volto.po +100 -21
- package/locales/ru.json +1 -1
- package/locales/sk.json +1 -0
- package/locales/sl.json +1 -0
- package/locales/sm.json +1 -0
- package/locales/sq.json +1 -0
- package/locales/sr.json +1 -0
- package/locales/sr@cyrl.json +1 -0
- package/locales/sr@latn.json +1 -0
- package/locales/sv.json +1 -1
- package/locales/ta.json +1 -0
- package/locales/te.json +1 -0
- package/locales/th.json +1 -0
- package/locales/to.json +1 -0
- package/locales/tr.json +1 -0
- package/locales/uk.json +1 -0
- package/locales/vi.json +1 -0
- package/locales/volto.pot +97 -18
- package/locales/zh_CN/LC_MESSAGES/volto.po +96 -17
- package/locales/zh_CN.json +1 -1
- package/locales/zh_Hant.json +1 -0
- package/locales/zh_Hant_HK.json +1 -0
- package/package.json +28 -26
- package/razzle.config.js +16 -0
- package/src/actions/content/content.js +0 -1
- package/src/actions/controlpanels/controlpanels.js +13 -7
- package/src/actions/controlpanels/controlpanels.test.js +11 -5
- package/src/actions/users/users.js +2 -2
- package/src/components/manage/Add/Add.jsx +5 -6
- package/src/components/manage/Blocks/Block/Edit.jsx +1 -0
- package/src/components/manage/Blocks/Block/EditBlockWrapper.jsx +9 -4
- package/src/components/manage/Blocks/Image/Edit.jsx +5 -1
- package/src/components/manage/Blocks/LeadImage/Edit.jsx +2 -2
- package/src/components/manage/Blocks/LeadImage/LeadImageSidebar.jsx +1 -1
- package/src/components/manage/Blocks/Listing/ImageGallery.jsx +6 -4
- package/src/components/manage/Blocks/Maps/Edit.jsx +2 -1
- package/src/components/manage/Blocks/Search/SearchBlockView.jsx +21 -4
- package/src/components/manage/Blocks/Teaser/Data.jsx +21 -7
- package/src/components/manage/Blocks/Teaser/DefaultBody.jsx +1 -1
- package/src/components/manage/Blocks/Teaser/schema.js +8 -3
- package/src/components/manage/Blocks/Title/Edit.jsx +8 -2
- package/src/components/manage/Blocks/Video/Edit.jsx +2 -1
- package/src/components/manage/Contents/Contents.jsx +23 -2
- package/src/components/manage/Contents/Contents.test.jsx +7 -0
- package/src/components/manage/Contents/ContentsBreadcrumbs.Multilingual.test.jsx +18 -5
- package/src/components/manage/Contents/ContentsBreadcrumbs.jsx +20 -26
- package/src/components/manage/Contents/ContentsBreadcrumbs.test.jsx +14 -0
- package/src/components/manage/Contents/ContentsDeleteModal.jsx +258 -206
- package/src/components/manage/Contents/ContentsDeleteModal.stories.jsx +26 -8
- package/src/components/manage/Contents/ContentsItem.jsx +10 -2
- package/src/components/manage/Contents/ContentsUploadModal.test.jsx +13 -22
- package/src/components/manage/Controlpanels/ContentType.jsx +1 -1
- package/src/components/manage/Controlpanels/Groups/GroupsControlpanel.jsx +3 -2
- package/src/components/manage/Controlpanels/Users/RenderUsers.jsx +156 -175
- package/src/components/manage/Controlpanels/Users/UsersControlpanel.jsx +575 -630
- package/src/components/manage/Controlpanels/Users/UsersControlpanel.test.jsx +4 -3
- package/src/components/manage/Edit/Edit.jsx +2 -3
- package/src/components/manage/Form/Form.jsx +32 -0
- package/src/components/manage/Form/Form.test.jsx +22 -18
- package/src/components/manage/Form/InlineForm.jsx +2 -2
- package/src/components/manage/Multilingual/CompareLanguages.jsx +2 -5
- package/src/components/manage/Multilingual/CreateTranslation.jsx +8 -8
- package/src/components/manage/Multilingual/ManageTranslations.jsx +4 -2
- package/src/components/manage/Multilingual/ManageTranslations.test.jsx +5 -1
- package/src/components/manage/Multilingual/TranslationObject.jsx +1 -1
- package/src/components/manage/Sidebar/ObjectBrowserNav.jsx +2 -1
- package/src/components/manage/Sidebar/SidebarPortal.test.tsx +42 -0
- package/src/components/manage/Sidebar/SidebarPortal.tsx +48 -0
- package/src/components/manage/TemplateChooser/TemplateChooser.jsx +2 -1
- package/src/components/manage/Toolbar/More.jsx +4 -1
- package/src/components/manage/Toolbar/More.test.jsx +3 -0
- package/src/components/manage/Toolbar/PersonalTools.jsx +2 -1
- package/src/components/manage/Toolbar/Toolbar.jsx +3 -4
- package/src/components/manage/Toolbar/Types.jsx +7 -7
- package/src/components/manage/UniversalLink/UniversalLink.tsx +1 -0
- package/src/components/manage/Widgets/DatetimeWidget.jsx +5 -0
- package/src/components/manage/Widgets/FileWidget.jsx +14 -8
- package/src/components/manage/Widgets/ImageWidget.jsx +171 -38
- package/src/components/manage/Widgets/InternalUrlWidget.jsx +2 -0
- package/src/components/manage/Widgets/ObjectBrowserWidget.jsx +3 -0
- package/src/components/manage/Widgets/RegistryImageWidget.test.jsx +3 -2
- package/src/components/manage/Widgets/SelectAutoComplete.jsx +29 -12
- package/src/components/manage/Widgets/SelectWidget.jsx +3 -1
- package/src/components/manage/Widgets/UrlWidget.jsx +2 -0
- package/src/components/theme/AlternateHrefLangs/AlternateHrefLangs.jsx +1 -3
- package/src/components/theme/AlternateHrefLangs/AlternateHrefLangs.test.jsx +0 -4
- package/src/components/theme/App/App.jsx +3 -1
- package/src/components/theme/App/App.test.jsx +1 -0
- package/src/components/theme/Avatar/Avatar.jsx +2 -1
- package/src/components/theme/FormattedDate/FormattedDate.stories.jsx +20 -2
- package/src/components/theme/Image/Image.jsx +11 -8
- package/src/components/theme/LanguageSelector/{LanguageSelector.test.jsx → LanguageSelector.test.tsx} +6 -6
- package/src/components/theme/LanguageSelector/LanguageSelector.tsx +89 -0
- package/src/components/theme/Logo/Logo.Multilingual.test.jsx +0 -5
- package/src/components/theme/MultilingualRedirector/MultilingualRedirector.jsx +8 -12
- package/src/components/theme/MultilingualRedirector/MultilingualRedirector.test.jsx +3 -5
- package/src/components/theme/Navigation/NavItem.jsx +1 -5
- package/src/components/theme/Navigation/Navigation.Multilingual.test.jsx +0 -5
- package/src/components/theme/NotFound/NotFound.jsx +5 -2
- package/src/components/theme/NotFound/NotFound.test.jsx +3 -0
- package/src/components/theme/PreviewImage/PreviewImage.jsx +1 -1
- package/src/components/theme/RequestTimeout/RequestTimeout.jsx +1 -1
- package/src/components/theme/Sitemap/Sitemap.jsx +6 -5
- package/src/components/theme/Sitemap/Sitemap.test.jsx +0 -1
- package/src/components/theme/View/FileView.jsx +9 -1
- package/src/components/theme/View/View.jsx +1 -1
- package/src/components/theme/Widgets/ImageWidget.jsx +2 -1
- package/src/config/ControlPanels.js +1 -0
- package/src/config/index.js +18 -25
- package/src/config/server.js +0 -2
- package/src/express-middleware/devproxy.js +20 -5
- package/src/helpers/Api/APIResourceWithAuth.js +8 -3
- package/src/helpers/Api/Api.js +7 -4
- package/src/helpers/AsyncConnect/ssr.js +4 -1
- package/src/helpers/Content/Content.js +23 -0
- package/src/helpers/Content/Content.test.js +39 -0
- package/src/helpers/Content/withClientSideContent.jsx +35 -0
- package/src/helpers/Extensions/withBlockSchemaEnhancer.jsx +4 -1
- package/src/helpers/Html/Html.jsx +13 -7
- package/src/helpers/LanguageMap/LanguageMap.js +115 -8
- package/src/helpers/MessageLabels/MessageLabels.js +5 -0
- package/src/helpers/Sitemap/Sitemap.js +4 -4
- package/src/helpers/Url/Url.js +32 -2
- package/src/helpers/Url/Url.test.js +62 -0
- package/src/helpers/Utils/withSaveAsDraft.jsx +241 -0
- package/src/hooks/user/useUser.js +1 -1
- package/src/middleware/Api.test.js +4 -0
- package/src/middleware/api.js +77 -28
- package/src/middleware/storeProtectLoadUtils.test.js +3 -3
- package/src/reducers/content/content.js +3 -18
- package/src/reducers/diff/diff.js +5 -1
- package/src/reducers/diff/diff.test.js +60 -4
- package/src/routes.js +4 -2
- package/src/server.jsx +45 -14
- package/src/start-client.jsx +9 -6
- package/src/start-server.js +9 -3
- package/test-setup-config.jsx +0 -2
- package/theme/themes/pastanaga/collections/table.overrides +9 -0
- package/theme/themes/pastanaga/extras/blocks.less +26 -0
- package/theme/themes/pastanaga/extras/contents.less +17 -5
- package/theme/themes/pastanaga/extras/main.less +32 -2
- package/tsconfig.json +3 -4
- package/types/components/manage/Blocks/Teaser/schema.d.ts +1 -0
- package/types/components/manage/Controlpanels/Users/RenderUsers.d.ts +18 -2
- package/types/components/manage/Controlpanels/Users/UsersControlpanel.d.ts +6 -2
- package/types/components/manage/Controlpanels/index.d.ts +2 -2
- package/types/components/manage/Sidebar/SidebarPortal.d.ts +7 -15
- package/types/components/manage/Widgets/ImageWidget.d.ts +41 -1
- package/types/components/manage/Widgets/RecurrenceWidget/Utils.d.ts +12 -18
- package/types/components/theme/FormattedDate/FormattedDate.stories.d.ts +1 -1
- package/types/components/theme/LanguageSelector/LanguageSelector.d.ts +3 -10
- package/types/helpers/Content/Content.d.ts +7 -0
- package/types/helpers/Content/withClientSideContent.d.ts +1 -0
- package/types/helpers/Extensions/withBlockSchemaEnhancer.d.ts +4 -5
- package/types/helpers/Helmet/Helmet.d.ts +1 -1
- package/types/helpers/LanguageMap/LanguageMap.d.ts +428 -4
- package/types/helpers/MessageLabels/MessageLabels.d.ts +68 -62
- package/types/helpers/Url/Url.d.ts +14 -0
- package/types/helpers/Url/bulkFlattenToAppURL.d.ts +5 -0
- package/types/helpers/Utils/withSaveAsDraft.d.ts +1 -0
- package/types/middleware/api.d.ts +6 -9
- package/types/reducers/index.d.ts +1 -0
- package/types/start-client.d.ts +0 -1
- package/package-why.json +0 -34
- package/src/actions/content/content.multilingual.test.js +0 -17
- package/src/components/manage/Sidebar/SidebarPortal.jsx +0 -47
- package/src/components/manage/Sidebar/SidebarPortal.test.jsx +0 -26
- package/src/components/theme/LanguageSelector/LanguageSelector.jsx +0 -77
- package/theme/themes/pastanaga/extras/utils.less +0 -63
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { useEffect, useRef } from 'react';
|
|
2
2
|
import { Button, Dimmer, Loader, Message } from 'semantic-ui-react';
|
|
3
3
|
import { useIntl, defineMessages } from 'react-intl';
|
|
4
|
-
import { useDispatch } from 'react-redux';
|
|
4
|
+
import { useDispatch, useSelector } from 'react-redux';
|
|
5
5
|
import { useLocation } from 'react-router-dom';
|
|
6
6
|
import loadable from '@loadable/component';
|
|
7
7
|
import { connect } from 'react-redux';
|
|
@@ -9,11 +9,15 @@ import { compose } from 'redux';
|
|
|
9
9
|
import { toast } from 'react-toastify';
|
|
10
10
|
import useLinkEditor from '@plone/volto/components/manage/AnchorPlugin/useLinkEditor';
|
|
11
11
|
import withObjectBrowser from '@plone/volto/components/manage/Sidebar/ObjectBrowser';
|
|
12
|
+
import config from '@plone/volto/registry';
|
|
12
13
|
|
|
13
14
|
import {
|
|
14
15
|
flattenToAppURL,
|
|
15
16
|
getBaseUrl,
|
|
17
|
+
getParentUrl,
|
|
16
18
|
isInternalURL,
|
|
19
|
+
normalizeUrl,
|
|
20
|
+
removeProtocol,
|
|
17
21
|
} from '@plone/volto/helpers/Url/Url';
|
|
18
22
|
import { validateFileUploadSize } from '@plone/volto/helpers/FormValidation/FormValidation';
|
|
19
23
|
import { usePrevious } from '@plone/volto/helpers/Utils/usePrevious';
|
|
@@ -28,6 +32,9 @@ import clearSVG from '@plone/volto/icons/clear.svg';
|
|
|
28
32
|
import navTreeSVG from '@plone/volto/icons/nav.svg';
|
|
29
33
|
import linkSVG from '@plone/volto/icons/link.svg';
|
|
30
34
|
import uploadSVG from '@plone/volto/icons/upload.svg';
|
|
35
|
+
import Image from '../../theme/Image/Image';
|
|
36
|
+
import { urlValidator } from '@plone/volto/helpers/FormValidation/validators';
|
|
37
|
+
import { searchContent } from '@plone/volto/actions/search/search';
|
|
31
38
|
|
|
32
39
|
const Dropzone = loadable(() => import('react-dropzone'));
|
|
33
40
|
|
|
@@ -70,6 +77,14 @@ const messages = defineMessages({
|
|
|
70
77
|
id: 'imageUploadErrorMessage',
|
|
71
78
|
defaultMessage: 'Please upload an image instead.',
|
|
72
79
|
},
|
|
80
|
+
externalURLsNotAllowed: {
|
|
81
|
+
id: 'externalURLsNotAllowed',
|
|
82
|
+
defaultMessage: 'External URLs are not allowed in this field.',
|
|
83
|
+
},
|
|
84
|
+
internalImageNotFoundErrorMessage: {
|
|
85
|
+
id: 'internalImageNotFoundErrorMessage',
|
|
86
|
+
defaultMessage: 'No image was found in the internal path you provided.',
|
|
87
|
+
},
|
|
73
88
|
});
|
|
74
89
|
|
|
75
90
|
const UnconnectedImageInput = (props) => {
|
|
@@ -89,12 +104,15 @@ const UnconnectedImageInput = (props) => {
|
|
|
89
104
|
placeholderLinkInput = '',
|
|
90
105
|
onSelectItem,
|
|
91
106
|
} = props;
|
|
92
|
-
const imageValue = value?.[0]?.['@id'] || value;
|
|
107
|
+
const imageValue = value?.[0]?.['@id'] || value?.['@id'] || value;
|
|
93
108
|
|
|
94
109
|
const intl = useIntl();
|
|
95
110
|
const linkEditor = useLinkEditor();
|
|
96
111
|
const location = useLocation();
|
|
97
112
|
const dispatch = useDispatch();
|
|
113
|
+
const isFolderish = useSelector(
|
|
114
|
+
(state) => state?.content?.data?.is_folderish,
|
|
115
|
+
);
|
|
98
116
|
const contextUrl = location.pathname;
|
|
99
117
|
|
|
100
118
|
const [uploading, setUploading] = React.useState(false);
|
|
@@ -109,21 +127,41 @@ const UnconnectedImageInput = (props) => {
|
|
|
109
127
|
const imageId = content?.['@id'];
|
|
110
128
|
const image = content?.image;
|
|
111
129
|
let loading = false;
|
|
130
|
+
const isRelationChoice = props.factory === 'Relation Choice';
|
|
112
131
|
|
|
113
132
|
useEffect(() => {
|
|
114
133
|
if (uploading && loading && loaded) {
|
|
115
134
|
setUploading(false);
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
135
|
+
if (isRelationChoice) {
|
|
136
|
+
onChange(id, content, {
|
|
137
|
+
image_field: 'image',
|
|
138
|
+
image_scales: { image: [image] },
|
|
139
|
+
});
|
|
140
|
+
} else {
|
|
141
|
+
onChange(id, imageId, {
|
|
142
|
+
image_field: 'image',
|
|
143
|
+
image_scales: { image: [image] },
|
|
144
|
+
});
|
|
145
|
+
}
|
|
120
146
|
}
|
|
121
|
-
}, [
|
|
147
|
+
}, [
|
|
148
|
+
loading,
|
|
149
|
+
loaded,
|
|
150
|
+
uploading,
|
|
151
|
+
imageId,
|
|
152
|
+
image,
|
|
153
|
+
id,
|
|
154
|
+
content,
|
|
155
|
+
isRelationChoice,
|
|
156
|
+
onChange,
|
|
157
|
+
]);
|
|
122
158
|
|
|
123
159
|
loading = usePrevious(props.request?.loading);
|
|
124
160
|
|
|
125
161
|
const handleUpload = React.useCallback(
|
|
126
162
|
(eventOrFile) => {
|
|
163
|
+
let uploadUrl = getBaseUrl(contextUrl);
|
|
164
|
+
if (!isFolderish) uploadUrl = getParentUrl(uploadUrl);
|
|
127
165
|
if (restrictFileUpload === true) return;
|
|
128
166
|
eventOrFile.target && eventOrFile.stopPropagation();
|
|
129
167
|
|
|
@@ -139,7 +177,7 @@ const UnconnectedImageInput = (props) => {
|
|
|
139
177
|
const fields = fileData.match(/^data:(.*);(.*),(.*)$/);
|
|
140
178
|
dispatch(
|
|
141
179
|
createContent(
|
|
142
|
-
|
|
180
|
+
uploadUrl,
|
|
143
181
|
{
|
|
144
182
|
'@type': 'Image',
|
|
145
183
|
title: file.name,
|
|
@@ -156,11 +194,12 @@ const UnconnectedImageInput = (props) => {
|
|
|
156
194
|
});
|
|
157
195
|
},
|
|
158
196
|
[
|
|
197
|
+
contextUrl,
|
|
198
|
+
isFolderish,
|
|
159
199
|
restrictFileUpload,
|
|
160
200
|
intl.formatMessage,
|
|
161
201
|
dispatch,
|
|
162
|
-
props,
|
|
163
|
-
contextUrl,
|
|
202
|
+
props.block,
|
|
164
203
|
requestId,
|
|
165
204
|
],
|
|
166
205
|
);
|
|
@@ -170,6 +209,83 @@ const UnconnectedImageInput = (props) => {
|
|
|
170
209
|
}, [restrictFileUpload]);
|
|
171
210
|
const onDragLeave = React.useCallback(() => setDragging(false), []);
|
|
172
211
|
|
|
212
|
+
const validateManualLink = React.useCallback(
|
|
213
|
+
(url) => {
|
|
214
|
+
if (!url.startsWith('/')) {
|
|
215
|
+
const error = urlValidator({
|
|
216
|
+
value: url,
|
|
217
|
+
formatMessage: intl.formatMessage,
|
|
218
|
+
});
|
|
219
|
+
// if (error && url !== '') {
|
|
220
|
+
// this.setState({ errors: [error] });
|
|
221
|
+
// } else {
|
|
222
|
+
// this.setState({ errors: [] });
|
|
223
|
+
// }
|
|
224
|
+
return !Boolean(error);
|
|
225
|
+
} else {
|
|
226
|
+
return isInternalURL(url);
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
[intl.formatMessage],
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
const onSubmitURL = React.useCallback(
|
|
233
|
+
(url) => {
|
|
234
|
+
if (validateManualLink(url)) {
|
|
235
|
+
if (isInternalURL(url)) {
|
|
236
|
+
// convert it into an internal on if possible
|
|
237
|
+
props
|
|
238
|
+
.searchContent(
|
|
239
|
+
'/',
|
|
240
|
+
{
|
|
241
|
+
portal_type: config.settings.imageObjects,
|
|
242
|
+
'path.query': flattenToAppURL(url),
|
|
243
|
+
'path.depth': '0',
|
|
244
|
+
sort_on: 'getObjPositionInParent',
|
|
245
|
+
metadata_fields: '_all',
|
|
246
|
+
b_size: 1000,
|
|
247
|
+
},
|
|
248
|
+
`${props.block}-${props.mode}`,
|
|
249
|
+
)
|
|
250
|
+
.then((resp) => {
|
|
251
|
+
if (resp.items?.length > 0) {
|
|
252
|
+
onChange(props.id, resp.items[0], {});
|
|
253
|
+
} else {
|
|
254
|
+
toast.error(
|
|
255
|
+
<Toast
|
|
256
|
+
error
|
|
257
|
+
title={intl.formatMessage(messages.Error)}
|
|
258
|
+
content={intl.formatMessage(
|
|
259
|
+
messages.internalImageNotFoundErrorMessage,
|
|
260
|
+
)}
|
|
261
|
+
/>,
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
} else {
|
|
266
|
+
if (isRelationChoice) {
|
|
267
|
+
toast.error(
|
|
268
|
+
<Toast
|
|
269
|
+
error
|
|
270
|
+
title={intl.formatMessage(messages.Error)}
|
|
271
|
+
content={intl.formatMessage(messages.imageUploadErrorMessage)}
|
|
272
|
+
/>,
|
|
273
|
+
);
|
|
274
|
+
} else {
|
|
275
|
+
// if it's an external link, we save it as is
|
|
276
|
+
onChange(props.id, [
|
|
277
|
+
{
|
|
278
|
+
'@id': normalizeUrl(url),
|
|
279
|
+
title: removeProtocol(url),
|
|
280
|
+
},
|
|
281
|
+
]);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
},
|
|
286
|
+
[validateManualLink, props, intl, isRelationChoice, onChange],
|
|
287
|
+
);
|
|
288
|
+
|
|
173
289
|
return imageValue ? (
|
|
174
290
|
<div
|
|
175
291
|
className="image-upload-widget-image"
|
|
@@ -178,15 +294,20 @@ const UnconnectedImageInput = (props) => {
|
|
|
178
294
|
role="toolbar"
|
|
179
295
|
>
|
|
180
296
|
{selected && <ImageToolbar {...props} />}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
297
|
+
{/* If it's relation choice (preview_image_link) */}
|
|
298
|
+
{isRelationChoice ? (
|
|
299
|
+
<Image item={value} width="fit-content" height="auto" loading="lazy" />
|
|
300
|
+
) : (
|
|
301
|
+
<Image
|
|
302
|
+
className={props.className}
|
|
303
|
+
src={
|
|
304
|
+
isInternalURL(imageValue)
|
|
305
|
+
? `${flattenToAppURL(imageValue)}/@@images/image/${imageSize}`
|
|
306
|
+
: imageValue
|
|
307
|
+
}
|
|
308
|
+
alt=""
|
|
309
|
+
/>
|
|
310
|
+
)}
|
|
190
311
|
</div>
|
|
191
312
|
) : (
|
|
192
313
|
<div
|
|
@@ -227,7 +348,7 @@ const UnconnectedImageInput = (props) => {
|
|
|
227
348
|
</Loader>
|
|
228
349
|
</Dimmer>
|
|
229
350
|
)}
|
|
230
|
-
<
|
|
351
|
+
<Image src={imageBlockSVG} alt="" className="placeholder" />
|
|
231
352
|
<p>{description || intl.formatMessage(messages.addImage)}</p>
|
|
232
353
|
<div className="toolbar-wrapper">
|
|
233
354
|
<div className="toolbar-inner" ref={linkEditor.anchorNode}>
|
|
@@ -242,15 +363,24 @@ const UnconnectedImageInput = (props) => {
|
|
|
242
363
|
e.preventDefault();
|
|
243
364
|
openObjectBrowser({
|
|
244
365
|
mode: objectBrowserPickerType,
|
|
245
|
-
onSelectItem:
|
|
246
|
-
?
|
|
247
|
-
|
|
248
|
-
onChange(props.id,
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
366
|
+
onSelectItem: isRelationChoice
|
|
367
|
+
? (url, item) => {
|
|
368
|
+
// we save the whole item if it's a relation choice
|
|
369
|
+
onChange(props.id, item);
|
|
370
|
+
}
|
|
371
|
+
: onSelectItem
|
|
372
|
+
? onSelectItem
|
|
373
|
+
: // else we save the url along with the image field and scales
|
|
374
|
+
(
|
|
375
|
+
url,
|
|
376
|
+
{ title, image_field, image_scales },
|
|
377
|
+
) => {
|
|
378
|
+
onChange(props.id, flattenToAppURL(url), {
|
|
379
|
+
title,
|
|
380
|
+
image_field,
|
|
381
|
+
image_scales,
|
|
382
|
+
});
|
|
383
|
+
},
|
|
254
384
|
currentPath: contextUrl,
|
|
255
385
|
});
|
|
256
386
|
}}
|
|
@@ -311,13 +441,14 @@ const UnconnectedImageInput = (props) => {
|
|
|
311
441
|
intl.formatMessage(messages.linkAnImage)
|
|
312
442
|
}
|
|
313
443
|
objectBrowserPickerType={objectBrowserPickerType}
|
|
314
|
-
onChange={(_, e) =>
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
444
|
+
onChange={(_, e) => {
|
|
445
|
+
onSubmitURL(e);
|
|
446
|
+
// onChange(
|
|
447
|
+
// props.id,
|
|
448
|
+
// isInternalURL(e) ? flattenToAppURL(e) : e,
|
|
449
|
+
// {},
|
|
450
|
+
// );
|
|
451
|
+
}}
|
|
321
452
|
id={id}
|
|
322
453
|
/>
|
|
323
454
|
)}
|
|
@@ -331,6 +462,8 @@ const UnconnectedImageInput = (props) => {
|
|
|
331
462
|
};
|
|
332
463
|
|
|
333
464
|
export const ImageInput = compose(
|
|
465
|
+
// This HOC goes first because it injects block in case that it's not present (not a block, but a DX field)
|
|
466
|
+
withObjectBrowser,
|
|
334
467
|
connect(
|
|
335
468
|
(state, ownProps) => {
|
|
336
469
|
const requestId = `image-upload-${ownProps.id}`;
|
|
@@ -339,9 +472,9 @@ export const ImageInput = compose(
|
|
|
339
472
|
content: state.content.subrequests[ownProps.block || requestId]?.data,
|
|
340
473
|
};
|
|
341
474
|
},
|
|
342
|
-
{ createContent },
|
|
475
|
+
{ createContent, searchContent },
|
|
343
476
|
),
|
|
344
|
-
)(
|
|
477
|
+
)(UnconnectedImageInput);
|
|
345
478
|
|
|
346
479
|
const ImageUploadWidget = (props) => {
|
|
347
480
|
const { fieldSet, id, title } = props;
|
|
@@ -100,6 +100,7 @@ export const InternalUrlWidget = (props) => {
|
|
|
100
100
|
{value?.length > 0 ? (
|
|
101
101
|
<Button.Group>
|
|
102
102
|
<Button
|
|
103
|
+
type="button"
|
|
103
104
|
basic
|
|
104
105
|
className="cancel"
|
|
105
106
|
aria-label="clearUrlBrowser"
|
|
@@ -115,6 +116,7 @@ export const InternalUrlWidget = (props) => {
|
|
|
115
116
|
) : (
|
|
116
117
|
<Button.Group>
|
|
117
118
|
<Button
|
|
119
|
+
type="button"
|
|
118
120
|
basic
|
|
119
121
|
icon
|
|
120
122
|
aria-label="openUrlBrowser"
|
|
@@ -399,6 +399,7 @@ export class ObjectBrowserWidgetComponent extends Component {
|
|
|
399
399
|
{this.state.manualLinkInput && isEmpty(items) && (
|
|
400
400
|
<Button.Group>
|
|
401
401
|
<Button
|
|
402
|
+
type="button"
|
|
402
403
|
basic
|
|
403
404
|
className="cancel"
|
|
404
405
|
onClick={(e) => {
|
|
@@ -409,6 +410,7 @@ export class ObjectBrowserWidgetComponent extends Component {
|
|
|
409
410
|
<Icon name={clearSVG} size="18px" color="#e40166" />
|
|
410
411
|
</Button>
|
|
411
412
|
<Button
|
|
413
|
+
type="button"
|
|
412
414
|
basic
|
|
413
415
|
primary
|
|
414
416
|
disabled={!this.state.validURL}
|
|
@@ -423,6 +425,7 @@ export class ObjectBrowserWidgetComponent extends Component {
|
|
|
423
425
|
)}
|
|
424
426
|
{!this.state.manualLinkInput && (
|
|
425
427
|
<Button
|
|
428
|
+
type="button"
|
|
426
429
|
aria-label={this.props.intl.formatMessage(
|
|
427
430
|
messages.openObjectBrowser,
|
|
428
431
|
)}
|
|
@@ -82,10 +82,11 @@ describe('RegistryImageWidget', () => {
|
|
|
82
82
|
const dropzone = container.querySelector('.file-widget-dropzone');
|
|
83
83
|
const preview = container.querySelector('.image-preview');
|
|
84
84
|
const filename = container.querySelector('.field-file-name');
|
|
85
|
+
const img = container.querySelector('img[src*="logo"]');
|
|
85
86
|
|
|
86
|
-
return dropzone && preview && filename;
|
|
87
|
+
return dropzone && preview && filename && img && img.complete;
|
|
87
88
|
},
|
|
88
|
-
{ timeout:
|
|
89
|
+
{ timeout: 2000 },
|
|
89
90
|
);
|
|
90
91
|
|
|
91
92
|
expect(container).toMatchSnapshot();
|
|
@@ -105,6 +105,7 @@ class SelectAutoComplete extends Component {
|
|
|
105
105
|
error: [],
|
|
106
106
|
choices: [],
|
|
107
107
|
value: null,
|
|
108
|
+
isMulti: true,
|
|
108
109
|
};
|
|
109
110
|
|
|
110
111
|
/**
|
|
@@ -127,10 +128,16 @@ class SelectAutoComplete extends Component {
|
|
|
127
128
|
componentDidMount() {
|
|
128
129
|
const { id, lang, value, choices } = this.props;
|
|
129
130
|
if (value && value?.length > 0) {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
131
|
+
let tokensQuery;
|
|
132
|
+
if (typeof value === 'string') {
|
|
133
|
+
tokensQuery = convertValueToVocabQuery(
|
|
134
|
+
normalizeValue(choices, [value], this.props.intl),
|
|
135
|
+
);
|
|
136
|
+
} else {
|
|
137
|
+
tokensQuery = convertValueToVocabQuery(
|
|
138
|
+
normalizeValue(choices, value, this.props.intl),
|
|
139
|
+
);
|
|
140
|
+
}
|
|
134
141
|
this.props.getVocabularyTokenTitle({
|
|
135
142
|
vocabNameOrURL: this.props.vocabBaseUrl,
|
|
136
143
|
subrequest: `widget-${id}-${lang}`,
|
|
@@ -160,13 +167,22 @@ class SelectAutoComplete extends Component {
|
|
|
160
167
|
* @returns {undefined}
|
|
161
168
|
*/
|
|
162
169
|
handleChange(selectedOption) {
|
|
163
|
-
|
|
164
|
-
this.props.id,
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
+
if (!Array.isArray(selectedOption)) {
|
|
171
|
+
this.props.onChange(this.props.id, selectedOption?.value || null);
|
|
172
|
+
if (selectedOption) {
|
|
173
|
+
this.setState((state) => ({
|
|
174
|
+
termsPairsCache: [...state.termsPairsCache, selectedOption],
|
|
175
|
+
}));
|
|
176
|
+
}
|
|
177
|
+
} else {
|
|
178
|
+
this.props.onChange(
|
|
179
|
+
this.props.id,
|
|
180
|
+
selectedOption ? selectedOption.map((item) => item.value) : null,
|
|
181
|
+
);
|
|
182
|
+
this.setState((state) => ({
|
|
183
|
+
termsPairsCache: [...state.termsPairsCache, ...selectedOption],
|
|
184
|
+
}));
|
|
185
|
+
}
|
|
170
186
|
}
|
|
171
187
|
|
|
172
188
|
timeoutRef = React.createRef();
|
|
@@ -251,7 +267,8 @@ class SelectAutoComplete extends Component {
|
|
|
251
267
|
this.props.intl.formatMessage(messages.select)
|
|
252
268
|
}
|
|
253
269
|
onChange={this.handleChange}
|
|
254
|
-
isMulti
|
|
270
|
+
isMulti={this.props.isMulti}
|
|
271
|
+
isClearable={!this.props.isMulti}
|
|
255
272
|
/>
|
|
256
273
|
</FormFieldWrapper>
|
|
257
274
|
);
|
|
@@ -128,6 +128,7 @@ class SelectWidget extends Component {
|
|
|
128
128
|
isMulti: PropTypes.bool,
|
|
129
129
|
placeholder: PropTypes.string,
|
|
130
130
|
sort: PropTypes.bool,
|
|
131
|
+
isClearable: PropTypes.bool,
|
|
131
132
|
};
|
|
132
133
|
|
|
133
134
|
/**
|
|
@@ -156,6 +157,7 @@ class SelectWidget extends Component {
|
|
|
156
157
|
noValueOption: true,
|
|
157
158
|
customOptionStyling: null,
|
|
158
159
|
sort: false,
|
|
160
|
+
isClearable: true,
|
|
159
161
|
};
|
|
160
162
|
|
|
161
163
|
/**
|
|
@@ -302,7 +304,7 @@ class SelectWidget extends Component {
|
|
|
302
304
|
: undefined,
|
|
303
305
|
);
|
|
304
306
|
}}
|
|
305
|
-
isClearable
|
|
307
|
+
isClearable={this.props.isClearable}
|
|
306
308
|
/>
|
|
307
309
|
</FormFieldWrapper>
|
|
308
310
|
);
|
|
@@ -105,6 +105,7 @@ export const UrlWidget = (props) => {
|
|
|
105
105
|
{value?.length > 0 ? (
|
|
106
106
|
<Button.Group>
|
|
107
107
|
<Button
|
|
108
|
+
type="button"
|
|
108
109
|
basic
|
|
109
110
|
className="cancel"
|
|
110
111
|
aria-label="clearUrlBrowser"
|
|
@@ -120,6 +121,7 @@ export const UrlWidget = (props) => {
|
|
|
120
121
|
) : (
|
|
121
122
|
<Button.Group>
|
|
122
123
|
<Button
|
|
124
|
+
type="button"
|
|
123
125
|
basic
|
|
124
126
|
icon
|
|
125
127
|
aria-label="openUrlBrowser"
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import config from '@plone/volto/registry';
|
|
2
1
|
import Helmet from '@plone/volto/helpers/Helmet/Helmet';
|
|
3
2
|
import { flattenToAppURL, toPublicURL } from '@plone/volto/helpers/Url/Url';
|
|
4
3
|
|
|
@@ -6,8 +5,7 @@ const AlternateHrefLangs = (props) => {
|
|
|
6
5
|
const { content } = props;
|
|
7
6
|
return (
|
|
8
7
|
<Helmet>
|
|
9
|
-
{
|
|
10
|
-
content['@components']?.translations?.items &&
|
|
8
|
+
{content['@components']?.translations?.items &&
|
|
11
9
|
[
|
|
12
10
|
...content['@components']?.translations?.items,
|
|
13
11
|
{ '@id': content['@id'], language: content.language.token },
|
|
@@ -13,7 +13,6 @@ const mockStore = configureStore();
|
|
|
13
13
|
describe('AlternateHrefLangs', () => {
|
|
14
14
|
beforeEach(() => {});
|
|
15
15
|
it('non multilingual site, renders nothing', () => {
|
|
16
|
-
config.settings.isMultilingual = false;
|
|
17
16
|
const content = {
|
|
18
17
|
'@id': '/',
|
|
19
18
|
'@components': {},
|
|
@@ -38,7 +37,6 @@ describe('AlternateHrefLangs', () => {
|
|
|
38
37
|
|
|
39
38
|
it('multilingual site, with some translations', () => {
|
|
40
39
|
config.settings.publicURL = 'https://plone.org';
|
|
41
|
-
config.settings.isMultilingual = true;
|
|
42
40
|
config.settings.supportedLanguages = ['en', 'es', 'eu'];
|
|
43
41
|
|
|
44
42
|
const content = {
|
|
@@ -85,7 +83,6 @@ describe('AlternateHrefLangs', () => {
|
|
|
85
83
|
|
|
86
84
|
it('multilingual site, with all available translations', () => {
|
|
87
85
|
config.settings.publicURL = 'https://plone.org';
|
|
88
|
-
config.settings.isMultilingual = true;
|
|
89
86
|
config.settings.supportedLanguages = ['en', 'es', 'eu'];
|
|
90
87
|
const store = mockStore({
|
|
91
88
|
intl: {
|
|
@@ -139,7 +136,6 @@ describe('AlternateHrefLangs', () => {
|
|
|
139
136
|
|
|
140
137
|
it('multilingual site, with all available translations - with server URL', () => {
|
|
141
138
|
config.settings.publicURL = 'https://plone.org';
|
|
142
|
-
config.settings.isMultilingual = true;
|
|
143
139
|
config.settings.supportedLanguages = ['en', 'es', 'eu'];
|
|
144
140
|
const store = mockStore({
|
|
145
141
|
intl: {
|
|
@@ -235,7 +235,9 @@ export const fetchContent = async ({ store, location }) => {
|
|
|
235
235
|
|
|
236
236
|
const visitor = ([id, data]) => {
|
|
237
237
|
const blockType = data['@type'];
|
|
238
|
-
const
|
|
238
|
+
const block = blocksConfig[blockType];
|
|
239
|
+
if (!block) return;
|
|
240
|
+
const { getAsyncData } = block;
|
|
239
241
|
if (getAsyncData) {
|
|
240
242
|
const p = getAsyncData({
|
|
241
243
|
store,
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import React from 'react';
|
|
6
6
|
import PropTypes from 'prop-types';
|
|
7
7
|
import { getInitials } from '@plone/volto/helpers/Utils/Utils';
|
|
8
|
+
import Image from '@plone/volto/components/theme/Image/Image';
|
|
8
9
|
|
|
9
10
|
const defaultSize = 30;
|
|
10
11
|
const defaultColor = 'Teal';
|
|
@@ -15,7 +16,7 @@ const Avatar = ({ src, title, text, size, color, className }) => {
|
|
|
15
16
|
return (
|
|
16
17
|
<div className={className} title={title}>
|
|
17
18
|
{src ? (
|
|
18
|
-
<
|
|
19
|
+
<Image src={src} alt={title} />
|
|
19
20
|
) : (
|
|
20
21
|
<svg width={size} height={size}>
|
|
21
22
|
<circle cx={radius} cy={radius} r={radius} fill={color} />
|
|
@@ -82,9 +82,27 @@ export default {
|
|
|
82
82
|
],
|
|
83
83
|
argTypes: {
|
|
84
84
|
locale: {
|
|
85
|
+
options: [
|
|
86
|
+
'ca',
|
|
87
|
+
'de',
|
|
88
|
+
'en',
|
|
89
|
+
'es',
|
|
90
|
+
'eu',
|
|
91
|
+
'fi',
|
|
92
|
+
'fr',
|
|
93
|
+
'hi',
|
|
94
|
+
'it',
|
|
95
|
+
'ja',
|
|
96
|
+
'nl',
|
|
97
|
+
'pt',
|
|
98
|
+
'pt-BR',
|
|
99
|
+
'ro',
|
|
100
|
+
'ru',
|
|
101
|
+
'sv',
|
|
102
|
+
'zh-CN',
|
|
103
|
+
],
|
|
85
104
|
control: {
|
|
86
|
-
type: '
|
|
87
|
-
options: ['en', 'de', 'us'],
|
|
105
|
+
type: 'radio',
|
|
88
106
|
},
|
|
89
107
|
},
|
|
90
108
|
},
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import PropTypes from 'prop-types';
|
|
2
2
|
import cx from 'classnames';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
flattenToAppURL,
|
|
5
|
+
flattenScales,
|
|
6
|
+
addSubpathPrefix,
|
|
7
|
+
} from '@plone/volto/helpers/Url/Url';
|
|
4
8
|
|
|
5
9
|
/**
|
|
6
10
|
* Image component
|
|
@@ -30,7 +34,7 @@ export default function Image({
|
|
|
30
34
|
attrs.className = cx(className, { responsive }) || undefined;
|
|
31
35
|
|
|
32
36
|
if (!item && src) {
|
|
33
|
-
attrs.src = src;
|
|
37
|
+
attrs.src = addSubpathPrefix(src);
|
|
34
38
|
} else {
|
|
35
39
|
const isFromRealObject = !item.image_scales;
|
|
36
40
|
const imageFieldWithDefault = imageField || item.image_field || 'image';
|
|
@@ -46,9 +50,11 @@ export default function Image({
|
|
|
46
50
|
|
|
47
51
|
const isSvg = image['content-type'] === 'image/svg+xml';
|
|
48
52
|
// In case `base_path` is present (`preview_image_link`) use it as base path
|
|
49
|
-
const basePath =
|
|
53
|
+
const basePath = addSubpathPrefix(
|
|
54
|
+
flattenToAppURL(image.base_path || item['@id']),
|
|
55
|
+
);
|
|
56
|
+
attrs.src = `${basePath}/${image.download}`;
|
|
50
57
|
|
|
51
|
-
attrs.src = `${flattenToAppURL(basePath)}/${image.download}`;
|
|
52
58
|
attrs.width = image.width;
|
|
53
59
|
attrs.height = image.height;
|
|
54
60
|
|
|
@@ -67,10 +73,7 @@ export default function Image({
|
|
|
67
73
|
});
|
|
68
74
|
|
|
69
75
|
attrs.srcSet = sortedScales
|
|
70
|
-
.map(
|
|
71
|
-
(scale) =>
|
|
72
|
-
`${flattenToAppURL(basePath)}/${scale.download} ${scale.width}w`,
|
|
73
|
-
)
|
|
76
|
+
.map((scale) => `${basePath}/${scale.download} ${scale.width}w`)
|
|
74
77
|
.join(', ');
|
|
75
78
|
}
|
|
76
79
|
}
|