@plone/volto 19.0.0-alpha.35 → 19.0.0-alpha.37
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/CHANGELOG.md +54 -0
- package/README.md +1 -1
- package/locales/af/LC_MESSAGES/volto.po +29 -13
- package/locales/af.json +1 -1
- package/locales/ar/LC_MESSAGES/volto.po +29 -13
- package/locales/ar.json +1 -1
- package/locales/bg/LC_MESSAGES/volto.po +29 -13
- package/locales/bg.json +1 -1
- package/locales/bn/LC_MESSAGES/volto.po +29 -13
- package/locales/bn.json +1 -1
- package/locales/ca/LC_MESSAGES/volto.po +32 -16
- package/locales/ca.json +1 -1
- package/locales/cs/LC_MESSAGES/volto.po +30 -14
- package/locales/cs.json +1 -1
- package/locales/cy/LC_MESSAGES/volto.po +29 -13
- package/locales/cy.json +1 -1
- package/locales/da/LC_MESSAGES/volto.po +29 -13
- package/locales/da.json +1 -1
- package/locales/de/LC_MESSAGES/volto.po +32 -16
- package/locales/de.json +1 -1
- package/locales/el/LC_MESSAGES/volto.po +29 -13
- package/locales/el.json +1 -1
- package/locales/en/LC_MESSAGES/volto.po +25 -10
- package/locales/en.json +1 -1
- package/locales/en_AU/LC_MESSAGES/volto.po +29 -13
- package/locales/en_AU.json +1 -1
- package/locales/en_GB/LC_MESSAGES/volto.po +29 -13
- package/locales/en_GB.json +1 -1
- package/locales/eo/LC_MESSAGES/volto.po +29 -13
- package/locales/eo.json +1 -1
- package/locales/es/LC_MESSAGES/volto.po +67 -52
- package/locales/es.json +1 -1
- package/locales/et/LC_MESSAGES/volto.po +29 -13
- package/locales/et.json +1 -1
- package/locales/eu/LC_MESSAGES/volto.po +55 -40
- package/locales/eu.json +1 -1
- package/locales/fa/LC_MESSAGES/volto.po +29 -13
- package/locales/fa.json +1 -1
- package/locales/fi/LC_MESSAGES/volto.po +30 -14
- package/locales/fi.json +1 -1
- package/locales/fr/LC_MESSAGES/volto.po +208 -193
- package/locales/fr.json +1 -1
- package/locales/fu/LC_MESSAGES/volto.po +29 -13
- package/locales/fu.json +1 -1
- package/locales/gl/LC_MESSAGES/volto.po +58 -43
- package/locales/gl.json +1 -1
- package/locales/he/LC_MESSAGES/volto.po +29 -13
- package/locales/he.json +1 -1
- package/locales/hi/LC_MESSAGES/volto.po +34 -18
- package/locales/hi.json +1 -1
- package/locales/hr/LC_MESSAGES/volto.po +30 -14
- package/locales/hr.json +1 -1
- package/locales/hu/LC_MESSAGES/volto.po +29 -13
- package/locales/hu.json +1 -1
- package/locales/hy/LC_MESSAGES/volto.po +29 -13
- package/locales/hy.json +1 -1
- package/locales/id/LC_MESSAGES/volto.po +29 -13
- package/locales/id.json +1 -1
- package/locales/it/LC_MESSAGES/volto.po +34 -18
- package/locales/it.json +1 -1
- package/locales/ja/LC_MESSAGES/volto.po +29 -13
- package/locales/ja.json +1 -1
- package/locales/ka/LC_MESSAGES/volto.po +29 -13
- package/locales/ka.json +1 -1
- package/locales/kn/LC_MESSAGES/volto.po +29 -13
- package/locales/kn.json +1 -1
- package/locales/ko/LC_MESSAGES/volto.po +29 -13
- package/locales/ko.json +1 -1
- package/locales/lt/LC_MESSAGES/volto.po +30 -14
- package/locales/lt.json +1 -1
- package/locales/lv/LC_MESSAGES/volto.po +29 -13
- package/locales/lv.json +1 -1
- package/locales/mi/LC_MESSAGES/volto.po +29 -13
- package/locales/mi.json +1 -1
- package/locales/mk/LC_MESSAGES/volto.po +29 -13
- package/locales/mk.json +1 -1
- package/locales/my/LC_MESSAGES/volto.po +29 -13
- package/locales/my.json +1 -1
- package/locales/nb_NO/LC_MESSAGES/volto.po +29 -13
- package/locales/nb_NO.json +1 -1
- package/locales/nl/LC_MESSAGES/volto.po +69 -53
- package/locales/nl.json +1 -1
- package/locales/nn/LC_MESSAGES/volto.po +29 -13
- package/locales/nn.json +1 -1
- package/locales/pl/LC_MESSAGES/volto.po +30 -14
- package/locales/pl.json +1 -1
- package/locales/pt/LC_MESSAGES/volto.po +30 -14
- package/locales/pt.json +1 -1
- package/locales/pt_BR/LC_MESSAGES/volto.po +54 -39
- package/locales/pt_BR.json +1 -1
- package/locales/rm/LC_MESSAGES/volto.po +29 -13
- package/locales/rm.json +1 -1
- package/locales/ro/LC_MESSAGES/volto.po +30 -15
- package/locales/ro.json +1 -1
- package/locales/ru/LC_MESSAGES/volto.po +30 -14
- package/locales/ru.json +1 -1
- package/locales/sk/LC_MESSAGES/volto.po +30 -14
- package/locales/sk.json +1 -1
- package/locales/sl/LC_MESSAGES/volto.po +29 -13
- package/locales/sl.json +1 -1
- package/locales/sm/LC_MESSAGES/volto.po +29 -13
- package/locales/sm.json +1 -1
- package/locales/sq/LC_MESSAGES/volto.po +29 -13
- package/locales/sq.json +1 -1
- package/locales/sr/LC_MESSAGES/volto.po +30 -14
- package/locales/sr.json +1 -1
- package/locales/sr@cyrl/LC_MESSAGES/volto.po +29 -13
- package/locales/sr@cyrl.json +1 -1
- package/locales/sr@latn/LC_MESSAGES/volto.po +29 -13
- package/locales/sr@latn.json +1 -1
- package/locales/sv/LC_MESSAGES/volto.po +31 -15
- package/locales/sv.json +1 -1
- package/locales/ta/LC_MESSAGES/volto.po +30 -15
- package/locales/ta.json +1 -1
- package/locales/te/LC_MESSAGES/volto.po +29 -13
- package/locales/te.json +1 -1
- package/locales/th/LC_MESSAGES/volto.po +29 -13
- package/locales/th.json +1 -1
- package/locales/to/LC_MESSAGES/volto.po +29 -13
- package/locales/to.json +1 -1
- package/locales/tr/LC_MESSAGES/volto.po +29 -14
- package/locales/tr.json +1 -1
- package/locales/uk/LC_MESSAGES/volto.po +30 -14
- package/locales/uk.json +1 -1
- package/locales/vi/LC_MESSAGES/volto.po +29 -13
- package/locales/vi.json +1 -1
- package/locales/volto.pot +26 -11
- package/locales/zh_CN/LC_MESSAGES/volto.po +29 -14
- package/locales/zh_CN.json +1 -1
- package/locales/zh_Hant/LC_MESSAGES/volto.po +29 -13
- package/locales/zh_Hant.json +1 -1
- package/locales/zh_Hant_HK/LC_MESSAGES/volto.po +29 -13
- package/locales/zh_Hant_HK.json +1 -1
- package/package.json +10 -10
- package/src/components/manage/Add/Add.test.jsx +10 -3
- package/src/components/manage/Aliases/Aliases.test.jsx +5 -2
- package/src/components/manage/BlockChooser/BlockChooser.jsx +7 -10
- package/src/components/manage/Blocks/Block/Edit.jsx +19 -10
- package/src/components/manage/Blocks/Block/Order/Item.jsx +9 -4
- package/src/components/manage/Contents/Contents.test.jsx +7 -4
- package/src/components/manage/Contents/DropZoneContent.jsx +1 -0
- package/src/components/manage/Controlpanels/AddonsControlpanel.test.jsx +7 -4
- package/src/components/manage/Controlpanels/Aliases.test.jsx +7 -4
- package/src/components/manage/Controlpanels/BlockType.tsx +2 -3
- package/src/components/manage/Controlpanels/ContentType.test.jsx +12 -9
- package/src/components/manage/Controlpanels/ContentTypeLayout.test.jsx +12 -9
- package/src/components/manage/Controlpanels/ContentTypes.jsx +9 -2
- package/src/components/manage/Controlpanels/ContentTypes.test.jsx +7 -4
- package/src/components/manage/Controlpanels/Controlpanel.test.jsx +7 -4
- package/src/components/manage/Controlpanels/Controlpanels.test.jsx +13 -8
- package/src/components/manage/Controlpanels/Groups/GroupsControlpanel.test.jsx +7 -4
- package/src/components/manage/Controlpanels/ModerateComments.test.jsx +7 -4
- package/src/components/manage/Controlpanels/Rules/AddRule.test.jsx +7 -4
- package/src/components/manage/Controlpanels/Rules/ConfigureRule.test.jsx +9 -6
- package/src/components/manage/Controlpanels/Rules/EditRule.test.jsx +7 -4
- package/src/components/manage/Controlpanels/Rules/Rules.test.jsx +7 -4
- package/src/components/manage/Controlpanels/UndoControlpanel.test.jsx +7 -4
- package/src/components/manage/Controlpanels/Users/UserGroupMembershipControlPanel.test.jsx +7 -4
- package/src/components/manage/Controlpanels/Users/UsersControlpanel.jsx +58 -5
- package/src/components/manage/Controlpanels/Users/UsersControlpanel.ssr.test.jsx +624 -0
- package/src/components/manage/Controlpanels/Users/UsersControlpanel.test.jsx +21 -8
- package/src/components/manage/Delete/Delete.test.jsx +13 -8
- package/src/components/manage/Diff/Diff.test.jsx +7 -4
- package/src/components/manage/Edit/Edit.test.jsx +11 -6
- package/src/components/manage/Form/Form.jsx +6 -1
- package/src/components/manage/Form/ModalForm.jsx +164 -88
- package/src/components/manage/History/History.test.jsx +15 -8
- package/src/components/manage/LinksToItem/LinksToItem.test.jsx +7 -4
- package/src/components/manage/Multilingual/ManageTranslations.test.jsx +15 -12
- package/src/components/manage/Preferences/ChangePassword.test.jsx +7 -4
- package/src/components/manage/Preferences/PersonalPreferences.test.jsx +9 -6
- package/src/components/manage/Rules/Rules.test.jsx +5 -2
- package/src/components/manage/Sharing/Sharing.test.jsx +9 -6
- package/src/components/manage/Sidebar/ObjectBrowser.jsx +7 -0
- package/src/components/manage/Sidebar/ObjectBrowserBody.jsx +7 -3
- package/src/components/manage/Sidebar/ObjectBrowserBody.test.jsx +52 -0
- package/src/components/manage/Sidebar/Sidebar.jsx +2 -0
- package/src/components/manage/Sidebar/Sidebar.test.jsx +4 -1
- package/src/components/manage/Toolbar/Toolbar.jsx +89 -7
- package/src/components/manage/Toolbar/Toolbar.test.jsx +15 -10
- package/src/components/manage/Widgets/FormFieldWrapper.jsx +7 -5
- package/src/components/manage/Widgets/ObjectBrowserWidget.jsx +1 -0
- package/src/components/manage/Widgets/QuerystringWidget.test.jsx +3 -1
- package/src/components/manage/Widgets/TextWidget.jsx +4 -0
- package/src/components/manage/Widgets/TokenWidget.jsx +142 -186
- package/src/components/theme/App/App.test.jsx +13 -10
- package/src/components/theme/ContactForm/ContactForm.test.jsx +13 -8
- package/src/components/theme/MultilingualRedirector/MultilingualRedirector.test.jsx +6 -3
- package/src/components/theme/Search/Search.jsx +218 -328
- package/src/components/theme/Search/Search.test.jsx +14 -14
- package/src/components/theme/Sitemap/Sitemap.jsx +22 -30
- package/src/components/theme/Sitemap/Sitemap.test.jsx +18 -0
- package/src/components/theme/Unauthorized/Unauthorized.jsx +23 -30
- package/src/components/theme/Unauthorized/Unauthorized.test.jsx +6 -4
- package/src/components/theme/View/View.test.jsx +37 -24
- package/src/config/index.js +1 -0
- package/src/helpers/Api/Api.js +2 -2
- package/src/helpers/I18n/I18n.test.ts +44 -0
- package/src/helpers/I18n/I18n.ts +31 -0
- package/src/helpers/Robots/Robots.js +1 -1
- package/src/helpers/Robots/Robots.test.js +34 -0
- package/src/helpers/index.js +1 -0
- package/theme/themes/pastanaga/collections/form.overrides +21 -0
- package/theme/themes/pastanaga/elements/button.overrides +30 -3
- package/types/components/manage/Controlpanels/Relations/RelationsMatrix.d.ts +1 -1
- package/types/components/manage/Controlpanels/Users/UsersControlpanel.d.ts +2 -6
- package/types/components/manage/Controlpanels/Users/UsersControlpanel.ssr.test.d.ts +1 -0
- package/types/components/manage/Controlpanels/index.d.ts +1 -1
- package/types/components/manage/Multilingual/ManageTranslations.d.ts +1 -1
- package/types/components/manage/Sidebar/ObjectBrowser.d.ts +1 -1
- package/types/components/manage/Sidebar/ObjectBrowserBody.test.d.ts +1 -0
- package/types/components/manage/Widgets/ImageWidget.d.ts +1 -1
- package/types/components/manage/Widgets/InternalUrlWidget.d.ts +1 -1
- package/types/components/manage/Widgets/UrlWidget.d.ts +1 -1
- package/types/components/manage/Widgets/index.d.ts +2 -2
- package/types/components/theme/Search/Search.d.ts +1 -1
- package/types/helpers/I18n/I18n.d.ts +20 -0
- package/types/helpers/index.d.ts +1 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { useEffect } from 'react';
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
3
|
import { compose } from 'redux';
|
|
4
|
-
import { connect } from 'react-redux';
|
|
4
|
+
import { connect, useSelector } from 'react-redux';
|
|
5
5
|
import { asyncConnect } from '@plone/volto/helpers/AsyncConnect';
|
|
6
|
+
import { expandToBackendURL } from '@plone/volto/helpers/Url/Url';
|
|
6
7
|
import { defineMessages, injectIntl } from 'react-intl';
|
|
7
8
|
import { Container as SemanticContainer } from 'semantic-ui-react';
|
|
8
9
|
import Helmet from '@plone/volto/helpers/Helmet/Helmet';
|
|
9
|
-
import { toBackendLang } from '@plone/volto/helpers/Utils/Utils';
|
|
10
10
|
import { Link } from 'react-router-dom';
|
|
11
11
|
import config from '@plone/volto/registry';
|
|
12
12
|
|
|
@@ -20,6 +20,9 @@ const messages = defineMessages({
|
|
|
20
20
|
});
|
|
21
21
|
|
|
22
22
|
export function getSitemapPath(pathname = '', lang) {
|
|
23
|
+
/* This function is deprecated
|
|
24
|
+
* We keep it for backwards compatibility.
|
|
25
|
+
*/
|
|
23
26
|
const prefix = pathname.replace(/\/sitemap$/gm, '').replace(/^\//, '');
|
|
24
27
|
const path = prefix || lang || '';
|
|
25
28
|
return path;
|
|
@@ -32,18 +35,12 @@ export function getSitemapPath(pathname = '', lang) {
|
|
|
32
35
|
* @returns {JSX.Element} - Rendered component.
|
|
33
36
|
*/
|
|
34
37
|
function Sitemap(props) {
|
|
35
|
-
const {
|
|
36
|
-
location: { pathname },
|
|
37
|
-
lang,
|
|
38
|
-
getNavigation,
|
|
39
|
-
isMultilingual,
|
|
40
|
-
} = props;
|
|
41
|
-
|
|
38
|
+
const { getNavigation, navroot } = props;
|
|
42
39
|
useEffect(() => {
|
|
43
|
-
const
|
|
44
|
-
const path =
|
|
45
|
-
getNavigation(path,
|
|
46
|
-
}, [
|
|
40
|
+
const { settings } = config;
|
|
41
|
+
const path = `${expandToBackendURL(navroot?.navroot?.['@id'])}`;
|
|
42
|
+
getNavigation(path, settings.siteMapDepth);
|
|
43
|
+
}, [navroot, getNavigation]);
|
|
47
44
|
|
|
48
45
|
const renderItems = (items) => {
|
|
49
46
|
return (
|
|
@@ -64,12 +61,13 @@ function Sitemap(props) {
|
|
|
64
61
|
const Container =
|
|
65
62
|
config.getComponent({ name: 'Container' }).component || SemanticContainer;
|
|
66
63
|
|
|
64
|
+
const items = useSelector((state) => state.navigation.items);
|
|
67
65
|
return (
|
|
68
66
|
<div id="page-sitemap">
|
|
69
67
|
<Helmet title={props.intl.formatMessage(messages.Sitemap)} />
|
|
70
68
|
<Container className="view-wrapper">
|
|
71
69
|
<h1>{props.intl.formatMessage(messages.Sitemap)} </h1>
|
|
72
|
-
{
|
|
70
|
+
{items && renderItems(items)}
|
|
73
71
|
</Container>
|
|
74
72
|
</div>
|
|
75
73
|
);
|
|
@@ -77,18 +75,15 @@ function Sitemap(props) {
|
|
|
77
75
|
|
|
78
76
|
Sitemap.propTypes = {
|
|
79
77
|
getNavigation: PropTypes.func.isRequired,
|
|
80
|
-
|
|
78
|
+
navroot: PropTypes.object.isRequired,
|
|
81
79
|
intl: PropTypes.object.isRequired,
|
|
82
|
-
lang: PropTypes.string.isRequired,
|
|
83
|
-
items: PropTypes.array.isRequired,
|
|
84
80
|
};
|
|
85
81
|
|
|
86
82
|
export const __test__ = compose(
|
|
87
83
|
injectIntl,
|
|
88
84
|
connect(
|
|
89
85
|
(state) => ({
|
|
90
|
-
|
|
91
|
-
lang: state.intl.locale,
|
|
86
|
+
navroot: state.navroot?.data,
|
|
92
87
|
}),
|
|
93
88
|
{ getNavigation },
|
|
94
89
|
),
|
|
@@ -98,25 +93,22 @@ export default compose(
|
|
|
98
93
|
injectIntl,
|
|
99
94
|
connect(
|
|
100
95
|
(state) => ({
|
|
101
|
-
|
|
102
|
-
lang: state.intl.locale,
|
|
103
|
-
isMultilingual: state.site.data.features?.multilingual,
|
|
96
|
+
navroot: state.navroot?.data,
|
|
104
97
|
}),
|
|
105
98
|
{ getNavigation },
|
|
106
99
|
),
|
|
107
100
|
asyncConnect([
|
|
108
101
|
{
|
|
109
102
|
key: 'navigation',
|
|
110
|
-
promise: ({
|
|
103
|
+
promise: ({ store: { dispatch, getState } }) => {
|
|
111
104
|
if (!__SERVER__) return;
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
105
|
+
const navroot = getState().navroot.data?.navroot?.['@id'];
|
|
106
|
+
return dispatch(
|
|
107
|
+
getNavigation(
|
|
108
|
+
expandToBackendURL(navroot),
|
|
109
|
+
config.settings.siteMapDepth,
|
|
110
|
+
),
|
|
118
111
|
);
|
|
119
|
-
return dispatch(getNavigation(path, 4));
|
|
120
112
|
},
|
|
121
113
|
},
|
|
122
114
|
]),
|
|
@@ -43,6 +43,15 @@ describe('Sitemap', () => {
|
|
|
43
43
|
locale: 'en',
|
|
44
44
|
messages: {},
|
|
45
45
|
},
|
|
46
|
+
navroot: {
|
|
47
|
+
data: {
|
|
48
|
+
navroot: {
|
|
49
|
+
'@id': `http://localhost:8080/Plone/`,
|
|
50
|
+
'@type': 'Plone Site',
|
|
51
|
+
title: 'Plone Site',
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
},
|
|
46
55
|
});
|
|
47
56
|
const component = renderer.create(
|
|
48
57
|
<Provider store={store}>
|
|
@@ -93,6 +102,15 @@ describe('Sitemap in a multilingual site', () => {
|
|
|
93
102
|
locale: 'en',
|
|
94
103
|
messages: {},
|
|
95
104
|
},
|
|
105
|
+
navroot: {
|
|
106
|
+
data: {
|
|
107
|
+
navroot: {
|
|
108
|
+
'@id': `http://localhost:8080/Plone/en`,
|
|
109
|
+
'@type': 'LRF',
|
|
110
|
+
title: 'English',
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
},
|
|
96
114
|
});
|
|
97
115
|
const component = renderer.create(
|
|
98
116
|
<Provider store={store}>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { FormattedMessage } from 'react-intl';
|
|
2
|
-
import { Link } from 'react-router-dom';
|
|
2
|
+
import { Link, Redirect } from 'react-router-dom';
|
|
3
3
|
import { Container } from 'semantic-ui-react';
|
|
4
4
|
import { useSelector } from 'react-redux';
|
|
5
5
|
import { useLocation } from 'react-router-dom';
|
|
@@ -11,6 +11,24 @@ const Unauthorized = () => {
|
|
|
11
11
|
const error_message = useSelector((state) => state.apierror?.message);
|
|
12
12
|
let location = useLocation();
|
|
13
13
|
|
|
14
|
+
if (!token) {
|
|
15
|
+
return (
|
|
16
|
+
<Redirect
|
|
17
|
+
to={{
|
|
18
|
+
pathname: `${location.pathname.replace(/\/$/, '')}/login`,
|
|
19
|
+
search: `?return_url=${encodeURIComponent(location.pathname)}`,
|
|
20
|
+
state: {
|
|
21
|
+
// This is needed to cover the use case of being logged in in
|
|
22
|
+
// another backend (eg. in development), having a token for
|
|
23
|
+
// localhost and try to use it, the login route has to know that
|
|
24
|
+
// it's the same as it comes from a logout
|
|
25
|
+
isLogout: true,
|
|
26
|
+
},
|
|
27
|
+
}}
|
|
28
|
+
/>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
14
32
|
return (
|
|
15
33
|
<Container className="view-wrapper">
|
|
16
34
|
<BodyClass className="view-unauthorized" />
|
|
@@ -19,35 +37,10 @@ const Unauthorized = () => {
|
|
|
19
37
|
</h1>
|
|
20
38
|
<h3>{error_message}</h3>
|
|
21
39
|
<p className="description">
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
/>
|
|
27
|
-
) : (
|
|
28
|
-
<FormattedMessage
|
|
29
|
-
id="You are trying to access a protected resource, please {login} first."
|
|
30
|
-
defaultMessage="You are trying to access a protected resource, please {login} first."
|
|
31
|
-
values={{
|
|
32
|
-
login: (
|
|
33
|
-
<Link
|
|
34
|
-
to={{
|
|
35
|
-
pathname: `${location.pathname.replace(/\/$/, '')}/login`,
|
|
36
|
-
state: {
|
|
37
|
-
// This is needed to cover the use case of being logged in in
|
|
38
|
-
// another backend (eg. in development), having a token for
|
|
39
|
-
// localhost and try to use it, the login route has to know that
|
|
40
|
-
// it's the same as it comes from a logout
|
|
41
|
-
isLogout: true,
|
|
42
|
-
},
|
|
43
|
-
}}
|
|
44
|
-
>
|
|
45
|
-
<FormattedMessage id="log in" defaultMessage="log in" />
|
|
46
|
-
</Link>
|
|
47
|
-
),
|
|
48
|
-
}}
|
|
49
|
-
/>
|
|
50
|
-
)}
|
|
40
|
+
<FormattedMessage
|
|
41
|
+
id="You are trying to access a protected resource."
|
|
42
|
+
defaultMessage="You are trying to access a protected resource."
|
|
43
|
+
/>
|
|
51
44
|
</p>
|
|
52
45
|
<p>
|
|
53
46
|
<FormattedMessage
|
|
@@ -2,14 +2,14 @@ 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
|
-
import { MemoryRouter } from 'react-router-dom';
|
|
5
|
+
import { MemoryRouter, StaticRouter } from 'react-router-dom';
|
|
6
6
|
|
|
7
7
|
import Unauthorized from './Unauthorized';
|
|
8
8
|
|
|
9
9
|
const mockStore = configureStore();
|
|
10
10
|
|
|
11
11
|
describe('Unauthorized', () => {
|
|
12
|
-
it('
|
|
12
|
+
it('redirects to login', () => {
|
|
13
13
|
const store = mockStore({
|
|
14
14
|
userSession: {
|
|
15
15
|
token: null,
|
|
@@ -22,15 +22,17 @@ describe('Unauthorized', () => {
|
|
|
22
22
|
message: 'You are not authorized to access this resource',
|
|
23
23
|
},
|
|
24
24
|
});
|
|
25
|
+
const context = {};
|
|
25
26
|
const component = renderer.create(
|
|
26
27
|
<Provider store={store}>
|
|
27
|
-
<
|
|
28
|
+
<StaticRouter context={context} location="/private">
|
|
28
29
|
<Unauthorized />
|
|
29
|
-
</
|
|
30
|
+
</StaticRouter>
|
|
30
31
|
</Provider>,
|
|
31
32
|
);
|
|
32
33
|
const json = component.toJSON();
|
|
33
34
|
expect(json).toMatchSnapshot();
|
|
35
|
+
expect(context.url).toEqual('/private/login?return_url=%2Fprivate');
|
|
34
36
|
});
|
|
35
37
|
|
|
36
38
|
it('renders an unauthorized component for authenticated user', () => {
|
|
@@ -2,6 +2,7 @@ import React, { useEffect } from 'react';
|
|
|
2
2
|
import { render } from '@testing-library/react';
|
|
3
3
|
import configureStore from 'redux-mock-store';
|
|
4
4
|
import { Provider } from 'react-intl-redux';
|
|
5
|
+
import { CookiesProvider } from 'react-cookie';
|
|
5
6
|
|
|
6
7
|
import View from './View';
|
|
7
8
|
import config from '@plone/volto/registry';
|
|
@@ -158,10 +159,12 @@ describe('View', () => {
|
|
|
158
159
|
});
|
|
159
160
|
const { container } = render(
|
|
160
161
|
<Provider store={store}>
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
162
|
+
<CookiesProvider>
|
|
163
|
+
<>
|
|
164
|
+
<View location={{ pathname: '/test' }} />
|
|
165
|
+
<div id="toolbar"></div>
|
|
166
|
+
</>
|
|
167
|
+
</CookiesProvider>
|
|
165
168
|
</Provider>,
|
|
166
169
|
);
|
|
167
170
|
|
|
@@ -181,10 +184,12 @@ describe('View', () => {
|
|
|
181
184
|
});
|
|
182
185
|
const { container } = render(
|
|
183
186
|
<Provider store={store}>
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
187
|
+
<CookiesProvider>
|
|
188
|
+
<>
|
|
189
|
+
<View location={{ pathname: '/test' }} />
|
|
190
|
+
<div id="toolbar"></div>
|
|
191
|
+
</>
|
|
192
|
+
</CookiesProvider>
|
|
188
193
|
</Provider>,
|
|
189
194
|
);
|
|
190
195
|
|
|
@@ -204,10 +209,12 @@ describe('View', () => {
|
|
|
204
209
|
});
|
|
205
210
|
const { container } = render(
|
|
206
211
|
<Provider store={store}>
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
212
|
+
<CookiesProvider>
|
|
213
|
+
<>
|
|
214
|
+
<View location={{ pathname: '/test' }} />
|
|
215
|
+
<div id="toolbar"></div>
|
|
216
|
+
</>
|
|
217
|
+
</CookiesProvider>
|
|
211
218
|
</Provider>,
|
|
212
219
|
);
|
|
213
220
|
|
|
@@ -227,10 +234,12 @@ describe('View', () => {
|
|
|
227
234
|
});
|
|
228
235
|
const { container } = render(
|
|
229
236
|
<Provider store={store}>
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
237
|
+
<CookiesProvider>
|
|
238
|
+
<>
|
|
239
|
+
<View location={{ pathname: '/test' }} />
|
|
240
|
+
<div id="toolbar"></div>
|
|
241
|
+
</>
|
|
242
|
+
</CookiesProvider>
|
|
234
243
|
</Provider>,
|
|
235
244
|
);
|
|
236
245
|
|
|
@@ -258,20 +267,24 @@ describe('View', () => {
|
|
|
258
267
|
});
|
|
259
268
|
const { rerender } = render(
|
|
260
269
|
<Provider store={store}>
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
270
|
+
<CookiesProvider>
|
|
271
|
+
<>
|
|
272
|
+
<View location={{ pathname: '/test' }} />
|
|
273
|
+
<div id="toolbar"></div>
|
|
274
|
+
</>
|
|
275
|
+
</CookiesProvider>
|
|
265
276
|
</Provider>,
|
|
266
277
|
);
|
|
267
278
|
expect(instanceCount).toBe(1);
|
|
268
279
|
store.getState().content.data['@id'] = '/b';
|
|
269
280
|
rerender(
|
|
270
281
|
<Provider store={store}>
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
282
|
+
<CookiesProvider>
|
|
283
|
+
<>
|
|
284
|
+
<View location={{ pathname: '/test' }} />
|
|
285
|
+
<div id="toolbar"></div>
|
|
286
|
+
</>
|
|
287
|
+
</CookiesProvider>
|
|
275
288
|
</Provider>,
|
|
276
289
|
);
|
|
277
290
|
expect(instanceCount).toBe(2);
|
package/src/config/index.js
CHANGED
package/src/helpers/Api/Api.js
CHANGED
|
@@ -102,7 +102,7 @@ class Api {
|
|
|
102
102
|
request.attach.apply(request, attachment);
|
|
103
103
|
});
|
|
104
104
|
|
|
105
|
-
request.end((err, response) => {
|
|
105
|
+
request.end((err, response = {}) => {
|
|
106
106
|
if (
|
|
107
107
|
checkUrl &&
|
|
108
108
|
request.url &&
|
|
@@ -126,7 +126,7 @@ class Api {
|
|
|
126
126
|
if ([301, 302].includes(err?.status)) {
|
|
127
127
|
return reject({
|
|
128
128
|
code: err.status,
|
|
129
|
-
url: err.response
|
|
129
|
+
url: err.response?.headers?.location,
|
|
130
130
|
});
|
|
131
131
|
}
|
|
132
132
|
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { createIntl, createIntlCache } from 'react-intl';
|
|
2
|
+
import type { IntlShape } from 'react-intl';
|
|
3
|
+
import { formatMessageWithFallback } from './I18n';
|
|
4
|
+
|
|
5
|
+
const buildIntl = (
|
|
6
|
+
locale: string,
|
|
7
|
+
messages: Record<string, string>,
|
|
8
|
+
): IntlShape => createIntl({ locale, messages }, createIntlCache());
|
|
9
|
+
|
|
10
|
+
describe('formatMessageWithFallback', () => {
|
|
11
|
+
it('returns the translated string when the locale catalog has an entry', () => {
|
|
12
|
+
const intl = buildIntl('pt-BR', { Image: 'Imagem' });
|
|
13
|
+
expect(formatMessageWithFallback(intl, 'Image')).toBe('Imagem');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('falls back to the input message when no translation is registered', () => {
|
|
17
|
+
const intl = buildIntl('pt-BR', {});
|
|
18
|
+
expect(formatMessageWithFallback(intl, 'Image')).toBe('Image');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('returns undefined unchanged', () => {
|
|
22
|
+
const intl = buildIntl('en', {});
|
|
23
|
+
expect(formatMessageWithFallback(intl, undefined)).toBeUndefined();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('returns null unchanged', () => {
|
|
27
|
+
const intl = buildIntl('en', {});
|
|
28
|
+
expect(formatMessageWithFallback(intl, null)).toBeNull();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('returns an empty string unchanged', () => {
|
|
32
|
+
const intl = buildIntl('en', {});
|
|
33
|
+
expect(formatMessageWithFallback(intl, '')).toBe('');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('does not call intl.formatMessage when the message is falsy', () => {
|
|
37
|
+
const intl = buildIntl('en', {});
|
|
38
|
+
const spy = vi.spyOn(intl, 'formatMessage');
|
|
39
|
+
formatMessageWithFallback(intl, undefined);
|
|
40
|
+
formatMessageWithFallback(intl, null);
|
|
41
|
+
formatMessageWithFallback(intl, '');
|
|
42
|
+
expect(spy).not.toHaveBeenCalled();
|
|
43
|
+
});
|
|
44
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* I18n helpers.
|
|
3
|
+
* @module helpers/I18n/I18n
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { IntlShape } from 'react-intl';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Format a `react-intl` message using a plain string as both the id and the
|
|
10
|
+
* default fallback. Useful when a UI value (e.g. a block title configured as
|
|
11
|
+
* a plain string) should be looked up in the locale catalog when available,
|
|
12
|
+
* and rendered as-is otherwise.
|
|
13
|
+
*
|
|
14
|
+
* Returns the input untouched when it is falsy, so the result can be used
|
|
15
|
+
* directly in `||` fallback chains.
|
|
16
|
+
*
|
|
17
|
+
* @param intl react-intl `intl` instance, typically from `useIntl()`.
|
|
18
|
+
* @param message The string to translate; used as both `id` and
|
|
19
|
+
* `defaultMessage`.
|
|
20
|
+
* @returns Translated text, or `message` unchanged if falsy / no translation.
|
|
21
|
+
*/
|
|
22
|
+
export function formatMessageWithFallback<T extends string | undefined | null>(
|
|
23
|
+
intl: IntlShape,
|
|
24
|
+
message: T,
|
|
25
|
+
): T extends string ? string : T {
|
|
26
|
+
if (!message) return message as T extends string ? string : T;
|
|
27
|
+
return intl.formatMessage({
|
|
28
|
+
id: message,
|
|
29
|
+
defaultMessage: message,
|
|
30
|
+
}) as T extends string ? string : T;
|
|
31
|
+
}
|
|
@@ -24,7 +24,7 @@ export const generateRobots = (req) =>
|
|
|
24
24
|
request.set('Authorization', `Bearer ${authToken}`);
|
|
25
25
|
}
|
|
26
26
|
request.use(addHeadersFactory(req));
|
|
27
|
-
request.end((error, { text, body }) => {
|
|
27
|
+
request.end((error, { text, body } = {}) => {
|
|
28
28
|
if (error) {
|
|
29
29
|
resolve(text || error);
|
|
30
30
|
} else {
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { generateRobots } from './Robots';
|
|
2
|
+
|
|
3
|
+
const request = {
|
|
4
|
+
set: vi.fn(() => request),
|
|
5
|
+
use: vi.fn(() => request),
|
|
6
|
+
end: vi.fn(),
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
vi.mock('superagent', () => ({
|
|
10
|
+
default: {
|
|
11
|
+
get: vi.fn(() => request),
|
|
12
|
+
},
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
vi.mock('@plone/volto/helpers/Proxy/Proxy', () => ({
|
|
16
|
+
addHeadersFactory: vi.fn(() => vi.fn()),
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
describe('generateRobots', () => {
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
vi.clearAllMocks();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('resolves the original error when superagent does not provide a response', async () => {
|
|
25
|
+
const error = new Error('network error');
|
|
26
|
+
request.end.mockImplementationOnce((callback) => callback(error));
|
|
27
|
+
|
|
28
|
+
await expect(
|
|
29
|
+
generateRobots({
|
|
30
|
+
universalCookies: { get: vi.fn(() => null) },
|
|
31
|
+
}),
|
|
32
|
+
).resolves.toBe(error);
|
|
33
|
+
});
|
|
34
|
+
});
|
package/src/helpers/index.js
CHANGED
|
@@ -112,6 +112,7 @@ export {
|
|
|
112
112
|
normalizeString,
|
|
113
113
|
} from '@plone/volto/helpers/Utils/Utils';
|
|
114
114
|
export { messages } from './MessageLabels/MessageLabels';
|
|
115
|
+
export { formatMessageWithFallback } from './I18n/I18n';
|
|
115
116
|
export {
|
|
116
117
|
withBlockSchemaEnhancer,
|
|
117
118
|
withVariationSchemaEnhancer,
|
|
@@ -31,6 +31,27 @@
|
|
|
31
31
|
line-height: initial;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
// Restore visible focus for keyboard navigation — overrides Semantic UI's `outline: none` on form inputs
|
|
35
|
+
.ui.form input:not([type]),
|
|
36
|
+
.ui.form input[type='date'],
|
|
37
|
+
.ui.form input[type='datetime-local'],
|
|
38
|
+
.ui.form input[type='email'],
|
|
39
|
+
.ui.form input[type='number'],
|
|
40
|
+
.ui.form input[type='password'],
|
|
41
|
+
.ui.form input[type='search'],
|
|
42
|
+
.ui.form input[type='tel'],
|
|
43
|
+
.ui.form input[type='time'],
|
|
44
|
+
.ui.form input[type='text'],
|
|
45
|
+
.ui.form input[type='file'],
|
|
46
|
+
.ui.form input[type='url'],
|
|
47
|
+
.ui.form textarea {
|
|
48
|
+
padding-left: 5px;
|
|
49
|
+
|
|
50
|
+
&:focus-visible {
|
|
51
|
+
outline: revert;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
34
55
|
.ui.form .ui.input input:not([type]),
|
|
35
56
|
.ui.form .ui.input input[type='date'],
|
|
36
57
|
.ui.form .ui.input input[type='datetime-local'],
|
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
#main,
|
|
30
|
-
.slate-inline-toolbar.slate-toolbar
|
|
30
|
+
.slate-inline-toolbar.slate-toolbar,
|
|
31
|
+
.contenttype-plone-site .ui.page.modals {
|
|
31
32
|
.ui.basic.buttons .button,
|
|
32
33
|
.ui.basic.button {
|
|
33
34
|
-webkit-box-shadow: 0px 0px 0px @basicBorderSize transparent inset !important;
|
|
@@ -42,11 +43,16 @@
|
|
|
42
43
|
cursor: pointer;
|
|
43
44
|
text-align: initial;
|
|
44
45
|
|
|
45
|
-
&:focus {
|
|
46
|
+
&:focus:not(:focus-visible) {
|
|
46
47
|
outline: none;
|
|
47
48
|
}
|
|
48
49
|
}
|
|
49
50
|
|
|
51
|
+
.ui.basic.buttons .button:focus-visible,
|
|
52
|
+
.ui.basic.button:focus-visible {
|
|
53
|
+
outline: revert;
|
|
54
|
+
}
|
|
55
|
+
|
|
50
56
|
.ui.basic.primary.button,
|
|
51
57
|
.ui.basic.secondary.button {
|
|
52
58
|
box-shadow: none !important;
|
|
@@ -69,7 +75,7 @@
|
|
|
69
75
|
cursor: pointer;
|
|
70
76
|
text-align: initial;
|
|
71
77
|
|
|
72
|
-
&:focus {
|
|
78
|
+
&:focus:not(:focus-visible) {
|
|
73
79
|
outline: none;
|
|
74
80
|
}
|
|
75
81
|
}
|
|
@@ -85,3 +91,24 @@
|
|
|
85
91
|
margin: 0;
|
|
86
92
|
}
|
|
87
93
|
}
|
|
94
|
+
|
|
95
|
+
.modals {
|
|
96
|
+
.modal {
|
|
97
|
+
.actions {
|
|
98
|
+
display: flex;
|
|
99
|
+
justify-content: flex-end;
|
|
100
|
+
gap: 0.5em;
|
|
101
|
+
|
|
102
|
+
.ui.basic.button {
|
|
103
|
+
display: flex;
|
|
104
|
+
justify-content: center;
|
|
105
|
+
padding: 0.3em;
|
|
106
|
+
margin: 0;
|
|
107
|
+
|
|
108
|
+
svg {
|
|
109
|
+
margin: 0;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -3,7 +3,7 @@ declare const _default: {
|
|
|
3
3
|
state: {
|
|
4
4
|
isObjectBrowserOpen: boolean;
|
|
5
5
|
};
|
|
6
|
-
openObjectBrowser: ({ mode, onSelectItem, dataName, overlay, propDataName, searchableTypes, selectableTypes, maximumSelectionSize, currentPath, onlyFolderishSelectable, }?: {
|
|
6
|
+
openObjectBrowser: ({ mode, onSelectItem, dataName, overlay, propDataName, searchableTypes, selectableTypes, maximumSelectionSize, currentPath, initialPath, onlyFolderishSelectable, }?: {
|
|
7
7
|
mode: string;
|
|
8
8
|
dataName: string;
|
|
9
9
|
onSelectItem: string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -4,7 +4,7 @@ export declare const RulesControlpanel: import("@loadable/component").LoadableCl
|
|
|
4
4
|
export declare const AddRuleControlpanel: import("@loadable/component").LoadableClassComponent<any>;
|
|
5
5
|
export declare const EditRuleControlpanel: import("@loadable/component").LoadableClassComponent<any>;
|
|
6
6
|
export declare const ConfigureRuleControlpanel: import("@loadable/component").LoadableClassComponent<any>;
|
|
7
|
-
export declare const UsersControlpanel: import("@loadable/component").
|
|
7
|
+
export declare const UsersControlpanel: import("@loadable/component").LoadableClassComponent<any>;
|
|
8
8
|
export declare const RenderUsers: import("@loadable/component").LoadableComponent<any>;
|
|
9
9
|
export declare const UserGroupMembershipControlPanel: import("@loadable/component").LoadableComponent<unknown>;
|
|
10
10
|
export declare const GroupsControlpanel: import("@loadable/component").LoadableClassComponent<any>;
|
|
@@ -3,7 +3,7 @@ declare const _default: {
|
|
|
3
3
|
state: {
|
|
4
4
|
isObjectBrowserOpen: boolean;
|
|
5
5
|
};
|
|
6
|
-
openObjectBrowser: ({ mode, onSelectItem, dataName, overlay, propDataName, searchableTypes, selectableTypes, maximumSelectionSize, currentPath, onlyFolderishSelectable, }?: {
|
|
6
|
+
openObjectBrowser: ({ mode, onSelectItem, dataName, overlay, propDataName, searchableTypes, selectableTypes, maximumSelectionSize, currentPath, initialPath, onlyFolderishSelectable, }?: {
|
|
7
7
|
mode: string;
|
|
8
8
|
dataName: string;
|
|
9
9
|
onSelectItem: string;
|
|
@@ -31,7 +31,7 @@ declare function withObjectBrowser(WrappedComponent: any): {
|
|
|
31
31
|
* }),
|
|
32
32
|
* });
|
|
33
33
|
*/
|
|
34
|
-
openObjectBrowser: ({ mode, onSelectItem, dataName, overlay, propDataName, searchableTypes, selectableTypes, maximumSelectionSize, currentPath, onlyFolderishSelectable, }?: {
|
|
34
|
+
openObjectBrowser: ({ mode, onSelectItem, dataName, overlay, propDataName, searchableTypes, selectableTypes, maximumSelectionSize, currentPath, initialPath, onlyFolderishSelectable, }?: {
|
|
35
35
|
mode: string;
|
|
36
36
|
dataName: string;
|
|
37
37
|
onSelectItem: string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|