@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
|
@@ -3,15 +3,9 @@ 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
|
-
import config from '@plone/volto/registry';
|
|
7
6
|
|
|
8
7
|
import LanguageSelector from './LanguageSelector';
|
|
9
8
|
|
|
10
|
-
beforeAll(() => {
|
|
11
|
-
config.settings.isMultilingual = true;
|
|
12
|
-
config.settings.supportedLanguages = ['de', 'es'];
|
|
13
|
-
});
|
|
14
|
-
|
|
15
9
|
const mockStore = configureStore();
|
|
16
10
|
|
|
17
11
|
describe('LanguageSelector', () => {
|
|
@@ -30,6 +24,12 @@ describe('LanguageSelector', () => {
|
|
|
30
24
|
},
|
|
31
25
|
},
|
|
32
26
|
},
|
|
27
|
+
site: {
|
|
28
|
+
data: {
|
|
29
|
+
features: { multilingual: true },
|
|
30
|
+
'plone.available_languages': ['es'],
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
33
|
});
|
|
34
34
|
const component = renderer.create(
|
|
35
35
|
<Provider store={store}>
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { Link } from 'react-router-dom';
|
|
2
|
+
|
|
3
|
+
import { useSelector } from 'react-redux';
|
|
4
|
+
import cx from 'classnames';
|
|
5
|
+
|
|
6
|
+
import Helmet from '@plone/volto/helpers/Helmet/Helmet';
|
|
7
|
+
import langmap from '@plone/volto/helpers/LanguageMap/LanguageMap';
|
|
8
|
+
import { flattenToAppURL } from '@plone/volto/helpers/Url/Url';
|
|
9
|
+
import { toReactIntlLang } from '@plone/volto/helpers/Utils/Utils';
|
|
10
|
+
|
|
11
|
+
import { defineMessages, useIntl, type IntlShape } from 'react-intl';
|
|
12
|
+
import type {
|
|
13
|
+
Content,
|
|
14
|
+
GetSiteResponse,
|
|
15
|
+
GetTranslationResponse,
|
|
16
|
+
} from '@plone/types';
|
|
17
|
+
|
|
18
|
+
const messages = defineMessages({
|
|
19
|
+
switchLanguageTo: {
|
|
20
|
+
id: 'Switch to',
|
|
21
|
+
defaultMessage: 'Switch to',
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
type FormState = {
|
|
26
|
+
content: {
|
|
27
|
+
data: Content;
|
|
28
|
+
};
|
|
29
|
+
intl: IntlShape;
|
|
30
|
+
site: {
|
|
31
|
+
data: GetSiteResponse;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const LanguageSelector = ({
|
|
36
|
+
onClickAction = () => {},
|
|
37
|
+
}: {
|
|
38
|
+
onClickAction?: () => void;
|
|
39
|
+
}) => {
|
|
40
|
+
const intl = useIntl();
|
|
41
|
+
const currentLang = useSelector<FormState, IntlShape['locale']>(
|
|
42
|
+
(state) => state.intl.locale,
|
|
43
|
+
);
|
|
44
|
+
const translations = useSelector<
|
|
45
|
+
FormState,
|
|
46
|
+
GetTranslationResponse['items'] | undefined
|
|
47
|
+
>((state) => state.content.data?.['@components']?.translations?.items);
|
|
48
|
+
const isMultilingual = useSelector<
|
|
49
|
+
FormState,
|
|
50
|
+
GetSiteResponse['features']['multilingual']
|
|
51
|
+
>((state) => state.site.data.features?.multilingual);
|
|
52
|
+
const availableLanguages = useSelector<
|
|
53
|
+
FormState,
|
|
54
|
+
GetSiteResponse['plone.available_languages']
|
|
55
|
+
>((state) => state.site.data?.['plone.available_languages']);
|
|
56
|
+
|
|
57
|
+
return isMultilingual ? (
|
|
58
|
+
<div className="language-selector">
|
|
59
|
+
{availableLanguages?.map((lang) => {
|
|
60
|
+
const langKey = lang as keyof typeof langmap;
|
|
61
|
+
const translation = translations?.find(
|
|
62
|
+
(t: { language: string }) => t.language === lang,
|
|
63
|
+
);
|
|
64
|
+
return (
|
|
65
|
+
<Link
|
|
66
|
+
aria-label={`${intl.formatMessage(
|
|
67
|
+
messages.switchLanguageTo,
|
|
68
|
+
)} ${langmap[langKey].nativeName.toLowerCase()}`}
|
|
69
|
+
className={cx({ selected: toReactIntlLang(lang) === currentLang })}
|
|
70
|
+
to={translation ? flattenToAppURL(translation['@id']) : `/${lang}`}
|
|
71
|
+
title={langmap[langKey].nativeName}
|
|
72
|
+
onClick={() => {
|
|
73
|
+
onClickAction();
|
|
74
|
+
}}
|
|
75
|
+
key={`language-selector-${lang}`}
|
|
76
|
+
>
|
|
77
|
+
{langmap[langKey].nativeName}
|
|
78
|
+
</Link>
|
|
79
|
+
);
|
|
80
|
+
})}
|
|
81
|
+
</div>
|
|
82
|
+
) : (
|
|
83
|
+
<Helmet>
|
|
84
|
+
<html lang={toReactIntlLang(currentLang)} />
|
|
85
|
+
</Helmet>
|
|
86
|
+
);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export default LanguageSelector;
|
|
@@ -3,14 +3,9 @@ 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
|
-
import config from '@plone/volto/registry';
|
|
7
6
|
|
|
8
7
|
import Logo from './Logo';
|
|
9
8
|
|
|
10
|
-
beforeAll(() => {
|
|
11
|
-
config.settings.isMultilingual = true;
|
|
12
|
-
});
|
|
13
|
-
|
|
14
9
|
const mockStore = configureStore();
|
|
15
10
|
|
|
16
11
|
describe('Multilingual Logo', () => {
|
|
@@ -1,28 +1,24 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Redirect } from 'react-router-dom';
|
|
3
|
-
import { useDispatch } from 'react-redux';
|
|
3
|
+
import { useDispatch, useSelector } from 'react-redux';
|
|
4
4
|
import { useCookies } from 'react-cookie';
|
|
5
|
-
import config from '@plone/volto/registry';
|
|
6
5
|
import { changeLanguage } from '@plone/volto/actions/language/language';
|
|
7
6
|
import { toGettextLang } from '@plone/volto/helpers/Utils/Utils';
|
|
8
7
|
|
|
9
8
|
const MultilingualRedirector = (props) => {
|
|
10
|
-
const { settings } = config;
|
|
11
9
|
const { pathname, children } = props;
|
|
12
10
|
const [cookies] = useCookies();
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
? currentLanguage
|
|
18
|
-
: settings.defaultLanguage;
|
|
11
|
+
const site = useSelector((state) => state.site.data);
|
|
12
|
+
const isMultilingual = site.features?.multilingual;
|
|
13
|
+
const redirectToLanguage =
|
|
14
|
+
cookies['I18N_LANGUAGE'] || site['plone.default_language'];
|
|
19
15
|
const dispatch = useDispatch();
|
|
20
16
|
|
|
21
17
|
React.useEffect(() => {
|
|
22
18
|
// ToDo: Add means to support language negotiation (with config)
|
|
23
19
|
// const detectedLang = (navigator.language || navigator.userLanguage).substring(0, 2);
|
|
24
20
|
let mounted = true;
|
|
25
|
-
if (
|
|
21
|
+
if (isMultilingual && pathname === '/') {
|
|
26
22
|
const langFileName = toGettextLang(redirectToLanguage);
|
|
27
23
|
import(
|
|
28
24
|
/* @vite-ignore */ '@root/../locales/' + langFileName + '.json'
|
|
@@ -35,9 +31,9 @@ const MultilingualRedirector = (props) => {
|
|
|
35
31
|
return () => {
|
|
36
32
|
mounted = false;
|
|
37
33
|
};
|
|
38
|
-
}, [pathname, dispatch, redirectToLanguage,
|
|
34
|
+
}, [pathname, dispatch, redirectToLanguage, isMultilingual]);
|
|
39
35
|
|
|
40
|
-
return pathname === '/' &&
|
|
36
|
+
return pathname === '/' && isMultilingual ? (
|
|
41
37
|
<Redirect to={`/${redirectToLanguage}`} />
|
|
42
38
|
) : (
|
|
43
39
|
<>{children}</>
|
|
@@ -3,19 +3,17 @@ 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
|
-
import config from '@plone/volto/registry';
|
|
7
6
|
|
|
8
7
|
import MultilingualRedirector from './MultilingualRedirector';
|
|
9
8
|
|
|
10
|
-
beforeAll(() => {
|
|
11
|
-
config.settings.isMultilingual = true;
|
|
12
|
-
});
|
|
13
|
-
|
|
14
9
|
const mockStore = configureStore();
|
|
15
10
|
|
|
16
11
|
describe('MultilingualRedirector', () => {
|
|
17
12
|
it('renders a MultilingualRedirector component', () => {
|
|
18
13
|
const store = mockStore({
|
|
14
|
+
site: {
|
|
15
|
+
data: {},
|
|
16
|
+
},
|
|
19
17
|
intl: {
|
|
20
18
|
locale: 'en',
|
|
21
19
|
messages: {},
|
|
@@ -15,11 +15,7 @@ const NavItem = ({ item, lang }) => {
|
|
|
15
15
|
key={item.url}
|
|
16
16
|
className="item"
|
|
17
17
|
activeClassName="active"
|
|
18
|
-
exact={
|
|
19
|
-
settings.isMultilingual
|
|
20
|
-
? item.url === `/${toBackendLang(lang)}`
|
|
21
|
-
: item.url === ''
|
|
22
|
-
}
|
|
18
|
+
exact={item.url === '' || item.url === `/${toBackendLang(lang)}`}
|
|
23
19
|
>
|
|
24
20
|
{item.title}
|
|
25
21
|
</NavLink>
|
|
@@ -3,14 +3,9 @@ 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
|
-
import config from '@plone/volto/registry';
|
|
7
6
|
|
|
8
7
|
import Navigation from './Navigation';
|
|
9
8
|
|
|
10
|
-
beforeAll(() => {
|
|
11
|
-
config.settings.isMultilingual = true;
|
|
12
|
-
});
|
|
13
|
-
|
|
14
9
|
const mockStore = configureStore();
|
|
15
10
|
|
|
16
11
|
describe('Navigation Multilingual', () => {
|
|
@@ -19,15 +19,18 @@ import config from '@plone/volto/registry';
|
|
|
19
19
|
const NotFound = () => {
|
|
20
20
|
const dispatch = useDispatch();
|
|
21
21
|
const lang = useSelector((state) => state.intl.locale);
|
|
22
|
+
const isMultilingual = useSelector(
|
|
23
|
+
(state) => state.site.data.features?.multilingual,
|
|
24
|
+
);
|
|
22
25
|
|
|
23
26
|
useEffect(() => {
|
|
24
27
|
dispatch(
|
|
25
28
|
getNavigation(
|
|
26
|
-
|
|
29
|
+
isMultilingual ? `/${toBackendLang(lang)}` : '/',
|
|
27
30
|
config.settings.navDepth,
|
|
28
31
|
),
|
|
29
32
|
);
|
|
30
|
-
}, [dispatch, lang]);
|
|
33
|
+
}, [dispatch, lang, isMultilingual]);
|
|
31
34
|
|
|
32
35
|
return (
|
|
33
36
|
<Container className="view-wrapper">
|
|
@@ -29,7 +29,7 @@ const RequestTimeout = () => (
|
|
|
29
29
|
<h1 style={{ textAlign: 'center', lineHeight: '40px' }}>
|
|
30
30
|
<FormattedMessage
|
|
31
31
|
id="No connection to the server"
|
|
32
|
-
defaultMessage="There is no connection to the server, due to a timeout
|
|
32
|
+
defaultMessage="There is no connection to the server, due to a timeout or no network connection."
|
|
33
33
|
/>
|
|
34
34
|
<br />
|
|
35
35
|
<a href={config.settings.apiPath}>{config.settings.apiPath}</a>
|
|
@@ -36,14 +36,14 @@ function Sitemap(props) {
|
|
|
36
36
|
location: { pathname },
|
|
37
37
|
lang,
|
|
38
38
|
getNavigation,
|
|
39
|
+
isMultilingual,
|
|
39
40
|
} = props;
|
|
40
41
|
|
|
41
42
|
useEffect(() => {
|
|
42
|
-
const
|
|
43
|
-
const language = settings.isMultilingual ? `${toBackendLang(lang)}` : null;
|
|
43
|
+
const language = isMultilingual ? `${toBackendLang(lang)}` : null;
|
|
44
44
|
const path = getSitemapPath(pathname, language);
|
|
45
45
|
getNavigation(path, 4);
|
|
46
|
-
}, [pathname, lang, getNavigation]);
|
|
46
|
+
}, [pathname, lang, getNavigation, isMultilingual]);
|
|
47
47
|
|
|
48
48
|
const renderItems = (items) => {
|
|
49
49
|
return (
|
|
@@ -100,6 +100,7 @@ export default compose(
|
|
|
100
100
|
(state) => ({
|
|
101
101
|
items: state.navigation.items,
|
|
102
102
|
lang: state.intl.locale,
|
|
103
|
+
isMultilingual: state.site.data.features?.multilingual,
|
|
103
104
|
}),
|
|
104
105
|
{ getNavigation },
|
|
105
106
|
),
|
|
@@ -108,10 +109,10 @@ export default compose(
|
|
|
108
109
|
key: 'navigation',
|
|
109
110
|
promise: ({ location, store: { dispatch, getState } }) => {
|
|
110
111
|
if (!__SERVER__) return;
|
|
111
|
-
const
|
|
112
|
+
const state = getState();
|
|
112
113
|
const path = getSitemapPath(
|
|
113
114
|
location.pathname,
|
|
114
|
-
|
|
115
|
+
state.site.data.features?.multilingual
|
|
115
116
|
? toBackendLang(getState().intl.locale)
|
|
116
117
|
: null,
|
|
117
118
|
);
|
|
@@ -7,6 +7,7 @@ import React from 'react';
|
|
|
7
7
|
import PropTypes from 'prop-types';
|
|
8
8
|
import { Container as SemanticContainer } from 'semantic-ui-react';
|
|
9
9
|
import { flattenToAppURL } from '@plone/volto/helpers/Url/Url';
|
|
10
|
+
import { FormattedMessage } from 'react-intl';
|
|
10
11
|
import config from '@plone/volto/registry';
|
|
11
12
|
|
|
12
13
|
/**
|
|
@@ -30,7 +31,14 @@ const FileView = ({ content }) => {
|
|
|
30
31
|
)}
|
|
31
32
|
{content.file?.download && (
|
|
32
33
|
<a href={flattenToAppURL(content.file.download)}>
|
|
33
|
-
{content.file.filename
|
|
34
|
+
{content.file.filename ? (
|
|
35
|
+
content.file.filename
|
|
36
|
+
) : (
|
|
37
|
+
<FormattedMessage
|
|
38
|
+
id="Download file"
|
|
39
|
+
defaultMessage="Download file"
|
|
40
|
+
/>
|
|
41
|
+
)}
|
|
34
42
|
</a>
|
|
35
43
|
)}
|
|
36
44
|
</Container>
|
|
@@ -246,7 +246,7 @@ class View extends Component {
|
|
|
246
246
|
/>
|
|
247
247
|
<SlotRenderer name="aboveContent" content={this.props.content} />
|
|
248
248
|
<RenderedView
|
|
249
|
-
key={this.props.content['@id']}
|
|
249
|
+
key={flattenToAppURL(this.props.content['@id'])}
|
|
250
250
|
content={this.props.content}
|
|
251
251
|
location={this.props.location}
|
|
252
252
|
token={this.props.token}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import cx from 'classnames';
|
|
3
3
|
import { flattenToAppURL } from '@plone/volto/helpers/Url/Url';
|
|
4
|
+
import Image from '@plone/volto/components/theme/Image/Image';
|
|
4
5
|
|
|
5
6
|
const niceBytes = (bytes) => {
|
|
6
7
|
bytes = Number(bytes);
|
|
@@ -18,7 +19,7 @@ const niceBytes = (bytes) => {
|
|
|
18
19
|
const ImageWidget = ({ value, className }) =>
|
|
19
20
|
value ? (
|
|
20
21
|
<span className={cx(className, 'image', 'widget')}>
|
|
21
|
-
<
|
|
22
|
+
<Image
|
|
22
23
|
src={
|
|
23
24
|
value.data
|
|
24
25
|
? `data:${value['content-type']};base64,${value.data}`
|
package/src/config/index.js
CHANGED
|
@@ -25,13 +25,11 @@ import { installDefaultBlocks } from './Blocks';
|
|
|
25
25
|
import { getSiteAsyncPropExtender } from '@plone/volto/helpers/Site';
|
|
26
26
|
import { registerValidators } from './validation';
|
|
27
27
|
|
|
28
|
+
import languages from '@plone/volto/constants/Languages.cjs';
|
|
29
|
+
|
|
28
30
|
const host = process.env.HOST || 'localhost';
|
|
29
31
|
const port = process.env.PORT || '3000';
|
|
30
32
|
|
|
31
|
-
const apiPath =
|
|
32
|
-
process.env.RAZZLE_API_PATH ||
|
|
33
|
-
(__DEVELOPMENT__ ? `http://${host}:${port}` : '');
|
|
34
|
-
|
|
35
33
|
const getServerURL = (url) => {
|
|
36
34
|
if (!url) return;
|
|
37
35
|
const apiPathURL = parseUrl(url);
|
|
@@ -44,12 +42,14 @@ const getServerURL = (url) => {
|
|
|
44
42
|
// if RAZZLE_PUBLIC_URL is present, use it
|
|
45
43
|
// if in DEV, use the host/port combination by default
|
|
46
44
|
// if in PROD, assume it's RAZZLE_API_PATH server name (no /api or alikes) or fallback
|
|
47
|
-
// to DEV settings if RAZZLE_API_PATH is not present
|
|
45
|
+
// to DEV settings if RAZZLE_API_PATH is not present.
|
|
46
|
+
// Finally, add the subpath, if there is one.
|
|
48
47
|
const publicURL =
|
|
49
|
-
process.env.RAZZLE_PUBLIC_URL ||
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
48
|
+
(process.env.RAZZLE_PUBLIC_URL ||
|
|
49
|
+
(__DEVELOPMENT__
|
|
50
|
+
? `http://${host}:${port}`
|
|
51
|
+
: getServerURL(process.env.RAZZLE_API_PATH) ||
|
|
52
|
+
`http://${host}:${port}`)) + (process.env.RAZZLE_SUBPATH_PREFIX || '');
|
|
53
53
|
|
|
54
54
|
const serverConfig =
|
|
55
55
|
typeof __SERVER__ !== 'undefined' && __SERVER__
|
|
@@ -63,7 +63,10 @@ let config = {
|
|
|
63
63
|
// The URL Volto is going to be served (see sensible defaults above)
|
|
64
64
|
publicURL,
|
|
65
65
|
okRoute: '/ok',
|
|
66
|
-
|
|
66
|
+
// Base URL for API requests from the browser.
|
|
67
|
+
// If not explicitly set, use the publicURL (seamless mode) --
|
|
68
|
+
// but in that case it will be updated in server.jsx for each request.
|
|
69
|
+
apiPath: process.env.RAZZLE_API_PATH || publicURL,
|
|
67
70
|
apiExpanders: [
|
|
68
71
|
// Added here for documentation purposes, added at the end because it
|
|
69
72
|
// depends on a value of this object.
|
|
@@ -97,6 +100,7 @@ let config = {
|
|
|
97
100
|
// apiPath: process.env.RAZZLE_API_PATH || 'http://localhost:8081/db/web', // for guillotina
|
|
98
101
|
actions_raising_api_errors: ['GET_CONTENT', 'UPDATE_CONTENT'],
|
|
99
102
|
internalApiPath: process.env.RAZZLE_INTERNAL_API_PATH || undefined,
|
|
103
|
+
subpathPrefix: process.env.RAZZLE_SUBPATH_PREFIX || '',
|
|
100
104
|
websockets: process.env.RAZZLE_WEBSOCKETS || false,
|
|
101
105
|
// TODO: legacyTraverse to be removed when the use of the legacy traverse is deprecated.
|
|
102
106
|
legacyTraverse: process.env.RAZZLE_LEGACY_TRAVERSE || false,
|
|
@@ -111,9 +115,7 @@ let config = {
|
|
|
111
115
|
openExternalLinkInNewTab: false,
|
|
112
116
|
notSupportedBrowsers: ['ie'],
|
|
113
117
|
defaultPageSize: 25,
|
|
114
|
-
|
|
115
|
-
supportedLanguages: ['en'],
|
|
116
|
-
defaultLanguage: process.env.SITE_DEFAULT_LANGUAGE || 'en',
|
|
118
|
+
supportedLanguages: Object.keys(languages),
|
|
117
119
|
navDepth: 1,
|
|
118
120
|
expressMiddleware: serverConfig.expressMiddleware, // BBB
|
|
119
121
|
defaultBlockType: 'slate',
|
|
@@ -141,7 +143,7 @@ let config = {
|
|
|
141
143
|
serverConfig,
|
|
142
144
|
storeExtenders: [],
|
|
143
145
|
showTags: true,
|
|
144
|
-
showRelatedItems:
|
|
146
|
+
showRelatedItems: true,
|
|
145
147
|
controlpanels: [],
|
|
146
148
|
controlPanelsIcons,
|
|
147
149
|
filterControlPanels,
|
|
@@ -198,7 +200,8 @@ config.settings.apiExpanders = [
|
|
|
198
200
|
...config.settings.apiExpanders,
|
|
199
201
|
{
|
|
200
202
|
match: '',
|
|
201
|
-
GET_CONTENT: ['breadcrumbs', 'actions', 'types', 'navroot'],
|
|
203
|
+
GET_CONTENT: ['breadcrumbs', 'actions', 'types', 'navroot', 'translations'],
|
|
204
|
+
// Note: translations is removed in the API middleware if the site is not multilingual.
|
|
202
205
|
},
|
|
203
206
|
{
|
|
204
207
|
match: '',
|
|
@@ -232,16 +235,6 @@ Object.entries(slots).forEach(([slotName, components]) => {
|
|
|
232
235
|
});
|
|
233
236
|
});
|
|
234
237
|
|
|
235
|
-
// Make sure that process.env.SITE_DEFAULT_LANGUAGE is set in availableLanguages
|
|
236
|
-
if (
|
|
237
|
-
process.env.SITE_DEFAULT_LANGUAGE &&
|
|
238
|
-
!config.settings.supportedLanguages.includes(
|
|
239
|
-
process.env.SITE_DEFAULT_LANGUAGE,
|
|
240
|
-
)
|
|
241
|
-
) {
|
|
242
|
-
config.settings.supportedLanguages.push(process.env.SITE_DEFAULT_LANGUAGE);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
238
|
registerValidators(ConfigRegistry);
|
|
246
239
|
installDefaultComponents(ConfigRegistry);
|
|
247
240
|
installDefaultWidgets(ConfigRegistry);
|
package/src/config/server.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import imagesMiddleware from '@plone/volto/express-middleware/images';
|
|
2
2
|
import filesMiddleware from '@plone/volto/express-middleware/files';
|
|
3
3
|
import robotstxtMiddleware from '@plone/volto/express-middleware/robotstxt';
|
|
4
|
-
import okMiddleware from '@plone/volto/express-middleware/ok';
|
|
5
4
|
import sitemapMiddleware from '@plone/volto/express-middleware/sitemap';
|
|
6
5
|
import staticsMiddleware from '@plone/volto/express-middleware/static';
|
|
7
6
|
import devProxyMiddleware from '@plone/volto/express-middleware/devproxy';
|
|
@@ -12,7 +11,6 @@ const settings = {
|
|
|
12
11
|
filesMiddleware(),
|
|
13
12
|
imagesMiddleware(),
|
|
14
13
|
robotstxtMiddleware(),
|
|
15
|
-
okMiddleware(),
|
|
16
14
|
sitemapMiddleware(),
|
|
17
15
|
staticsMiddleware(),
|
|
18
16
|
],
|
|
@@ -10,8 +10,16 @@ import querystring from 'querystring';
|
|
|
10
10
|
import { parse as parseUrl } from 'url';
|
|
11
11
|
|
|
12
12
|
const filter = function (pathname, req) {
|
|
13
|
-
//
|
|
14
|
-
|
|
13
|
+
// Check if pathname is defined, there are some corner cases that pathname is null
|
|
14
|
+
if (pathname) {
|
|
15
|
+
// This is the proxy to the API in case the accept header is 'application/json'
|
|
16
|
+
return (
|
|
17
|
+
config.settings.devProxyToApiPath &&
|
|
18
|
+
pathname.startsWith(`${config.settings.subpathPrefix}/++api++`)
|
|
19
|
+
);
|
|
20
|
+
} else {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
15
23
|
};
|
|
16
24
|
|
|
17
25
|
let _env = null;
|
|
@@ -73,17 +81,24 @@ export default function devProxyMiddleware() {
|
|
|
73
81
|
},
|
|
74
82
|
pathRewrite: (path, req) => {
|
|
75
83
|
const { apiPathURL, instancePath } = getEnv();
|
|
84
|
+
const vhSubpath = config.settings.subpathPrefix
|
|
85
|
+
? config.settings.subpathPrefix
|
|
86
|
+
.split('/')
|
|
87
|
+
.filter(Boolean)
|
|
88
|
+
.map((part) => '/_vh_' + part)
|
|
89
|
+
.join('')
|
|
90
|
+
: '';
|
|
76
91
|
const target =
|
|
77
92
|
config.settings.proxyRewriteTarget ||
|
|
78
93
|
`/VirtualHostBase/${apiPathURL.protocol.slice(0, -1)}/${
|
|
79
94
|
apiPathURL.hostname
|
|
80
|
-
}:${apiPathURL.port}${instancePath}/++api++/VirtualHostRoot`;
|
|
95
|
+
}:${apiPathURL.port}${instancePath}/++api++/VirtualHostRoot${vhSubpath}`;
|
|
81
96
|
|
|
82
|
-
return `${target}${path.replace(
|
|
97
|
+
return `${target}${path.replace(`${config.settings.subpathPrefix}/++api++`, '')}`;
|
|
83
98
|
},
|
|
99
|
+
changeOrigin: true,
|
|
84
100
|
logLevel: process.env.DEBUG_HPM ? 'debug' : 'silent',
|
|
85
101
|
...(process.env.RAZZLE_DEV_PROXY_INSECURE && {
|
|
86
|
-
changeOrigin: true,
|
|
87
102
|
secure: false,
|
|
88
103
|
}),
|
|
89
104
|
});
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import superagent from 'superagent';
|
|
7
7
|
import config from '@plone/volto/registry';
|
|
8
8
|
import { addHeadersFactory } from '@plone/volto/helpers/Proxy/Proxy';
|
|
9
|
+
import { stripSubpathPrefix } from '@plone/volto/helpers/Url/Url';
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Get a resource image/file with authenticated (if token exist) API headers
|
|
@@ -16,9 +17,9 @@ import { addHeadersFactory } from '@plone/volto/helpers/Proxy/Proxy';
|
|
|
16
17
|
export const getAPIResourceWithAuth = (req) =>
|
|
17
18
|
new Promise((resolve, reject) => {
|
|
18
19
|
const { settings } = config;
|
|
19
|
-
const
|
|
20
|
-
|
|
20
|
+
const apiSuffix = settings.legacyTraverse ? '' : '/++api++';
|
|
21
21
|
let apiPath = '';
|
|
22
|
+
|
|
22
23
|
if (settings.internalApiPath && __SERVER__) {
|
|
23
24
|
apiPath = settings.internalApiPath;
|
|
24
25
|
} else if (__DEVELOPMENT__ && settings.devProxyToApiPath) {
|
|
@@ -26,8 +27,12 @@ export const getAPIResourceWithAuth = (req) =>
|
|
|
26
27
|
} else {
|
|
27
28
|
apiPath = settings.apiPath;
|
|
28
29
|
}
|
|
30
|
+
|
|
31
|
+
//strip subpath if any
|
|
32
|
+
const contentPath = stripSubpathPrefix(req.path);
|
|
33
|
+
|
|
29
34
|
const request = superagent
|
|
30
|
-
.get(`${apiPath}${__DEVELOPMENT__ ? '' :
|
|
35
|
+
.get(`${apiPath}${__DEVELOPMENT__ ? '' : apiSuffix}${contentPath}`)
|
|
31
36
|
.maxResponseSize(settings.maxResponseSize)
|
|
32
37
|
.responseType('blob');
|
|
33
38
|
const authToken = req.universalCookies.get('auth_token');
|
package/src/helpers/Api/Api.js
CHANGED
|
@@ -7,7 +7,10 @@ import superagent from 'superagent';
|
|
|
7
7
|
import Cookies from 'universal-cookie';
|
|
8
8
|
import config from '@plone/volto/registry';
|
|
9
9
|
import { addHeadersFactory } from '@plone/volto/helpers/Proxy/Proxy';
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
stripQuerystring,
|
|
12
|
+
stripSubpathPrefix,
|
|
13
|
+
} from '@plone/volto/helpers/Url/Url';
|
|
11
14
|
|
|
12
15
|
const methods = ['get', 'post', 'put', 'patch', 'del'];
|
|
13
16
|
|
|
@@ -19,11 +22,10 @@ const methods = ['get', 'post', 'put', 'patch', 'del'];
|
|
|
19
22
|
*/
|
|
20
23
|
export function formatUrl(path) {
|
|
21
24
|
const { settings } = config;
|
|
22
|
-
const
|
|
25
|
+
const apiSuffix = settings.legacyTraverse ? '' : '/++api++';
|
|
23
26
|
|
|
24
27
|
if (path.startsWith('http://') || path.startsWith('https://')) return path;
|
|
25
28
|
|
|
26
|
-
const adjustedPath = path[0] !== '/' ? `/${path}` : path;
|
|
27
29
|
let apiPath = '';
|
|
28
30
|
if (settings.internalApiPath && __SERVER__) {
|
|
29
31
|
apiPath = settings.internalApiPath;
|
|
@@ -31,7 +33,8 @@ export function formatUrl(path) {
|
|
|
31
33
|
apiPath = settings.apiPath;
|
|
32
34
|
}
|
|
33
35
|
|
|
34
|
-
|
|
36
|
+
const contentPath = stripSubpathPrefix(path[0] !== '/' ? `/${path}` : path);
|
|
37
|
+
return `${apiPath}${apiSuffix}${contentPath}`;
|
|
35
38
|
}
|
|
36
39
|
|
|
37
40
|
/**
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { matchRoutes } from 'react-router-config';
|
|
2
2
|
import { mapSeries, isPromise } from './utils';
|
|
3
3
|
import { endGlobalLoad } from '@plone/volto/actions/asyncConnect/asyncConnect';
|
|
4
|
+
import { stripSubpathPrefix } from '@plone/volto/helpers/Url/Url';
|
|
4
5
|
|
|
5
6
|
export function filterComponents(branch) {
|
|
6
7
|
return branch.reduce((result, { route, match }) => {
|
|
@@ -18,7 +19,9 @@ export function loadAsyncConnect({
|
|
|
18
19
|
filter = () => true,
|
|
19
20
|
...rest
|
|
20
21
|
}) {
|
|
21
|
-
const layered = filterComponents(
|
|
22
|
+
const layered = filterComponents(
|
|
23
|
+
matchRoutes(routes, stripSubpathPrefix(location.pathname)),
|
|
24
|
+
);
|
|
22
25
|
|
|
23
26
|
if (layered.length === 0) {
|
|
24
27
|
return Promise.resolve();
|