@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
|
@@ -2,6 +2,7 @@ import React from 'react';
|
|
|
2
2
|
import { render, act } from '@testing-library/react';
|
|
3
3
|
import configureStore from 'redux-mock-store';
|
|
4
4
|
import { Provider } from 'react-intl-redux';
|
|
5
|
+
import { MemoryRouter } from 'react-router-dom';
|
|
5
6
|
import jwt from 'jsonwebtoken';
|
|
6
7
|
|
|
7
8
|
import UsersControlpanel from './UsersControlpanel';
|
|
@@ -41,10 +42,10 @@ describe('UsersControlpanel', () => {
|
|
|
41
42
|
const { container } = await act(async () => {
|
|
42
43
|
return render(
|
|
43
44
|
<Provider store={store}>
|
|
44
|
-
|
|
45
|
-
<UsersControlpanel
|
|
45
|
+
<MemoryRouter initialEntries={['/controlpanel/users']}>
|
|
46
|
+
<UsersControlpanel />
|
|
46
47
|
<div id="toolbar"></div>
|
|
47
|
-
|
|
48
|
+
</MemoryRouter>
|
|
48
49
|
</Provider>,
|
|
49
50
|
);
|
|
50
51
|
});
|
|
@@ -44,8 +44,6 @@ import { tryParseJSON } from '@plone/volto/helpers/FormValidation/FormValidation
|
|
|
44
44
|
import saveSVG from '@plone/volto/icons/save.svg';
|
|
45
45
|
import clearSVG from '@plone/volto/icons/clear.svg';
|
|
46
46
|
|
|
47
|
-
import config from '@plone/volto/registry';
|
|
48
|
-
|
|
49
47
|
const messages = defineMessages({
|
|
50
48
|
edit: {
|
|
51
49
|
id: 'Edit {title}',
|
|
@@ -460,7 +458,7 @@ class Edit extends Component {
|
|
|
460
458
|
/>
|
|
461
459
|
</Button>
|
|
462
460
|
|
|
463
|
-
{
|
|
461
|
+
{this.props.isMultilingual && (
|
|
464
462
|
<CompareLanguages
|
|
465
463
|
content={this.props.content}
|
|
466
464
|
visual={this.state.visual}
|
|
@@ -545,6 +543,7 @@ export default compose(
|
|
|
545
543
|
updateRequest: state.content.update,
|
|
546
544
|
pathname: props.location.pathname,
|
|
547
545
|
returnUrl: qs.parse(props.location.search).return_url,
|
|
546
|
+
isMultilingual: state.site.data.features?.multilingual,
|
|
548
547
|
}),
|
|
549
548
|
{
|
|
550
549
|
updateContent,
|
|
@@ -9,6 +9,7 @@ import { Field, BlocksForm } from '@plone/volto/components/manage/Form';
|
|
|
9
9
|
import BlocksToolbar from '@plone/volto/components/manage/Form/BlocksToolbar';
|
|
10
10
|
import UndoToolbar from '@plone/volto/components/manage/Form/UndoToolbar';
|
|
11
11
|
import { difference } from '@plone/volto/helpers/Utils/Utils';
|
|
12
|
+
import withSaveAsDraft from '@plone/volto/helpers/Utils/withSaveAsDraft';
|
|
12
13
|
import FormValidation from '@plone/volto/helpers/FormValidation/FormValidation';
|
|
13
14
|
import {
|
|
14
15
|
getBlocksFieldname,
|
|
@@ -265,6 +266,18 @@ class Form extends Component {
|
|
|
265
266
|
this.onBlurField = this.onBlurField.bind(this);
|
|
266
267
|
this.onClickInput = this.onClickInput.bind(this);
|
|
267
268
|
this.onToggleMetadataFieldset = this.onToggleMetadataFieldset.bind(this);
|
|
269
|
+
this.updateFormDataWithSaved = this.updateFormDataWithSaved.bind(this);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Function sent as callback to saveAsDraft when user
|
|
274
|
+
* choses to load local data
|
|
275
|
+
* @param {Object} savedFormData
|
|
276
|
+
*/
|
|
277
|
+
updateFormDataWithSaved(savedFormData) {
|
|
278
|
+
if (savedFormData) {
|
|
279
|
+
this.setState({ formData: savedFormData });
|
|
280
|
+
}
|
|
268
281
|
}
|
|
269
282
|
|
|
270
283
|
/**
|
|
@@ -278,6 +291,12 @@ class Form extends Component {
|
|
|
278
291
|
let errors = {};
|
|
279
292
|
let activeIndex = 0;
|
|
280
293
|
|
|
294
|
+
if (!prevProps.schema && this.props.schema) {
|
|
295
|
+
this.props.checkSavedDraft(
|
|
296
|
+
this.state.formData,
|
|
297
|
+
this.updateFormDataWithSaved,
|
|
298
|
+
);
|
|
299
|
+
}
|
|
281
300
|
if (!this.props.isFormSelected && prevProps.isFormSelected) {
|
|
282
301
|
this.props.setUIState({
|
|
283
302
|
selected: null,
|
|
@@ -303,6 +322,10 @@ class Form extends Component {
|
|
|
303
322
|
this.props.onChangeFormData(this.state.formData);
|
|
304
323
|
}
|
|
305
324
|
}
|
|
325
|
+
// on each formData update it will save the form to the localStorage
|
|
326
|
+
if (!isEqual(prevState?.formData, this.state.formData)) {
|
|
327
|
+
this.props.onSaveDraft(this.state.formData);
|
|
328
|
+
}
|
|
306
329
|
if (
|
|
307
330
|
this.props.global &&
|
|
308
331
|
!isEqual(this.props.globalData, prevProps.globalData)
|
|
@@ -395,6 +418,13 @@ class Form extends Component {
|
|
|
395
418
|
*/
|
|
396
419
|
componentDidMount() {
|
|
397
420
|
this.setState({ isClient: true });
|
|
421
|
+
if (this.props.schema) {
|
|
422
|
+
this.props.checkSavedDraft(
|
|
423
|
+
this.state.formData,
|
|
424
|
+
this.updateFormDataWithSaved,
|
|
425
|
+
);
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
398
428
|
}
|
|
399
429
|
|
|
400
430
|
/**
|
|
@@ -653,6 +683,7 @@ class Form extends Component {
|
|
|
653
683
|
this.props.setFormData(this.props.formData);
|
|
654
684
|
}
|
|
655
685
|
}
|
|
686
|
+
this.props.onCancelDraft();
|
|
656
687
|
}
|
|
657
688
|
}
|
|
658
689
|
|
|
@@ -1136,4 +1167,5 @@ export default compose(
|
|
|
1136
1167
|
null,
|
|
1137
1168
|
{ forwardRef: true },
|
|
1138
1169
|
),
|
|
1170
|
+
withSaveAsDraft({ forwardRef: true }),
|
|
1139
1171
|
)(FormIntl);
|
|
@@ -2,7 +2,7 @@ import React from 'react';
|
|
|
2
2
|
import renderer from 'react-test-renderer';
|
|
3
3
|
import configureStore from 'redux-mock-store';
|
|
4
4
|
import { Provider } from 'react-intl-redux';
|
|
5
|
-
|
|
5
|
+
import { MemoryRouter } from 'react-router-dom';
|
|
6
6
|
import Form from './Form';
|
|
7
7
|
|
|
8
8
|
const mockStore = configureStore();
|
|
@@ -30,26 +30,30 @@ describe('Form', () => {
|
|
|
30
30
|
},
|
|
31
31
|
},
|
|
32
32
|
});
|
|
33
|
+
const route = '/some-route';
|
|
33
34
|
const component = renderer.create(
|
|
34
35
|
<Provider store={store}>
|
|
35
|
-
<
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
36
|
+
<MemoryRouter initialEntries={[route]}>
|
|
37
|
+
<Form
|
|
38
|
+
schema={{
|
|
39
|
+
fieldsets: [
|
|
40
|
+
{
|
|
41
|
+
id: 'default',
|
|
42
|
+
title: 'Default',
|
|
43
|
+
fields: ['title'],
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
properties: {
|
|
47
|
+
title: {},
|
|
42
48
|
},
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
onCancel={() => {}}
|
|
52
|
-
/>
|
|
49
|
+
required: [],
|
|
50
|
+
}}
|
|
51
|
+
requestError={errorMessage}
|
|
52
|
+
onSubmit={() => {}}
|
|
53
|
+
onCancel={() => {}}
|
|
54
|
+
/>
|
|
55
|
+
</MemoryRouter>
|
|
56
|
+
,
|
|
53
57
|
</Provider>,
|
|
54
58
|
);
|
|
55
59
|
const json = component.toJSON();
|
|
@@ -158,7 +158,7 @@ const InlineForm = (props) => {
|
|
|
158
158
|
onChangeField(id, value, itemInfo);
|
|
159
159
|
}}
|
|
160
160
|
key={field}
|
|
161
|
-
error={errors?.[block]?.[field] ||
|
|
161
|
+
error={errors?.[block]?.[field] || []}
|
|
162
162
|
block={block}
|
|
163
163
|
/>
|
|
164
164
|
))}
|
|
@@ -199,7 +199,7 @@ const InlineForm = (props) => {
|
|
|
199
199
|
onChangeField(id, value);
|
|
200
200
|
}}
|
|
201
201
|
key={field}
|
|
202
|
-
error={errors?.[block]?.[field] ||
|
|
202
|
+
error={errors?.[block]?.[field] || []}
|
|
203
203
|
block={block}
|
|
204
204
|
/>
|
|
205
205
|
))}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import React, { useState } from 'react';
|
|
2
2
|
import { defineMessages, useIntl } from 'react-intl';
|
|
3
|
-
import config from '@plone/volto/registry';
|
|
4
3
|
import langmap from '@plone/volto/helpers/LanguageMap/LanguageMap';
|
|
5
4
|
import { useDetectClickOutside } from '@plone/volto/helpers/Utils/useDetectClickOutside';
|
|
6
5
|
|
|
@@ -106,16 +105,14 @@ const CompareLanguages = React.forwardRef((props, ref) => {
|
|
|
106
105
|
|
|
107
106
|
const intl = useIntl();
|
|
108
107
|
const [viewMenu, setViewMenu] = useState(false);
|
|
109
|
-
const translations =
|
|
110
|
-
? content?.['@components']?.translations?.items || []
|
|
111
|
-
: [];
|
|
108
|
+
const translations = content?.['@components']?.translations?.items || [];
|
|
112
109
|
|
|
113
110
|
const translationsObject = {};
|
|
114
111
|
translations.forEach((t) => {
|
|
115
112
|
translationsObject[t.language] = t['@id'];
|
|
116
113
|
});
|
|
117
114
|
|
|
118
|
-
if (
|
|
115
|
+
if (translations.length > 0) {
|
|
119
116
|
return (
|
|
120
117
|
<div className="toolbar-compare-translations-wrapper">
|
|
121
118
|
<div className="toolbar-button-spacer" />
|
|
@@ -12,9 +12,12 @@ const CreateTranslation = (props) => {
|
|
|
12
12
|
const dispatch = useDispatch();
|
|
13
13
|
const { language, translationOf } = props.location.state;
|
|
14
14
|
const [translationLocation, setTranslationLocation] = React.useState(null);
|
|
15
|
-
const [translationObject, setTranslationObject] = React.useState(null);
|
|
16
15
|
const languageFrom = useSelector((state) => state.intl.locale);
|
|
17
16
|
|
|
17
|
+
const translationObject = useSelector(
|
|
18
|
+
(state) => state.content.subrequests['translationObject'],
|
|
19
|
+
);
|
|
20
|
+
|
|
18
21
|
React.useEffect(() => {
|
|
19
22
|
// Only on mount, we dispatch the locator query
|
|
20
23
|
dispatch(getTranslationLocator(translationOf, language)).then((resp) => {
|
|
@@ -22,11 +25,7 @@ const CreateTranslation = (props) => {
|
|
|
22
25
|
});
|
|
23
26
|
|
|
24
27
|
//and we load the translationObject
|
|
25
|
-
dispatch(getContent(translationOf, null, 'translationObject'))
|
|
26
|
-
(resp) => {
|
|
27
|
-
setTranslationObject(resp);
|
|
28
|
-
},
|
|
29
|
-
);
|
|
28
|
+
dispatch(getContent(translationOf, null, 'translationObject'));
|
|
30
29
|
|
|
31
30
|
// On unmount we dispatch the language change
|
|
32
31
|
return () => {
|
|
@@ -46,7 +45,8 @@ const CreateTranslation = (props) => {
|
|
|
46
45
|
|
|
47
46
|
return (
|
|
48
47
|
translationLocation &&
|
|
49
|
-
translationObject &&
|
|
48
|
+
translationObject.data &&
|
|
49
|
+
translationObject.loaded > 0 && (
|
|
50
50
|
<Redirect
|
|
51
51
|
to={{
|
|
52
52
|
pathname: `${flattenToAppURL(translationLocation)}/add`,
|
|
@@ -54,7 +54,7 @@ const CreateTranslation = (props) => {
|
|
|
54
54
|
state: {
|
|
55
55
|
translationOf: props.location.state.translationOf,
|
|
56
56
|
language: props.location.state.language,
|
|
57
|
-
translationObject: translationObject,
|
|
57
|
+
translationObject: translationObject.data,
|
|
58
58
|
languageFrom,
|
|
59
59
|
},
|
|
60
60
|
}}
|
|
@@ -8,7 +8,6 @@ import { Link, useLocation } from 'react-router-dom';
|
|
|
8
8
|
import Icon from '@plone/volto/components/theme/Icon/Icon';
|
|
9
9
|
import Toast from '@plone/volto/components/manage/Toast/Toast';
|
|
10
10
|
import Toolbar from '@plone/volto/components/manage/Toolbar/Toolbar';
|
|
11
|
-
import config from '@plone/volto/registry';
|
|
12
11
|
|
|
13
12
|
import withObjectBrowser from '@plone/volto/components/manage/Sidebar/ObjectBrowser';
|
|
14
13
|
import {
|
|
@@ -66,6 +65,9 @@ const ManageTranslations = (props) => {
|
|
|
66
65
|
const pathname = useLocation().pathname;
|
|
67
66
|
const content = useSelector((state) => state.content.data);
|
|
68
67
|
const dispatch = useDispatch();
|
|
68
|
+
const availableLanguages = useSelector(
|
|
69
|
+
(state) => state.site.data['plone.available_languages'],
|
|
70
|
+
);
|
|
69
71
|
|
|
70
72
|
const [isClient, setIsClient] = useState(false);
|
|
71
73
|
|
|
@@ -189,7 +191,7 @@ const ManageTranslations = (props) => {
|
|
|
189
191
|
</Table.Row>
|
|
190
192
|
</Table.Header>
|
|
191
193
|
<Table.Body>
|
|
192
|
-
{
|
|
194
|
+
{availableLanguages.map((lang) => (
|
|
193
195
|
<Table.Row key={lang}>
|
|
194
196
|
<Table.Cell collapsing>
|
|
195
197
|
{lang === content.language.token ? (
|
|
@@ -8,7 +8,6 @@ import config from '@plone/volto/registry';
|
|
|
8
8
|
import ManageTranslations from './ManageTranslations';
|
|
9
9
|
|
|
10
10
|
beforeAll(() => {
|
|
11
|
-
config.settings.isMultilingual = true;
|
|
12
11
|
config.settings.supportedLanguages = ['de', 'es'];
|
|
13
12
|
});
|
|
14
13
|
vi.mock('../Toolbar/Toolbar', () => ({
|
|
@@ -34,6 +33,11 @@ describe('ManageTranslations', () => {
|
|
|
34
33
|
language: 'en',
|
|
35
34
|
},
|
|
36
35
|
},
|
|
36
|
+
site: {
|
|
37
|
+
data: {
|
|
38
|
+
'plone.available_languages': ['de', 'es'],
|
|
39
|
+
},
|
|
40
|
+
},
|
|
37
41
|
});
|
|
38
42
|
const { container } = render(
|
|
39
43
|
<Provider store={store}>
|
|
@@ -63,7 +63,7 @@ const TranslationObject = ({
|
|
|
63
63
|
{
|
|
64
64
|
...window.__data,
|
|
65
65
|
intl: {
|
|
66
|
-
defaultLocale:
|
|
66
|
+
defaultLocale: translationObject.language.token,
|
|
67
67
|
locale: translationObject.language.token,
|
|
68
68
|
messages: locales[translationObject.language.token],
|
|
69
69
|
},
|
|
@@ -3,6 +3,7 @@ import { Button, Segment, Popup } from 'semantic-ui-react';
|
|
|
3
3
|
import { useIntl, defineMessages } from 'react-intl';
|
|
4
4
|
import cx from 'classnames';
|
|
5
5
|
import Icon from '@plone/volto/components/theme/Icon/Icon';
|
|
6
|
+
import Image from '@plone/volto/components/theme/Image/Image';
|
|
6
7
|
import { flattenToAppURL } from '@plone/volto/helpers/Url/Url';
|
|
7
8
|
import { getContentIcon } from '@plone/volto/helpers/Content/Content';
|
|
8
9
|
import config from '@plone/volto/registry';
|
|
@@ -69,7 +70,7 @@ const ObjectBrowserNav = ({
|
|
|
69
70
|
}
|
|
70
71
|
>
|
|
71
72
|
{item['@type'] === 'Image' ? (
|
|
72
|
-
<
|
|
73
|
+
<Image
|
|
73
74
|
src={`${item['@id']}/@@images/image/preview`}
|
|
74
75
|
alt={item.title}
|
|
75
76
|
style={{
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { cleanup, render, screen } from '@testing-library/react';
|
|
3
|
+
|
|
4
|
+
import SidebarPortal from './SidebarPortal';
|
|
5
|
+
|
|
6
|
+
describe('SidebarPortal', () => {
|
|
7
|
+
let portalRoot: HTMLDivElement | null;
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
portalRoot = document.createElement('div');
|
|
11
|
+
portalRoot.setAttribute('id', 'sidebar-properties');
|
|
12
|
+
document.body.appendChild(portalRoot);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
cleanup();
|
|
17
|
+
portalRoot?.remove();
|
|
18
|
+
portalRoot = null;
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test('renders the sidebar portal when the block is selected', async () => {
|
|
22
|
+
render(
|
|
23
|
+
<SidebarPortal selected={true}>
|
|
24
|
+
<p>Tested!</p>
|
|
25
|
+
</SidebarPortal>,
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
expect(await screen.findByText('Tested!')).toBeInTheDocument();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('does not render the sidebar portal when the block is not selected', () => {
|
|
32
|
+
render(
|
|
33
|
+
<SidebarPortal selected={false}>
|
|
34
|
+
<p>Tested, but you should not see this!</p>
|
|
35
|
+
</SidebarPortal>,
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
expect(
|
|
39
|
+
screen.queryByText('Tested, but you should not see this!'),
|
|
40
|
+
).not.toBeInTheDocument();
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { createPortal } from 'react-dom';
|
|
3
|
+
|
|
4
|
+
type SidebarPortalProps = {
|
|
5
|
+
children?: React.ReactNode;
|
|
6
|
+
selected: boolean;
|
|
7
|
+
tab?: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const SidebarPortal = ({
|
|
11
|
+
children,
|
|
12
|
+
selected,
|
|
13
|
+
tab = 'sidebar-properties',
|
|
14
|
+
}: SidebarPortalProps) => {
|
|
15
|
+
const [isClient, setIsClient] = React.useState(false);
|
|
16
|
+
|
|
17
|
+
React.useEffect(() => setIsClient(true), []);
|
|
18
|
+
|
|
19
|
+
if (!isClient || !selected) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const target = document.getElementById(tab);
|
|
24
|
+
|
|
25
|
+
if (!target) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return createPortal(
|
|
30
|
+
<div role="form" style={{ height: '100%' }}>
|
|
31
|
+
<div
|
|
32
|
+
style={{ height: '100%' }}
|
|
33
|
+
role="presentation"
|
|
34
|
+
onClick={(e) => {
|
|
35
|
+
e.stopPropagation();
|
|
36
|
+
}}
|
|
37
|
+
onKeyDown={(e) => {
|
|
38
|
+
e.stopPropagation();
|
|
39
|
+
}}
|
|
40
|
+
>
|
|
41
|
+
{children}
|
|
42
|
+
</div>
|
|
43
|
+
</div>,
|
|
44
|
+
target,
|
|
45
|
+
);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export default SidebarPortal;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import PropTypes from 'prop-types';
|
|
2
2
|
import { useIntl } from 'react-intl';
|
|
3
3
|
import { Button, Grid, Message } from 'semantic-ui-react';
|
|
4
|
+
import Image from '@plone/volto/components/theme/Image/Image';
|
|
4
5
|
|
|
5
6
|
const TemplateChooser = ({ templates, onSelectTemplate }) => {
|
|
6
7
|
const intl = useIntl();
|
|
@@ -15,7 +16,7 @@ const TemplateChooser = ({ templates, onSelectTemplate }) => {
|
|
|
15
16
|
className="template-chooser-item"
|
|
16
17
|
onClick={() => onSelectTemplate(index)}
|
|
17
18
|
>
|
|
18
|
-
<
|
|
19
|
+
<Image src={template.image} alt="" />
|
|
19
20
|
<div className="template-chooser-title">
|
|
20
21
|
{intl.formatMessage({
|
|
21
22
|
id: template.id,
|
|
@@ -114,6 +114,9 @@ const More = (props) => {
|
|
|
114
114
|
|
|
115
115
|
const content = useSelector((state) => state.content?.data, shallowEqual);
|
|
116
116
|
const workingCopy = useSelector((state) => state.workingCopy, shallowEqual);
|
|
117
|
+
const isMultilingual = useSelector(
|
|
118
|
+
(state) => state.site.data.features?.multilingual,
|
|
119
|
+
);
|
|
117
120
|
|
|
118
121
|
const actions = useSelector((state) => state.actions.actions, shallowEqual);
|
|
119
122
|
|
|
@@ -435,7 +438,7 @@ const More = (props) => {
|
|
|
435
438
|
</li>
|
|
436
439
|
</Plug>
|
|
437
440
|
)}
|
|
438
|
-
{editAction &&
|
|
441
|
+
{editAction && isMultilingual && (
|
|
439
442
|
<Plug pluggable="toolbar-more-manage-content" id="multilingual">
|
|
440
443
|
<li>
|
|
441
444
|
<Link to={`${path}/manage-translations`}>
|
|
@@ -7,6 +7,7 @@ import cx from 'classnames';
|
|
|
7
7
|
import { FormattedMessage, useIntl, defineMessages } from 'react-intl';
|
|
8
8
|
|
|
9
9
|
import Icon from '@plone/volto/components/theme/Icon/Icon';
|
|
10
|
+
import Image from '@plone/volto/components/theme/Image/Image';
|
|
10
11
|
import { getUser } from '@plone/volto/actions/users/users';
|
|
11
12
|
import { Pluggable } from '@plone/volto/components/manage/Pluggable';
|
|
12
13
|
import { expandToBackendURL, getBaseUrl } from '@plone/volto/helpers/Url/Url';
|
|
@@ -96,7 +97,7 @@ const PersonalTools = (props) => {
|
|
|
96
97
|
</header>
|
|
97
98
|
<div className={cx('avatar', { default: !user.portrait })}>
|
|
98
99
|
{user.portrait ? (
|
|
99
|
-
<
|
|
100
|
+
<Image
|
|
100
101
|
src={expandToBackendURL(user.portrait)}
|
|
101
102
|
alt={intl.formatMessage(messages.userAvatar)}
|
|
102
103
|
/>
|
|
@@ -537,10 +537,9 @@ class Toolbar extends Component {
|
|
|
537
537
|
</Link>
|
|
538
538
|
)}
|
|
539
539
|
{this.props.content &&
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
this.props.content['@components']?.translations)) && (
|
|
540
|
+
this.props.content.is_folderish &&
|
|
541
|
+
(this.props.types.length > 0 ||
|
|
542
|
+
this.props.content['@components']?.translations) && (
|
|
544
543
|
<button
|
|
545
544
|
className="add"
|
|
546
545
|
aria-label={this.props.intl.formatMessage(
|
|
@@ -2,6 +2,7 @@ import React from 'react';
|
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
3
|
import { connect } from 'react-redux';
|
|
4
4
|
import { Link } from 'react-router-dom';
|
|
5
|
+
import { useSelector } from 'react-redux';
|
|
5
6
|
import filter from 'lodash/filter';
|
|
6
7
|
import find from 'lodash/find';
|
|
7
8
|
import isEmpty from 'lodash/isEmpty';
|
|
@@ -10,12 +11,12 @@ import { FormattedMessage } from 'react-intl';
|
|
|
10
11
|
import { flattenToAppURL } from '@plone/volto/helpers/Url/Url';
|
|
11
12
|
import langmap from '@plone/volto/helpers/LanguageMap/LanguageMap';
|
|
12
13
|
import { toBackendLang } from '@plone/volto/helpers/Utils/Utils';
|
|
13
|
-
import config from '@plone/volto/registry';
|
|
14
14
|
|
|
15
15
|
const Types = ({ types, pathname, content, currentLanguage }) => {
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
const availableLanguages = useSelector(
|
|
17
|
+
(state) => state.site?.data?.['plone.available_languages'] || [],
|
|
18
|
+
);
|
|
19
|
+
return types.length > 0 || content?.['@components']?.translations ? (
|
|
19
20
|
<div className="menu-more pastanaga-menu">
|
|
20
21
|
{types.length > 0 && (
|
|
21
22
|
<>
|
|
@@ -53,11 +54,10 @@ const Types = ({ types, pathname, content, currentLanguage }) => {
|
|
|
53
54
|
</div>
|
|
54
55
|
</>
|
|
55
56
|
)}
|
|
56
|
-
{
|
|
57
|
-
content['@components'].translations &&
|
|
57
|
+
{content['@components'].translations &&
|
|
58
58
|
(() => {
|
|
59
59
|
const translationsLeft = filter(
|
|
60
|
-
|
|
60
|
+
availableLanguages,
|
|
61
61
|
(lang) =>
|
|
62
62
|
!Boolean(
|
|
63
63
|
content['@components'].translations &&
|
|
@@ -27,6 +27,10 @@ const messages = defineMessages({
|
|
|
27
27
|
id: 'Time',
|
|
28
28
|
defaultMessage: 'Time',
|
|
29
29
|
},
|
|
30
|
+
clearDateTime: {
|
|
31
|
+
id: 'Clear date/time',
|
|
32
|
+
defaultMessage: 'Clear date and time',
|
|
33
|
+
},
|
|
30
34
|
});
|
|
31
35
|
|
|
32
36
|
const PrevIcon = () => (
|
|
@@ -207,6 +211,7 @@ const DatetimeWidgetComponent = (props) => {
|
|
|
207
211
|
disabled={isDisabled || !datetime}
|
|
208
212
|
onClick={onResetDates}
|
|
209
213
|
className="item ui noborder button"
|
|
214
|
+
aria-label={intl.formatMessage(messages.clearDateTime)}
|
|
210
215
|
>
|
|
211
216
|
<Icon name={clearSVG} size="24px" className="close" />
|
|
212
217
|
</button>
|
|
@@ -15,7 +15,6 @@ import UniversalLink from '@plone/volto/components/manage/UniversalLink/Universa
|
|
|
15
15
|
import FormFieldWrapper from '@plone/volto/components/manage/Widgets/FormFieldWrapper';
|
|
16
16
|
import Image from '@plone/volto/components/theme/Image/Image';
|
|
17
17
|
import loadable from '@loadable/component';
|
|
18
|
-
import { flattenToAppURL } from '@plone/volto/helpers/Url/Url';
|
|
19
18
|
import { validateFileUploadSize } from '@plone/volto/helpers/FormValidation/FormValidation';
|
|
20
19
|
import { defineMessages, useIntl } from 'react-intl';
|
|
21
20
|
import { toast } from 'react-toastify';
|
|
@@ -90,18 +89,25 @@ const FileWidget = (props) => {
|
|
|
90
89
|
const [fileType, setFileType] = React.useState(false);
|
|
91
90
|
const intl = useIntl();
|
|
92
91
|
|
|
92
|
+
const imgAttrs = React.useMemo(() => {
|
|
93
|
+
const data = {};
|
|
94
|
+
if (value?.download) {
|
|
95
|
+
data.item = {
|
|
96
|
+
'@id': value.download.substring(0, value.download.indexOf('/@@images')),
|
|
97
|
+
image: value,
|
|
98
|
+
};
|
|
99
|
+
} else if (value?.data) {
|
|
100
|
+
data.src = `data:${value['content-type']};${value.encoding},${value.data}`;
|
|
101
|
+
}
|
|
102
|
+
return data;
|
|
103
|
+
}, [value]);
|
|
104
|
+
|
|
93
105
|
React.useEffect(() => {
|
|
94
106
|
if (value && imageMimetypes.includes(value['content-type'])) {
|
|
95
107
|
setFileType(true);
|
|
96
108
|
}
|
|
97
109
|
}, [value]);
|
|
98
110
|
|
|
99
|
-
const imgsrc = value?.download
|
|
100
|
-
? `${flattenToAppURL(value?.download)}?id=${Date.now()}`
|
|
101
|
-
: null || value?.data
|
|
102
|
-
? `data:${value['content-type']};${value.encoding},${value.data}`
|
|
103
|
-
: null;
|
|
104
|
-
|
|
105
111
|
/**
|
|
106
112
|
* Drop handler
|
|
107
113
|
* @method onDrop
|
|
@@ -175,7 +181,7 @@ const FileWidget = (props) => {
|
|
|
175
181
|
<Image
|
|
176
182
|
className="image-preview small ui image"
|
|
177
183
|
id={`field-${id}-image`}
|
|
178
|
-
|
|
184
|
+
{...imgAttrs}
|
|
179
185
|
/>
|
|
180
186
|
) : (
|
|
181
187
|
<div className="dropzone-placeholder">
|