@plone/volto 18.35.0 → 18.35.1
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 +36 -0
- package/locales/af/LC_MESSAGES/volto.po +5 -5
- package/locales/af.json +1 -1
- package/locales/ar/LC_MESSAGES/volto.po +5 -5
- package/locales/ar.json +1 -1
- package/locales/bg/LC_MESSAGES/volto.po +5 -5
- package/locales/bg.json +1 -1
- package/locales/bn/LC_MESSAGES/volto.po +5 -5
- package/locales/bn.json +1 -1
- package/locales/ca/LC_MESSAGES/volto.po +5 -5
- package/locales/ca.json +1 -1
- package/locales/cs/LC_MESSAGES/volto.po +5 -5
- package/locales/cs.json +1 -1
- package/locales/cy/LC_MESSAGES/volto.po +5 -5
- package/locales/cy.json +1 -1
- package/locales/da/LC_MESSAGES/volto.po +5 -5
- package/locales/da.json +1 -1
- package/locales/de/LC_MESSAGES/volto.po +5 -5
- package/locales/de.json +1 -1
- package/locales/el/LC_MESSAGES/volto.po +5 -5
- package/locales/el.json +1 -1
- package/locales/en/LC_MESSAGES/volto.po +5 -5
- package/locales/en.json +1 -1
- package/locales/en_AU/LC_MESSAGES/volto.po +5 -5
- package/locales/en_AU.json +1 -1
- package/locales/en_GB/LC_MESSAGES/volto.po +5 -5
- package/locales/en_GB.json +1 -1
- package/locales/eo/LC_MESSAGES/volto.po +5 -5
- package/locales/eo.json +1 -1
- package/locales/es/LC_MESSAGES/volto.po +5 -5
- package/locales/es.json +1 -1
- package/locales/et/LC_MESSAGES/volto.po +5 -5
- package/locales/et.json +1 -1
- package/locales/eu/LC_MESSAGES/volto.po +5 -5
- package/locales/eu.json +1 -1
- package/locales/fa/LC_MESSAGES/volto.po +5 -5
- package/locales/fa.json +1 -1
- package/locales/fi/LC_MESSAGES/volto.po +5 -5
- package/locales/fi.json +1 -1
- package/locales/fr/LC_MESSAGES/volto.po +5 -5
- package/locales/fr.json +1 -1
- package/locales/fu/LC_MESSAGES/volto.po +5 -5
- package/locales/fu.json +1 -1
- package/locales/gl/LC_MESSAGES/volto.po +5 -5
- package/locales/gl.json +1 -1
- package/locales/he/LC_MESSAGES/volto.po +5 -5
- package/locales/he.json +1 -1
- package/locales/hi/LC_MESSAGES/volto.po +5 -5
- package/locales/hi.json +1 -1
- package/locales/hr/LC_MESSAGES/volto.po +5 -5
- package/locales/hr.json +1 -1
- package/locales/hu/LC_MESSAGES/volto.po +5 -5
- package/locales/hu.json +1 -1
- package/locales/hy/LC_MESSAGES/volto.po +5 -5
- package/locales/hy.json +1 -1
- package/locales/id/LC_MESSAGES/volto.po +5 -5
- package/locales/id.json +1 -1
- package/locales/it/LC_MESSAGES/volto.po +5 -5
- package/locales/it.json +1 -1
- package/locales/ja/LC_MESSAGES/volto.po +5 -5
- package/locales/ja.json +1 -1
- package/locales/ka/LC_MESSAGES/volto.po +5 -5
- package/locales/ka.json +1 -1
- package/locales/kn/LC_MESSAGES/volto.po +5 -5
- package/locales/kn.json +1 -1
- package/locales/ko/LC_MESSAGES/volto.po +5 -5
- package/locales/ko.json +1 -1
- package/locales/lt/LC_MESSAGES/volto.po +5 -5
- package/locales/lt.json +1 -1
- package/locales/lv/LC_MESSAGES/volto.po +5 -5
- package/locales/lv.json +1 -1
- package/locales/mi/LC_MESSAGES/volto.po +5 -5
- package/locales/mi.json +1 -1
- package/locales/mk/LC_MESSAGES/volto.po +5 -5
- package/locales/mk.json +1 -1
- package/locales/my/LC_MESSAGES/volto.po +5 -5
- package/locales/my.json +1 -1
- package/locales/nb_NO/LC_MESSAGES/volto.po +5 -5
- package/locales/nb_NO.json +1 -1
- package/locales/nl/LC_MESSAGES/volto.po +5 -5
- package/locales/nl.json +1 -1
- package/locales/nn/LC_MESSAGES/volto.po +5 -5
- package/locales/nn.json +1 -1
- package/locales/pl/LC_MESSAGES/volto.po +5 -5
- package/locales/pl.json +1 -1
- package/locales/pt/LC_MESSAGES/volto.po +5 -5
- package/locales/pt.json +1 -1
- package/locales/pt_BR/LC_MESSAGES/volto.po +5 -5
- package/locales/pt_BR.json +1 -1
- package/locales/rm/LC_MESSAGES/volto.po +5 -5
- package/locales/rm.json +1 -1
- package/locales/ro/LC_MESSAGES/volto.po +5 -5
- package/locales/ro.json +1 -1
- package/locales/ru/LC_MESSAGES/volto.po +5 -5
- package/locales/ru.json +1 -1
- package/locales/sk/LC_MESSAGES/volto.po +5 -5
- package/locales/sk.json +1 -1
- package/locales/sl/LC_MESSAGES/volto.po +5 -5
- package/locales/sl.json +1 -1
- package/locales/sm/LC_MESSAGES/volto.po +5 -5
- package/locales/sm.json +1 -1
- package/locales/sq/LC_MESSAGES/volto.po +5 -5
- package/locales/sq.json +1 -1
- package/locales/sr/LC_MESSAGES/volto.po +5 -5
- package/locales/sr.json +1 -1
- package/locales/sr@cyrl/LC_MESSAGES/volto.po +5 -5
- package/locales/sr@cyrl.json +1 -1
- package/locales/sr@latn/LC_MESSAGES/volto.po +5 -5
- package/locales/sr@latn.json +1 -1
- package/locales/sv/LC_MESSAGES/volto.po +5 -5
- package/locales/sv.json +1 -1
- package/locales/ta/LC_MESSAGES/volto.po +5 -5
- package/locales/ta.json +1 -1
- package/locales/te/LC_MESSAGES/volto.po +5 -5
- package/locales/te.json +1 -1
- package/locales/th/LC_MESSAGES/volto.po +5 -5
- package/locales/th.json +1 -1
- package/locales/to/LC_MESSAGES/volto.po +5 -5
- package/locales/to.json +1 -1
- package/locales/tr/LC_MESSAGES/volto.po +5 -5
- package/locales/tr.json +1 -1
- package/locales/uk/LC_MESSAGES/volto.po +5 -5
- package/locales/uk.json +1 -1
- package/locales/vi/LC_MESSAGES/volto.po +5 -5
- package/locales/vi.json +1 -1
- package/locales/volto.pot +6 -6
- package/locales/zh_CN/LC_MESSAGES/volto.po +5 -5
- package/locales/zh_CN.json +1 -1
- package/locales/zh_Hant/LC_MESSAGES/volto.po +5 -5
- package/locales/zh_Hant.json +1 -1
- package/locales/zh_Hant_HK/LC_MESSAGES/volto.po +5 -5
- package/locales/zh_Hant_HK.json +1 -1
- package/package.json +18 -22
- package/src/actions/querystringsearch/querystringsearch.js +4 -1
- package/src/actions/querystringsearch/querystringsearch.test.js +77 -0
- package/src/components/manage/BlockChooser/BlockChooser.jsx +7 -10
- package/src/components/manage/Blocks/Block/Edit.jsx +9 -10
- package/src/components/manage/Blocks/Block/Order/Item.jsx +9 -4
- package/src/components/manage/Blocks/Image/View.test.jsx +8 -8
- package/src/components/manage/Contents/ContentsBreadcrumbs.jsx +7 -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/Widgets/ArrayWidget.jsx +3 -2
- package/src/components/manage/Widgets/FormFieldWrapper.jsx +16 -3
- package/src/components/manage/Widgets/ObjectBrowserWidget.jsx +1 -0
- package/src/components/manage/Widgets/QuerystringWidget.jsx +1 -18
- package/src/components/manage/Widgets/QuerystringWidget.test.jsx +45 -2
- package/src/components/manage/Widgets/SelectStyling.jsx +33 -1
- package/src/components/manage/Widgets/SelectWidget.jsx +3 -2
- package/src/components/manage/Widgets/TextWidget.test.jsx +44 -0
- package/src/components/theme/Unauthorized/Unauthorized.jsx +1 -2
- package/src/components/theme/View/EventView.stories.jsx +89 -0
- package/src/components/theme/View/FileView.stories.jsx +50 -0
- package/src/components/theme/View/LinkView.stories.jsx +57 -0
- package/src/components/theme/View/ListingView.stories.jsx +70 -0
- package/src/components/theme/View/NewsItemView.stories.jsx +58 -0
- package/src/components/theme/View/RenderBlocks.stories.jsx +112 -0
- package/src/components/theme/View/SummaryView.stories.jsx +71 -0
- package/src/components/theme/View/TabularView.stories.jsx +66 -0
- package/src/helpers/I18n/I18n.test.ts +44 -0
- package/src/helpers/I18n/I18n.ts +31 -0
- package/src/helpers/index.js +1 -0
- package/src/server.jsx +7 -1
- package/types/components/manage/Controlpanels/Relations/RelationsMatrix.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/QuerystringWidget.d.ts +0 -4
- package/types/components/manage/Widgets/UrlWidget.d.ts +1 -1
- package/types/components/manage/Widgets/index.d.ts +2 -2
- package/types/components/theme/View/EventView.stories.d.ts +19 -0
- package/types/components/theme/View/FileView.stories.d.ts +18 -0
- package/types/components/theme/View/LinkView.stories.d.ts +18 -0
- package/types/components/theme/View/ListingView.stories.d.ts +24 -0
- package/types/components/theme/View/NewsItemView.stories.d.ts +23 -0
- package/types/components/theme/View/RenderBlocks.stories.d.ts +23 -0
- package/types/components/theme/View/SummaryView.stories.d.ts +23 -0
- package/types/components/theme/View/TabularView.stories.d.ts +23 -0
- package/types/helpers/I18n/I18n.d.ts +20 -0
- package/types/helpers/index.d.ts +1 -0
|
@@ -36,13 +36,13 @@ config.blocks.blocksConfig = {
|
|
|
36
36
|
|
|
37
37
|
describe('Image View Component', () => {
|
|
38
38
|
test('renders a view image component with a local image', () => {
|
|
39
|
-
const {
|
|
40
|
-
const img =
|
|
39
|
+
const { container } = render(<View data={{ url: '/image.jpg' }} />);
|
|
40
|
+
const img = container.querySelector('img');
|
|
41
41
|
expect(img).toHaveAttribute('src', '/image.jpg/@@images/image');
|
|
42
42
|
expect(img).toHaveAttribute('loading', 'lazy');
|
|
43
43
|
});
|
|
44
44
|
test('renders a view image component with a local image with a link', () => {
|
|
45
|
-
const { container
|
|
45
|
+
const { container } = render(
|
|
46
46
|
<Provider store={store}>
|
|
47
47
|
<MemoryRouter>
|
|
48
48
|
<View
|
|
@@ -51,22 +51,22 @@ describe('Image View Component', () => {
|
|
|
51
51
|
</MemoryRouter>
|
|
52
52
|
</Provider>,
|
|
53
53
|
);
|
|
54
|
-
const img =
|
|
54
|
+
const img = container.querySelector('img');
|
|
55
55
|
const a = container.querySelector('a');
|
|
56
56
|
expect(img).toHaveAttribute('src', '/image.jpg/@@images/image');
|
|
57
57
|
expect(a).toHaveAttribute('href', '/front-page');
|
|
58
58
|
});
|
|
59
59
|
test('renders a view image component with an external image', () => {
|
|
60
|
-
const {
|
|
60
|
+
const { container } = render(
|
|
61
61
|
<Provider store={store}>
|
|
62
62
|
<View data={{ url: 'https://plone.org/logo.jpg' }} />
|
|
63
63
|
</Provider>,
|
|
64
64
|
);
|
|
65
|
-
const img =
|
|
65
|
+
const img = container.querySelector('img');
|
|
66
66
|
expect(img).toHaveAttribute('src', 'https://plone.org/logo.jpg');
|
|
67
67
|
});
|
|
68
68
|
test('renders a view image component with an external image with a link', () => {
|
|
69
|
-
const { container
|
|
69
|
+
const { container } = render(
|
|
70
70
|
<Provider store={store}>
|
|
71
71
|
<View
|
|
72
72
|
data={{
|
|
@@ -76,7 +76,7 @@ describe('Image View Component', () => {
|
|
|
76
76
|
/>
|
|
77
77
|
</Provider>,
|
|
78
78
|
);
|
|
79
|
-
const img =
|
|
79
|
+
const img = container.querySelector('img');
|
|
80
80
|
const a = container.querySelector('a');
|
|
81
81
|
expect(img).toHaveAttribute('src', 'https://plone.org/logo.jpg');
|
|
82
82
|
expect(a).toHaveAttribute('href', 'http://front-page');
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Breadcrumb } from 'semantic-ui-react';
|
|
3
|
+
import UniversalLink from '@plone/volto/components/manage/UniversalLink/UniversalLink';
|
|
3
4
|
import { Link, useLocation } from 'react-router-dom';
|
|
4
5
|
import { defineMessages, useIntl } from 'react-intl';
|
|
5
6
|
import langmap from '@plone/volto/helpers/LanguageMap/LanguageMap';
|
|
@@ -41,22 +42,22 @@ const ContentsBreadcrumbs = (props) => {
|
|
|
41
42
|
</>
|
|
42
43
|
)}
|
|
43
44
|
{settings.isMultilingual && pathname?.split('/')?.length > 2 && (
|
|
44
|
-
<
|
|
45
|
-
|
|
45
|
+
<UniversalLink
|
|
46
|
+
href={`/${lang}/contents`}
|
|
46
47
|
className="section"
|
|
47
48
|
title={intl.formatMessage(messages.home)}
|
|
48
49
|
>
|
|
49
50
|
{langmap?.[lang]?.nativeName ?? lang}
|
|
50
|
-
</
|
|
51
|
+
</UniversalLink>
|
|
51
52
|
)}
|
|
52
53
|
{!settings.isMultilingual && (
|
|
53
|
-
<
|
|
54
|
-
|
|
54
|
+
<UniversalLink
|
|
55
|
+
href="/contents"
|
|
55
56
|
className="section"
|
|
56
57
|
title={intl.formatMessage(messages.home)}
|
|
57
58
|
>
|
|
58
59
|
<ContentsBreadcrumbsHomeItem />
|
|
59
|
-
</
|
|
60
|
+
</UniversalLink>
|
|
60
61
|
)}
|
|
61
62
|
{items.map((breadcrumb, index, breadcrumbs) => [
|
|
62
63
|
<Breadcrumb.Divider key={`divider-${breadcrumb.url}`} />,
|
|
@@ -58,6 +58,7 @@ const withObjectBrowser = (WrappedComponent) =>
|
|
|
58
58
|
selectableTypes,
|
|
59
59
|
maximumSelectionSize,
|
|
60
60
|
currentPath,
|
|
61
|
+
initialPath,
|
|
61
62
|
onlyFolderishSelectable,
|
|
62
63
|
} = {}) =>
|
|
63
64
|
this.setState(() => ({
|
|
@@ -71,6 +72,7 @@ const withObjectBrowser = (WrappedComponent) =>
|
|
|
71
72
|
selectableTypes,
|
|
72
73
|
maximumSelectionSize,
|
|
73
74
|
currentPath,
|
|
75
|
+
initialPath,
|
|
74
76
|
onlyFolderishSelectable,
|
|
75
77
|
}));
|
|
76
78
|
|
|
@@ -82,6 +84,10 @@ const withObjectBrowser = (WrappedComponent) =>
|
|
|
82
84
|
this.props.pathname ||
|
|
83
85
|
this.props.location?.pathname;
|
|
84
86
|
|
|
87
|
+
let initialPath = this.state?.initialPath
|
|
88
|
+
? getBaseUrl(this.state.initialPath)
|
|
89
|
+
: null;
|
|
90
|
+
|
|
85
91
|
return (
|
|
86
92
|
<>
|
|
87
93
|
<WrappedComponent
|
|
@@ -105,6 +111,7 @@ const withObjectBrowser = (WrappedComponent) =>
|
|
|
105
111
|
: this.props.data
|
|
106
112
|
}
|
|
107
113
|
contextURL={getBaseUrl(contextURL)}
|
|
114
|
+
initialPath={initialPath}
|
|
108
115
|
closeObjectBrowser={this.closeObjectBrowser}
|
|
109
116
|
mode={this.state.mode}
|
|
110
117
|
onSelectItem={this.state.onSelectItem}
|
|
@@ -84,6 +84,7 @@ class ObjectBrowserBody extends Component {
|
|
|
84
84
|
onSelectItem: PropTypes.func,
|
|
85
85
|
dataName: PropTypes.string,
|
|
86
86
|
maximumSelectionSize: PropTypes.number,
|
|
87
|
+
initialPath: PropTypes.string,
|
|
87
88
|
contextURL: PropTypes.string,
|
|
88
89
|
searchableTypes: PropTypes.arrayOf(PropTypes.string),
|
|
89
90
|
onlyFolderishSelectable: PropTypes.bool,
|
|
@@ -113,18 +114,21 @@ class ObjectBrowserBody extends Component {
|
|
|
113
114
|
*/
|
|
114
115
|
constructor(props) {
|
|
115
116
|
super(props);
|
|
117
|
+
const defaultMultiplePath = props.initialPath || '/';
|
|
116
118
|
this.state = {
|
|
117
119
|
currentFolder:
|
|
118
|
-
this.props.mode === 'multiple'
|
|
120
|
+
this.props.mode === 'multiple'
|
|
121
|
+
? defaultMultiplePath
|
|
122
|
+
: this.props.contextURL || '/',
|
|
119
123
|
currentImageFolder:
|
|
120
124
|
this.props.mode === 'multiple'
|
|
121
|
-
?
|
|
125
|
+
? defaultMultiplePath
|
|
122
126
|
: this.props.mode === 'image' && this.props.data?.url
|
|
123
127
|
? getParentURL(this.props.data.url)
|
|
124
128
|
: '/',
|
|
125
129
|
currentLinkFolder:
|
|
126
130
|
this.props.mode === 'multiple'
|
|
127
|
-
?
|
|
131
|
+
? defaultMultiplePath
|
|
128
132
|
: this.props.mode === 'link' && this.props.data?.href
|
|
129
133
|
? getParentURL(this.props.data.href)
|
|
130
134
|
: '/',
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render } from '@testing-library/react';
|
|
3
|
+
import configureStore from 'redux-mock-store';
|
|
4
|
+
import { Provider } from 'react-intl-redux';
|
|
5
|
+
import ObjectBrowserBody from './ObjectBrowserBody';
|
|
6
|
+
|
|
7
|
+
const mockStore = configureStore();
|
|
8
|
+
|
|
9
|
+
const baseState = {
|
|
10
|
+
search: { subrequests: {} },
|
|
11
|
+
intl: { locale: 'en', messages: {} },
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const baseProps = {
|
|
15
|
+
block: 'test-block',
|
|
16
|
+
data: {},
|
|
17
|
+
closeObjectBrowser: () => {},
|
|
18
|
+
onChangeBlock: () => {},
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const getInitialSearchPath = (actions) => {
|
|
22
|
+
const action = actions.find((a) => a.type === 'SEARCH_CONTENT');
|
|
23
|
+
return action?.request?.path?.split('/@search')[0];
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
describe('ObjectBrowserBody', () => {
|
|
27
|
+
it('uses initialPath as the default folder when mode=multiple', () => {
|
|
28
|
+
const store = mockStore(baseState);
|
|
29
|
+
render(
|
|
30
|
+
<Provider store={store}>
|
|
31
|
+
<ObjectBrowserBody
|
|
32
|
+
{...baseProps}
|
|
33
|
+
mode="multiple"
|
|
34
|
+
initialPath="/company/team"
|
|
35
|
+
/>
|
|
36
|
+
</Provider>,
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
expect(getInitialSearchPath(store.getActions())).toBe('/company/team');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('defaults to root when mode=multiple and initialPath is not provided', () => {
|
|
43
|
+
const store = mockStore(baseState);
|
|
44
|
+
render(
|
|
45
|
+
<Provider store={store}>
|
|
46
|
+
<ObjectBrowserBody {...baseProps} mode="multiple" />
|
|
47
|
+
</Provider>,
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
expect(getInitialSearchPath(store.getActions())).toBe('/');
|
|
51
|
+
});
|
|
52
|
+
});
|
|
@@ -315,7 +315,8 @@ class ArrayWidget extends Component {
|
|
|
315
315
|
// small fix for https://github.com/clauderic/react-sortable-hoc/pull/352:
|
|
316
316
|
getHelperDimensions={({ node }) => node.getBoundingClientRect()}
|
|
317
317
|
id={`field-${this.props.id}`}
|
|
318
|
-
|
|
318
|
+
fieldTitle={this.props.title}
|
|
319
|
+
aria-label={this.props.title || undefined}
|
|
319
320
|
key={this.props.id}
|
|
320
321
|
isDisabled={this.props.disabled || this.props.isDisabled}
|
|
321
322
|
className="react-select-container"
|
|
@@ -384,7 +385,7 @@ class ArrayWidget extends Component {
|
|
|
384
385
|
)
|
|
385
386
|
)
|
|
386
387
|
}
|
|
387
|
-
isClearable
|
|
388
|
+
isClearable={!this.props.required}
|
|
388
389
|
isMulti
|
|
389
390
|
/>
|
|
390
391
|
</FormFieldWrapper>
|
|
@@ -2,9 +2,14 @@
|
|
|
2
2
|
* FormFieldWrapper component.
|
|
3
3
|
* @module components/manage/Widgets/FormFieldWrapper
|
|
4
4
|
*/
|
|
5
|
-
import React, {
|
|
5
|
+
import React, {
|
|
6
|
+
Children,
|
|
7
|
+
Component,
|
|
8
|
+
isValidElement,
|
|
9
|
+
cloneElement,
|
|
10
|
+
} from 'react';
|
|
6
11
|
import PropTypes from 'prop-types';
|
|
7
|
-
import { Form, Grid, Icon as IconOld, Label } from 'semantic-ui-react';
|
|
12
|
+
import { Form, Grid, Icon as IconOld, Input, Label } from 'semantic-ui-react';
|
|
8
13
|
import map from 'lodash/map';
|
|
9
14
|
import cx from 'classnames';
|
|
10
15
|
import { defineMessages, injectIntl } from 'react-intl';
|
|
@@ -103,7 +108,15 @@ class FormFieldWrapper extends Component {
|
|
|
103
108
|
|
|
104
109
|
const wdg = (
|
|
105
110
|
<>
|
|
106
|
-
{this.props.children
|
|
111
|
+
{Children.map(this.props.children, (child) => {
|
|
112
|
+
if (isValidElement(child) && required && child.type === Input) {
|
|
113
|
+
return cloneElement(child, {
|
|
114
|
+
'aria-required': true,
|
|
115
|
+
'aria-invalid': !!(error && error.length > 0),
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
return child;
|
|
119
|
+
})}
|
|
107
120
|
|
|
108
121
|
<div aria-live="polite" aria-atomic="true">
|
|
109
122
|
{map(error, (message) => (
|
|
@@ -307,6 +307,7 @@ export class ObjectBrowserWidgetComponent extends Component {
|
|
|
307
307
|
this.props.openObjectBrowser({
|
|
308
308
|
mode: this.props.mode,
|
|
309
309
|
currentPath: this.props.initialPath || this.props.location.pathname,
|
|
310
|
+
initialPath: this.props.initialPath,
|
|
310
311
|
propDataName: 'value',
|
|
311
312
|
onSelectItem: (url, item) => {
|
|
312
313
|
this.onChange(item);
|
|
@@ -7,10 +7,6 @@ const messages = defineMessages({
|
|
|
7
7
|
id: 'Criteria',
|
|
8
8
|
defaultMessage: 'Criteria',
|
|
9
9
|
},
|
|
10
|
-
depth: {
|
|
11
|
-
id: 'Depth',
|
|
12
|
-
defaultMessage: 'Depth',
|
|
13
|
-
},
|
|
14
10
|
SortOn: {
|
|
15
11
|
id: 'Sort on',
|
|
16
12
|
defaultMessage: 'Sort on',
|
|
@@ -38,16 +34,7 @@ export const objectSchema = ({ intl, isDisabled, value }) => ({
|
|
|
38
34
|
{
|
|
39
35
|
id: 'default',
|
|
40
36
|
title: 'Default',
|
|
41
|
-
fields: [
|
|
42
|
-
'query',
|
|
43
|
-
...(value?.query?.filter((q) => q.i === 'path').length > 0
|
|
44
|
-
? ['depth']
|
|
45
|
-
: []),
|
|
46
|
-
'sort_on',
|
|
47
|
-
'sort_order_boolean',
|
|
48
|
-
'limit',
|
|
49
|
-
'b_size',
|
|
50
|
-
],
|
|
37
|
+
fields: ['query', 'sort_on', 'sort_order_boolean', 'limit', 'b_size'],
|
|
51
38
|
},
|
|
52
39
|
],
|
|
53
40
|
properties: {
|
|
@@ -55,10 +42,6 @@ export const objectSchema = ({ intl, isDisabled, value }) => ({
|
|
|
55
42
|
title: intl.formatMessage(messages.Criteria),
|
|
56
43
|
widget: 'query',
|
|
57
44
|
},
|
|
58
|
-
depth: {
|
|
59
|
-
title: intl.formatMessage(messages.depth),
|
|
60
|
-
type: 'number',
|
|
61
|
-
},
|
|
62
45
|
sort_on: {
|
|
63
46
|
title: intl.formatMessage(messages.SortOn),
|
|
64
47
|
widget: 'query_sort_on',
|
|
@@ -4,7 +4,7 @@ import configureStore from 'redux-mock-store';
|
|
|
4
4
|
import { Provider } from 'react-intl-redux';
|
|
5
5
|
import { waitFor } from '@testing-library/react';
|
|
6
6
|
|
|
7
|
-
import QuerystringWidget from './QuerystringWidget';
|
|
7
|
+
import QuerystringWidget, { objectSchema } from './QuerystringWidget';
|
|
8
8
|
|
|
9
9
|
const mockStore = configureStore();
|
|
10
10
|
|
|
@@ -25,6 +25,47 @@ test('renders an querystring widget component', async () => {
|
|
|
25
25
|
expect(component.toJSON()).toMatchSnapshot();
|
|
26
26
|
});
|
|
27
27
|
|
|
28
|
+
describe('objectSchema', () => {
|
|
29
|
+
const mockIntl = { formatMessage: ({ defaultMessage }) => defaultMessage };
|
|
30
|
+
|
|
31
|
+
it('does not include depth in the default fieldset', () => {
|
|
32
|
+
const schema = objectSchema({ intl: mockIntl });
|
|
33
|
+
const fields = schema.fieldsets[0].fields;
|
|
34
|
+
|
|
35
|
+
expect(fields).not.toContain('depth');
|
|
36
|
+
expect(fields).toEqual([
|
|
37
|
+
'query',
|
|
38
|
+
'sort_on',
|
|
39
|
+
'sort_order_boolean',
|
|
40
|
+
'limit',
|
|
41
|
+
'b_size',
|
|
42
|
+
]);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('does not include depth in the schema properties', () => {
|
|
46
|
+
const schema = objectSchema({ intl: mockIntl });
|
|
47
|
+
|
|
48
|
+
expect(schema.properties).not.toHaveProperty('depth');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('does not include depth even when value contains a path criterion', () => {
|
|
52
|
+
const value = {
|
|
53
|
+
query: [
|
|
54
|
+
{
|
|
55
|
+
i: 'path',
|
|
56
|
+
o: 'plone.app.querystring.operation.string.path',
|
|
57
|
+
v: '/folder',
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
};
|
|
61
|
+
const schema = objectSchema({ intl: mockIntl, value });
|
|
62
|
+
const fields = schema.fieldsets[0].fields;
|
|
63
|
+
|
|
64
|
+
expect(fields).not.toContain('depth');
|
|
65
|
+
expect(schema.properties).not.toHaveProperty('depth');
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
28
69
|
test('can take a schemaEnhancer', async () => {
|
|
29
70
|
const store = mockStore({
|
|
30
71
|
querystring: { indexes: {} },
|
|
@@ -51,6 +92,8 @@ test('can take a schemaEnhancer', async () => {
|
|
|
51
92
|
/>
|
|
52
93
|
</Provider>,
|
|
53
94
|
);
|
|
54
|
-
await waitFor(() => {
|
|
95
|
+
await waitFor(() => {
|
|
96
|
+
expect(component.toJSON()?.children).toHaveLength(3);
|
|
97
|
+
});
|
|
55
98
|
expect(component.toJSON()).toMatchSnapshot();
|
|
56
99
|
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import { defineMessages, useIntl } from 'react-intl';
|
|
2
3
|
import { Popup } from 'semantic-ui-react';
|
|
3
4
|
import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable';
|
|
4
5
|
import Icon from '@plone/volto/components/theme/Icon/Icon';
|
|
@@ -10,6 +11,13 @@ import checkSVG from '@plone/volto/icons/check.svg';
|
|
|
10
11
|
import checkBlankSVG from '@plone/volto/icons/check-blank.svg';
|
|
11
12
|
import clearSVG from '@plone/volto/icons/clear.svg';
|
|
12
13
|
|
|
14
|
+
const messages = defineMessages({
|
|
15
|
+
clearSelection: {
|
|
16
|
+
id: 'Clear selection',
|
|
17
|
+
defaultMessage: 'Clear selection',
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
|
|
13
21
|
export const MenuList = ({ children }) => {
|
|
14
22
|
return <DynamicHeightList>{children}</DynamicHeightList>;
|
|
15
23
|
};
|
|
@@ -86,8 +94,32 @@ export const DropdownIndicator = injectLazyLibs('reactSelect')((props) => {
|
|
|
86
94
|
|
|
87
95
|
export const ClearIndicator = injectLazyLibs('reactSelect')((props) => {
|
|
88
96
|
const { ClearIndicator } = props.reactSelect.components;
|
|
97
|
+
const intl = useIntl();
|
|
98
|
+
const fieldLabelId = props.selectProps?.['aria-labelledby'];
|
|
99
|
+
const clearLabelId = `${props.selectProps?.inputId}-clear-label`;
|
|
89
100
|
return (
|
|
90
|
-
<ClearIndicator
|
|
101
|
+
<ClearIndicator
|
|
102
|
+
{...props}
|
|
103
|
+
innerProps={{
|
|
104
|
+
...props.innerProps,
|
|
105
|
+
'aria-hidden': false,
|
|
106
|
+
...(fieldLabelId
|
|
107
|
+
? { 'aria-labelledby': `${fieldLabelId} ${clearLabelId}` }
|
|
108
|
+
: { 'aria-label': intl.formatMessage(messages.clearSelection) }),
|
|
109
|
+
role: 'button',
|
|
110
|
+
tabIndex: 0,
|
|
111
|
+
onKeyDown: (e) => {
|
|
112
|
+
if (e.key === 'Enter' || e.key === ' ' || e.key === 'Spacebar') {
|
|
113
|
+
e.preventDefault();
|
|
114
|
+
e.stopPropagation();
|
|
115
|
+
props.clearValue();
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
}}
|
|
119
|
+
>
|
|
120
|
+
<span id={clearLabelId} hidden>
|
|
121
|
+
{intl.formatMessage(messages.clearSelection)}
|
|
122
|
+
</span>
|
|
91
123
|
<Icon name={clearSVG} size="18px" color="#e40166" />
|
|
92
124
|
</ClearIndicator>
|
|
93
125
|
);
|
|
@@ -263,9 +263,10 @@ class SelectWidget extends Component {
|
|
|
263
263
|
<FormFieldWrapper {...this.props}>
|
|
264
264
|
<Select
|
|
265
265
|
id={`field-${id}`}
|
|
266
|
+
fieldTitle={this.props.title}
|
|
266
267
|
key={choices}
|
|
267
268
|
name={id}
|
|
268
|
-
aria-
|
|
269
|
+
aria-label={this.props.title || undefined}
|
|
269
270
|
menuShouldScrollIntoView={false}
|
|
270
271
|
isDisabled={disabled}
|
|
271
272
|
isSearchable={true}
|
|
@@ -304,7 +305,7 @@ class SelectWidget extends Component {
|
|
|
304
305
|
: undefined,
|
|
305
306
|
);
|
|
306
307
|
}}
|
|
307
|
-
isClearable={this.props.isClearable}
|
|
308
|
+
isClearable={!this.props.required && this.props.isClearable}
|
|
308
309
|
/>
|
|
309
310
|
</FormFieldWrapper>
|
|
310
311
|
);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import renderer from 'react-test-renderer';
|
|
3
|
+
import { render, screen } from '@testing-library/react';
|
|
3
4
|
import configureStore from 'redux-mock-store';
|
|
4
5
|
import { Provider } from 'react-intl-redux';
|
|
5
6
|
|
|
@@ -30,3 +31,46 @@ test('renders a text widget component', () => {
|
|
|
30
31
|
const json = component.toJSON();
|
|
31
32
|
expect(json).toMatchSnapshot();
|
|
32
33
|
});
|
|
34
|
+
|
|
35
|
+
test('adds aria-required attribute to input when required prop is true', () => {
|
|
36
|
+
const store = mockStore({
|
|
37
|
+
intl: { locale: 'en', messages: {} },
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
render(
|
|
41
|
+
<Provider store={store}>
|
|
42
|
+
<TextWidget
|
|
43
|
+
id="my-field"
|
|
44
|
+
title="My field"
|
|
45
|
+
onChange={() => {}}
|
|
46
|
+
onBlur={() => {}}
|
|
47
|
+
onClick={() => {}}
|
|
48
|
+
required={true}
|
|
49
|
+
/>
|
|
50
|
+
</Provider>,
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
expect(screen.getByRole('textbox')).toHaveAttribute('aria-required', 'true');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('adds aria-invalid attribute to input when field has errors', () => {
|
|
57
|
+
const store = mockStore({
|
|
58
|
+
intl: { locale: 'en', messages: {} },
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
render(
|
|
62
|
+
<Provider store={store}>
|
|
63
|
+
<TextWidget
|
|
64
|
+
id="my-field"
|
|
65
|
+
title="My field"
|
|
66
|
+
onChange={() => {}}
|
|
67
|
+
onBlur={() => {}}
|
|
68
|
+
onClick={() => {}}
|
|
69
|
+
required={true}
|
|
70
|
+
error={['This field is required']}
|
|
71
|
+
/>
|
|
72
|
+
</Provider>,
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
expect(screen.getByRole('textbox')).toHaveAttribute('aria-invalid', 'true');
|
|
76
|
+
});
|
|
@@ -4,7 +4,6 @@ import { Container } from 'semantic-ui-react';
|
|
|
4
4
|
import { useSelector } from 'react-redux';
|
|
5
5
|
import { useLocation } from 'react-router-dom';
|
|
6
6
|
import { withServerErrorCode } from '@plone/volto/helpers/Utils/Utils';
|
|
7
|
-
import { getBaseUrl } from '@plone/volto/helpers/Url/Url';
|
|
8
7
|
import BodyClass from '@plone/volto/helpers/BodyClass/BodyClass';
|
|
9
8
|
|
|
10
9
|
const Unauthorized = () => {
|
|
@@ -33,7 +32,7 @@ const Unauthorized = () => {
|
|
|
33
32
|
login: (
|
|
34
33
|
<Link
|
|
35
34
|
to={{
|
|
36
|
-
pathname: `${
|
|
35
|
+
pathname: `${location.pathname.replace(/\/$/, '')}/login`,
|
|
37
36
|
state: {
|
|
38
37
|
// This is needed to cover the use case of being logged in in
|
|
39
38
|
// another backend (eg. in development), having a token for
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { injectIntl } from 'react-intl';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import EventViewComponent from './EventView';
|
|
4
|
+
import { RealStoreWrapper as Wrapper } from '@plone/volto/storybook';
|
|
5
|
+
import config from '@plone/volto/registry';
|
|
6
|
+
const IntlEventViewComponent = injectIntl(EventViewComponent);
|
|
7
|
+
const { settings } = config;
|
|
8
|
+
|
|
9
|
+
function StoryComponent(args) {
|
|
10
|
+
return (
|
|
11
|
+
<Wrapper
|
|
12
|
+
customStore={{
|
|
13
|
+
intl: {
|
|
14
|
+
locale: 'en',
|
|
15
|
+
messages: {},
|
|
16
|
+
},
|
|
17
|
+
}}
|
|
18
|
+
>
|
|
19
|
+
<div id="toolbar" style={{ display: 'none' }} />
|
|
20
|
+
<IntlEventViewComponent
|
|
21
|
+
content={{
|
|
22
|
+
'@id': 'http://localhost:8080/Plone/my-page',
|
|
23
|
+
...args,
|
|
24
|
+
}}
|
|
25
|
+
/>
|
|
26
|
+
</Wrapper>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const Default = StoryComponent.bind({});
|
|
31
|
+
Default.args = {
|
|
32
|
+
title: 'Hello World!',
|
|
33
|
+
end: '2019-06-23T16:20:00+00:00',
|
|
34
|
+
start: '2019-06-23T15:20:00+00:00',
|
|
35
|
+
attendees: [],
|
|
36
|
+
subjects: [],
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const EventViewAllProps = StoryComponent.bind({});
|
|
40
|
+
EventViewAllProps.args = {
|
|
41
|
+
title: 'Hello World!',
|
|
42
|
+
description: 'Hi',
|
|
43
|
+
text: {
|
|
44
|
+
data: '<p>Hello World!</p>',
|
|
45
|
+
},
|
|
46
|
+
attendees: ['John Doe', 'Mario Rossi'],
|
|
47
|
+
contact_email: 'test@example.com',
|
|
48
|
+
contact_name: 'John Doe',
|
|
49
|
+
contact_phone: '0123456789',
|
|
50
|
+
end: '2019-06-24T15:20:00+00:00',
|
|
51
|
+
event_url: 'https://www.example.com',
|
|
52
|
+
location: 'Volto, Plone',
|
|
53
|
+
open_end: false,
|
|
54
|
+
recurrence: 'RRULE:FREQ=DAILY;INTERVAL=7;COUNT=7',
|
|
55
|
+
start: '2019-06-23T15:20:00+00:00',
|
|
56
|
+
subjects: ['Guillotina', 'Volto'],
|
|
57
|
+
whole_day: false,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export const EventViewWithoutLinkToApiInTheText = StoryComponent.bind({});
|
|
61
|
+
EventViewWithoutLinkToApiInTheText.args = {
|
|
62
|
+
title: 'Hello World!',
|
|
63
|
+
attendees: [],
|
|
64
|
+
end: '2019-06-23T16:20:00+00:00',
|
|
65
|
+
start: '2019-06-23T15:20:00+00:00',
|
|
66
|
+
subjects: [],
|
|
67
|
+
text: {
|
|
68
|
+
data: `<p>Hello World!</p><p>This is an <a href="${settings.apiPath}/foo/bar">internal link</a> and a <a href="${settings.apiPath}/foo/baz">second link</a></p>`,
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
export default {
|
|
72
|
+
title: 'Public components/View/EventView',
|
|
73
|
+
component: EventViewComponent,
|
|
74
|
+
decorators: [
|
|
75
|
+
(Story) => (
|
|
76
|
+
<div className="ui segment form attached" style={{ width: '900px' }}>
|
|
77
|
+
<Story />
|
|
78
|
+
</div>
|
|
79
|
+
),
|
|
80
|
+
],
|
|
81
|
+
argTypes: {
|
|
82
|
+
end: {
|
|
83
|
+
control: 'date',
|
|
84
|
+
},
|
|
85
|
+
start: {
|
|
86
|
+
control: 'date',
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { injectIntl } from 'react-intl';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import FileViewComponent from './FileView';
|
|
4
|
+
import { RealStoreWrapper as Wrapper } from '@plone/volto/storybook';
|
|
5
|
+
|
|
6
|
+
const IntlFileViewComponent = injectIntl(FileViewComponent);
|
|
7
|
+
|
|
8
|
+
function StoryComponent(args) {
|
|
9
|
+
return (
|
|
10
|
+
<Wrapper customStore={{}}>
|
|
11
|
+
<div id="toolbar" style={{ display: 'none' }} />
|
|
12
|
+
<IntlFileViewComponent
|
|
13
|
+
content={{
|
|
14
|
+
...args,
|
|
15
|
+
file: {
|
|
16
|
+
download: 'file:///preview.pdf',
|
|
17
|
+
...args,
|
|
18
|
+
},
|
|
19
|
+
}}
|
|
20
|
+
/>
|
|
21
|
+
</Wrapper>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const Default = StoryComponent.bind({});
|
|
26
|
+
Default.args = {
|
|
27
|
+
title: 'Hello World!',
|
|
28
|
+
description: 'Hi',
|
|
29
|
+
filename: 'preview.pdf',
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export default {
|
|
33
|
+
title: 'Public components/View/FileView',
|
|
34
|
+
component: FileViewComponent,
|
|
35
|
+
decorators: [
|
|
36
|
+
(Story) => (
|
|
37
|
+
<div className="ui segment form attached" style={{ width: '900px' }}>
|
|
38
|
+
<Story />
|
|
39
|
+
</div>
|
|
40
|
+
),
|
|
41
|
+
],
|
|
42
|
+
argTypes: {
|
|
43
|
+
title: {
|
|
44
|
+
description: 'Title of the component',
|
|
45
|
+
},
|
|
46
|
+
filename: {
|
|
47
|
+
description: 'Name of the file',
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
};
|