@eeacms/volto-eea-website-theme 3.19.1 → 4.0.0
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/.eslintrc.js +7 -6
- package/CHANGELOG.md +19 -26
- package/DEVELOP.md +19 -17
- package/README.md +19 -7
- package/docker-compose.yml +1 -1
- package/jest-addon.config.js +8 -4
- package/package.json +1 -1
- package/src/actions/navigation.js +1 -1
- package/src/components/manage/Blocks/ContextNavigation/ContextNavigationEdit.jsx +4 -2
- package/src/components/manage/Blocks/ContextNavigation/ContextNavigationEdit.test.jsx +25 -19
- package/src/components/manage/Blocks/ContextNavigation/ContextNavigationView.jsx +2 -1
- package/src/components/manage/Blocks/ContextNavigation/ContextNavigationView.test.jsx +6 -4
- package/src/components/manage/Blocks/ContextNavigation/variations/Accordion.jsx +2 -2
- package/src/components/manage/Blocks/ContextNavigation/variations/ReportNavigation.jsx +4 -2
- package/src/components/manage/Blocks/ContextNavigation/variations/ReportNavigation.test.jsx +1 -1
- package/src/components/manage/Blocks/GroupBlockTemplate/FlexGroup/FlexGroup.jsx +12 -44
- package/src/components/manage/Blocks/GroupBlockTemplate/FlexGroup/RenderBlocks.jsx +5 -4
- package/src/components/manage/Blocks/GroupBlockTemplate/FlexGroup/editor-flex.less +45 -4
- package/src/components/manage/Blocks/LayoutSettings/LayoutSettingsEdit.jsx +2 -1
- package/src/components/manage/Blocks/LayoutSettings/LayoutSettingsEdit.test.jsx +12 -4
- package/src/components/manage/Blocks/LayoutSettings/LayoutSettingsView.jsx +1 -1
- package/src/components/manage/Blocks/Title/Edit.jsx +3 -3
- package/src/components/manage/Blocks/Title/View.jsx +2 -1
- package/src/components/manage/Blocks/Title/variations/WebReport.jsx +2 -2
- package/src/components/manage/Blocks/Title/variations/WebReport.test.jsx +6 -4
- package/src/components/manage/Blocks/Title/variations/WebReportPage.jsx +2 -2
- package/src/components/manage/Blocks/Title/variations/WebReportPage.test.jsx +6 -4
- package/src/components/theme/Banner/View.jsx +1 -1
- package/src/components/theme/BaseTag.jsx +2 -1
- package/src/components/theme/BaseTag.test.jsx +7 -2
- package/src/components/theme/DraftBackground/DraftBackground.jsx +2 -1
- package/src/components/theme/Homepage/HomePageInverseView.jsx +3 -3
- package/src/components/theme/Homepage/HomePageInverseView.test.jsx +1 -1
- package/src/components/theme/Homepage/HomePageView.jsx +3 -3
- package/src/components/theme/Homepage/HomePageView.test.jsx +1 -1
- package/src/components/theme/Logo.jsx +3 -3
- package/src/components/theme/NotFound/GoneView.jsx +3 -2
- package/src/components/theme/NotFound/GoneView.test.jsx +5 -4
- package/src/components/theme/NotFound/NotFound.jsx +1 -1
- package/src/components/theme/NotFound/NotFound.test.jsx +3 -2
- package/src/components/theme/PrintLoader/PrintLoader.test.jsx +1 -1
- package/src/components/theme/SubsiteClass.jsx +6 -4
- package/src/components/theme/SubsiteClass.test.jsx +3 -2
- package/src/components/theme/WebReport/WebReportSectionView.jsx +2 -2
- package/src/components/theme/WebReport/WebReportSectionView.test.jsx +10 -5
- package/src/components/theme/Widgets/ADUserGroupSelectWidget.jsx +2 -2
- package/src/components/theme/Widgets/ContributorsViewWidget.jsx +1 -1
- package/src/components/theme/Widgets/CreatableSelectWidget.jsx +7 -4
- package/src/components/theme/Widgets/CreatorsViewWidget.jsx +1 -1
- package/src/components/theme/Widgets/DateWidget.jsx +1 -1
- package/src/components/theme/Widgets/DateWidget.test.js +1 -1
- package/src/components/theme/Widgets/DatetimeWidget.jsx +1 -1
- package/src/components/theme/Widgets/DatetimeWidget.test.js +1 -1
- package/src/components/theme/Widgets/ImageViewWidget.jsx +1 -0
- package/src/components/theme/Widgets/NavigationBehaviorWidget.jsx +7 -3
- package/src/components/theme/Widgets/NavigationBehaviorWidget.test.jsx +51 -46
- package/src/components/theme/Widgets/UserSelectWidget.jsx +13 -10
- package/src/customizations/@plone/volto-slate/blocks/Table/TableBlockView.jsx +3 -3
- package/src/customizations/@plone/volto-slate/blocks/Text/TextBlockView.jsx +2 -2
- package/src/customizations/@plone/volto-slate/editor/SlateEditor.jsx +23 -10
- package/src/customizations/@plone/volto-slate/editor/render.jsx +7 -3
- package/src/customizations/@plone/volto-slate/utils/blocks.js +11 -8
- package/src/customizations/volto/components/manage/Blocks/Grid/View.jsx +2 -2
- package/src/customizations/volto/components/manage/Blocks/Image/Edit.jsx +30 -27
- package/src/customizations/volto/components/manage/Blocks/Image/Edit.test.jsx +244 -246
- package/src/customizations/volto/components/manage/Blocks/Image/View.jsx +23 -25
- package/src/customizations/volto/components/manage/Blocks/LeadImage/Edit.jsx +6 -4
- package/src/customizations/volto/components/manage/Blocks/LeadImage/LeadImageSidebar.jsx +4 -2
- package/src/customizations/volto/components/manage/Blocks/LeadImage/View.jsx +2 -2
- package/src/customizations/volto/components/manage/Controlpanels/Groups/RenderGroups.jsx +1 -1
- package/src/customizations/volto/components/manage/Controlpanels/Groups/RenderGroups.test.jsx +108 -42
- package/src/customizations/volto/components/manage/Diff/DiffField.jsx +4 -3
- package/src/customizations/volto/components/manage/Display/Display.jsx +8 -7
- package/src/customizations/volto/components/manage/Sidebar/ObjectBrowserBody.jsx +42 -21
- package/src/customizations/volto/components/manage/Sidebar/ObjectBrowserNav.jsx +2 -1
- package/src/customizations/volto/components/manage/Sidebar/SidebarPopup.jsx +46 -24
- package/src/customizations/volto/components/manage/Sidebar/objectBrowserSelection.js +58 -0
- package/src/customizations/volto/components/manage/Toolbar/More.jsx +8 -10
- package/src/customizations/volto/components/manage/Widgets/NumberWidget.jsx +1 -1
- package/src/customizations/volto/components/manage/Widgets/NumberWidget.test.jsx +6 -1
- package/src/customizations/volto/components/manage/Widgets/ObjectBrowserWidget.jsx +66 -12
- package/src/customizations/volto/components/manage/Workflow/Workflow.jsx +10 -9
- package/src/customizations/volto/components/theme/Breadcrumbs/Breadcrumbs.jsx +3 -2
- package/src/customizations/volto/components/theme/Comments/Comments.jsx +9 -8
- package/src/customizations/volto/components/theme/Comments/Comments.test.jsx +29 -7
- package/src/customizations/volto/components/theme/ContactForm/ContactForm.jsx +1 -1
- package/src/customizations/volto/components/theme/ContactForm/ContactForm.test.js +5 -0
- package/src/customizations/volto/components/theme/ContentMetadataTags/ContentMetadataTags.jsx +5 -7
- package/src/customizations/volto/components/theme/EventDetails/EventDetails.jsx +2 -2
- package/src/customizations/volto/components/theme/Footer/Footer.jsx +1 -1
- package/src/customizations/volto/components/theme/Header/Header.jsx +10 -8
- package/src/customizations/volto/components/theme/Header/Header.test.jsx +1 -1
- package/src/customizations/volto/components/theme/Header/LanguageSwitcher.jsx +3 -3
- package/src/customizations/volto/components/theme/Image/Image.jsx +4 -3
- package/src/customizations/volto/components/theme/Unauthorized/Unauthorized.jsx +1 -1
- package/src/customizations/volto/components/theme/View/DefaultView.jsx +4 -3
- package/src/customizations/volto/components/theme/View/EventView.jsx +3 -2
- package/src/customizations/volto/helpers/Html/Html.jsx +16 -6
- package/src/customizations/volto/helpers/Html/Readme.md +7 -1
- package/src/customizations/volto/reducers/breadcrumbs/breadcrumbs.js +3 -6
- package/src/customizations/volto/server.jsx +10 -10
- package/src/helpers/schema-utils.js +1 -1
- package/src/helpers/schema-utils.test.js +1 -1
- package/src/hocs/withErrorBoundary.jsx +1 -1
- package/src/hocs/withErrorBoundary.test.jsx +4 -11
- package/src/hocs/withRootNavigation.jsx +3 -2
- package/src/hocs/withRootNavigation.test.jsx +18 -14
- package/src/index.js +3 -3
- package/src/slate.js +1 -1
- package/src/customizations/volto/components/manage/Blocks/LeadImage/AlignChooser.jsx +0 -76
- package/src/customizations/volto/components/manage/Blocks/LeadImage/AlignChooser.test.js +0 -50
- package/src/customizations/volto/components/manage/Sidebar/SidebarPopup copy.jsx +0 -82
|
@@ -2,8 +2,9 @@ import React, { useEffect } from 'react';
|
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
3
|
import { Segment } from 'semantic-ui-react';
|
|
4
4
|
import { FormattedMessage, injectIntl } from 'react-intl';
|
|
5
|
-
import
|
|
6
|
-
import
|
|
5
|
+
import Icon from '@plone/volto/components/theme/Icon/Icon';
|
|
6
|
+
import BlockDataForm from '@plone/volto/components/manage/Form/BlockDataForm';
|
|
7
|
+
import { flattenToAppURL } from '@plone/volto/helpers/Url/Url';
|
|
7
8
|
import { LeadImageSchema } from './schema';
|
|
8
9
|
import imageSVG from '@plone/volto/icons/image.svg';
|
|
9
10
|
|
|
@@ -48,6 +49,7 @@ const LeadImageSidebar = ({ properties, data, block, onChangeBlock, intl }) => {
|
|
|
48
49
|
<>
|
|
49
50
|
<Segment className="sidebar-metadata-container" secondary>
|
|
50
51
|
{properties.image.filename}
|
|
52
|
+
{/* eslint-disable-next-line no-restricted-syntax */}
|
|
51
53
|
<img
|
|
52
54
|
src={
|
|
53
55
|
properties.image.data
|
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
|
|
6
6
|
import React from 'react';
|
|
7
7
|
import PropTypes from 'prop-types';
|
|
8
|
-
import
|
|
8
|
+
import UniversalLink from '@plone/volto/components/manage/UniversalLink/UniversalLink';
|
|
9
9
|
import cx from 'classnames';
|
|
10
10
|
import config from '@plone/volto/registry';
|
|
11
|
-
import
|
|
11
|
+
import Copyright from '@eeacms/volto-eea-design-system/ui/Copyright/Copyright';
|
|
12
12
|
import { Icon } from 'semantic-ui-react';
|
|
13
13
|
|
|
14
14
|
/**
|
|
@@ -8,7 +8,7 @@ import { FormattedMessage, injectIntl } from 'react-intl';
|
|
|
8
8
|
import { Dropdown, Table, Checkbox } from 'semantic-ui-react';
|
|
9
9
|
import trashSVG from '@plone/volto/icons/delete.svg';
|
|
10
10
|
import ploneSVG from '@plone/volto/icons/plone.svg';
|
|
11
|
-
import
|
|
11
|
+
import Icon from '@plone/volto/components/theme/Icon/Icon';
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* UsersControlpanelGroups class.
|
package/src/customizations/volto/components/manage/Controlpanels/Groups/RenderGroups.test.jsx
CHANGED
|
@@ -1,58 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RenderGroups — unit tests
|
|
3
|
+
*
|
|
4
|
+
* Volto 17 / 18 dual-support notes
|
|
5
|
+
* ---------------------------------
|
|
6
|
+
* The EEA change is a single-line tweak on line 80:
|
|
7
|
+
* `{this.props.group.title || this.props.group.groupname}`
|
|
8
|
+
* instead of showing only `groupname`.
|
|
9
|
+
*
|
|
10
|
+
* V17 and V18 upstream are byte-for-byte identical, so this shadow is
|
|
11
|
+
* compatible with both versions without any rebase.
|
|
12
|
+
*
|
|
13
|
+
* The `@plone/volto/components` barrel is mocked to prevent the chain:
|
|
14
|
+
* TranslationObject.jsx → store.js → @root/reducers
|
|
15
|
+
* that cannot be resolved in this Jest environment. The previous snapshot
|
|
16
|
+
* test used `react-test-renderer` which produced a snapshot that contained
|
|
17
|
+
* the full icon SVG tree; the new tests use @testing-library/react and
|
|
18
|
+
* assert on the actual EEA-specific behavior instead.
|
|
19
|
+
*/
|
|
20
|
+
|
|
1
21
|
import React from 'react';
|
|
2
|
-
import
|
|
22
|
+
import { render, screen } from '@testing-library/react';
|
|
23
|
+
import '@testing-library/jest-dom';
|
|
3
24
|
import configureStore from 'redux-mock-store';
|
|
4
25
|
import { Provider } from 'react-intl-redux';
|
|
5
26
|
|
|
6
27
|
import RenderGroups from './RenderGroups';
|
|
7
28
|
|
|
29
|
+
jest.mock('@plone/volto/components', () => ({
|
|
30
|
+
// RenderGroups.jsx uses only Icon from the barrel
|
|
31
|
+
Icon: () => null,
|
|
32
|
+
}));
|
|
33
|
+
|
|
8
34
|
const mockStore = configureStore();
|
|
9
35
|
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
groupname: 'Administrators',
|
|
15
|
-
id: 'Administrators',
|
|
16
|
-
title: 'Administrators',
|
|
17
|
-
roles: ['Manager'],
|
|
18
|
-
};
|
|
36
|
+
const makeStore = () =>
|
|
37
|
+
mockStore({
|
|
38
|
+
intl: { locale: 'en', messages: {} },
|
|
39
|
+
});
|
|
19
40
|
|
|
20
41
|
const testRoles = [
|
|
21
|
-
{
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
id: 'Member',
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
'@id': 'http://localhost:8080/Plone/@roles/Reader',
|
|
28
|
-
'@type': 'role',
|
|
29
|
-
id: 'Reader',
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
'@id': 'http://localhost:8080/Plone/@roles/Manager',
|
|
33
|
-
'@type': 'role',
|
|
34
|
-
id: 'Manager',
|
|
35
|
-
},
|
|
42
|
+
{ '@id': 'http://localhost:8080/Plone/@roles/Member', id: 'Member' },
|
|
43
|
+
{ '@id': 'http://localhost:8080/Plone/@roles/Reader', id: 'Reader' },
|
|
44
|
+
{ '@id': 'http://localhost:8080/Plone/@roles/Manager', id: 'Manager' },
|
|
36
45
|
];
|
|
37
46
|
|
|
38
|
-
describe('
|
|
39
|
-
it('
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
47
|
+
describe('RenderGroups — EEA: title || groupname display', () => {
|
|
48
|
+
it('shows the group title when available (EEA change)', () => {
|
|
49
|
+
const group = {
|
|
50
|
+
groupname: 'editors',
|
|
51
|
+
title: 'Site Editors',
|
|
52
|
+
roles: [],
|
|
53
|
+
};
|
|
54
|
+
render(
|
|
55
|
+
<Provider store={makeStore()}>
|
|
56
|
+
<RenderGroups group={group} roles={testRoles} onDelete={() => {}} />
|
|
57
|
+
</Provider>,
|
|
58
|
+
);
|
|
59
|
+
// EEA customization: title takes precedence over groupname
|
|
60
|
+
expect(screen.getByText('Site Editors')).toBeInTheDocument();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('falls back to groupname when title is absent', () => {
|
|
64
|
+
const group = {
|
|
65
|
+
groupname: 'Administrators',
|
|
66
|
+
title: '',
|
|
67
|
+
roles: ['Manager'],
|
|
68
|
+
};
|
|
69
|
+
render(
|
|
70
|
+
<Provider store={makeStore()}>
|
|
71
|
+
<RenderGroups group={group} roles={testRoles} onDelete={() => {}} />
|
|
72
|
+
</Provider>,
|
|
73
|
+
);
|
|
74
|
+
expect(screen.getByText('Administrators')).toBeInTheDocument();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('falls back to groupname when title is undefined', () => {
|
|
78
|
+
const group = {
|
|
79
|
+
groupname: 'Reviewers',
|
|
80
|
+
// no title key at all
|
|
81
|
+
roles: [],
|
|
82
|
+
};
|
|
83
|
+
render(
|
|
84
|
+
<Provider store={makeStore()}>
|
|
85
|
+
<RenderGroups group={group} roles={testRoles} onDelete={() => {}} />
|
|
86
|
+
</Provider>,
|
|
87
|
+
);
|
|
88
|
+
expect(screen.getByText('Reviewers')).toBeInTheDocument();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('renders a checkbox for each role', () => {
|
|
92
|
+
const group = {
|
|
93
|
+
groupname: 'Administrators',
|
|
94
|
+
title: 'Administrators',
|
|
95
|
+
roles: ['Manager'],
|
|
96
|
+
};
|
|
97
|
+
const { container } = render(
|
|
98
|
+
<Provider store={makeStore()}>
|
|
99
|
+
<RenderGroups group={group} roles={testRoles} onDelete={() => {}} />
|
|
100
|
+
</Provider>,
|
|
101
|
+
);
|
|
102
|
+
// One checkbox per role
|
|
103
|
+
const checkboxes = container.querySelectorAll('input[type="checkbox"]');
|
|
104
|
+
expect(checkboxes).toHaveLength(testRoles.length);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('checks the checkbox for roles the group already has', () => {
|
|
108
|
+
const group = {
|
|
109
|
+
groupname: 'managers',
|
|
110
|
+
title: 'Managers',
|
|
111
|
+
roles: ['Manager'],
|
|
112
|
+
};
|
|
113
|
+
const { container } = render(
|
|
114
|
+
<Provider store={makeStore()}>
|
|
115
|
+
<RenderGroups group={group} roles={testRoles} onDelete={() => {}} />
|
|
53
116
|
</Provider>,
|
|
54
117
|
);
|
|
55
|
-
const
|
|
56
|
-
|
|
118
|
+
const checkboxes = container.querySelectorAll('input[type="checkbox"]');
|
|
119
|
+
// Member → unchecked, Reader → unchecked, Manager → checked
|
|
120
|
+
expect(checkboxes[0]).not.toBeChecked();
|
|
121
|
+
expect(checkboxes[1]).not.toBeChecked();
|
|
122
|
+
expect(checkboxes[2]).toBeChecked();
|
|
57
123
|
});
|
|
58
124
|
});
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import React from 'react';
|
|
7
|
-
import
|
|
7
|
+
import join from 'lodash/join';
|
|
8
|
+
import map from 'lodash/map';
|
|
8
9
|
import PropTypes from 'prop-types';
|
|
9
10
|
import { Grid } from 'semantic-ui-react';
|
|
10
11
|
import ReactDOMServer from 'react-dom/server';
|
|
@@ -13,9 +14,9 @@ import { createBrowserHistory } from 'history';
|
|
|
13
14
|
import { ConnectedRouter } from 'connected-react-router';
|
|
14
15
|
import { useSelector, useStore } from 'react-redux';
|
|
15
16
|
import config from '@plone/volto/registry';
|
|
16
|
-
import
|
|
17
|
+
import Api from '@plone/volto/helpers/Api/Api';
|
|
17
18
|
import configureStore from '@plone/volto/store';
|
|
18
|
-
import
|
|
19
|
+
import RenderBlocks from '@plone/volto/components/theme/View/RenderBlocks';
|
|
19
20
|
import { serializeNodes } from '@plone/volto-slate/editor/render';
|
|
20
21
|
import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable';
|
|
21
22
|
|
|
@@ -5,14 +5,15 @@ import { compose } from 'redux';
|
|
|
5
5
|
import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable';
|
|
6
6
|
|
|
7
7
|
import jwtDecode from 'jwt-decode';
|
|
8
|
+
import { getSchema } from '@plone/volto/actions/schema/schema';
|
|
9
|
+
import { getUser } from '@plone/volto/actions/users/users';
|
|
8
10
|
import {
|
|
9
|
-
getSchema,
|
|
10
|
-
getUser,
|
|
11
11
|
updateContent,
|
|
12
12
|
getContent,
|
|
13
|
-
} from '@plone/volto/actions';
|
|
14
|
-
import { getLayoutFieldname } from '@plone/volto/helpers';
|
|
15
|
-
import
|
|
13
|
+
} from '@plone/volto/actions/content/content';
|
|
14
|
+
import { getLayoutFieldname } from '@plone/volto/helpers/Content/Content';
|
|
15
|
+
import Icon from '@plone/volto/components/theme/Icon/Icon';
|
|
16
|
+
import FormFieldWrapper from '@plone/volto/components/manage/Widgets/FormFieldWrapper';
|
|
16
17
|
import { defineMessages, injectIntl } from 'react-intl';
|
|
17
18
|
import config from '@plone/volto/registry';
|
|
18
19
|
|
|
@@ -101,8 +102,8 @@ const customSelectStyles = {
|
|
|
101
102
|
color: state.isSelected
|
|
102
103
|
? '#007bc1'
|
|
103
104
|
: state.isFocused
|
|
104
|
-
|
|
105
|
-
|
|
105
|
+
? '#4a4a4a'
|
|
106
|
+
: 'inherit',
|
|
106
107
|
':active': {
|
|
107
108
|
backgroundColor: null,
|
|
108
109
|
},
|
|
@@ -7,7 +7,7 @@ import { connect } from 'react-redux';
|
|
|
7
7
|
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
|
|
8
8
|
import { Input, Segment, Breadcrumb } from 'semantic-ui-react';
|
|
9
9
|
|
|
10
|
-
import
|
|
10
|
+
import join from 'lodash/join';
|
|
11
11
|
|
|
12
12
|
// These absolute imports (without using the corresponding centralized index.js) are required
|
|
13
13
|
// to cut circular import problems, this file should never use them. This is because of
|
|
@@ -25,6 +25,11 @@ import linkSVG from '@plone/volto/icons/link.svg';
|
|
|
25
25
|
import homeSVG from '@plone/volto/icons/home.svg';
|
|
26
26
|
|
|
27
27
|
import ObjectBrowserNav from '@plone/volto/components/manage/Sidebar/ObjectBrowserNav';
|
|
28
|
+
import {
|
|
29
|
+
isItemAlreadySelected,
|
|
30
|
+
isSelectableObjectBrowserItem,
|
|
31
|
+
shouldCloseAfterObjectBrowserSelection,
|
|
32
|
+
} from './objectBrowserSelection';
|
|
28
33
|
|
|
29
34
|
const messages = defineMessages({
|
|
30
35
|
SearchInputPlaceholder: {
|
|
@@ -74,6 +79,7 @@ class ObjectBrowserBody extends Component {
|
|
|
74
79
|
maximumSelectionSize: PropTypes.number,
|
|
75
80
|
contextURL: PropTypes.string,
|
|
76
81
|
searchableTypes: PropTypes.arrayOf(PropTypes.string),
|
|
82
|
+
onlyFolderishSelectable: PropTypes.bool,
|
|
77
83
|
};
|
|
78
84
|
|
|
79
85
|
/**
|
|
@@ -89,6 +95,7 @@ class ObjectBrowserBody extends Component {
|
|
|
89
95
|
selectableTypes: [],
|
|
90
96
|
searchableTypes: null,
|
|
91
97
|
maximumSelectionSize: null,
|
|
98
|
+
onlyFolderishSelectable: false,
|
|
92
99
|
};
|
|
93
100
|
|
|
94
101
|
/**
|
|
@@ -106,27 +113,27 @@ class ObjectBrowserBody extends Component {
|
|
|
106
113
|
this.props.mode === 'multiple'
|
|
107
114
|
? '/'
|
|
108
115
|
: this.props.mode === 'image' && this.props.data?.url
|
|
109
|
-
|
|
110
|
-
|
|
116
|
+
? getParentURL(this.props.data.url)
|
|
117
|
+
: '/',
|
|
111
118
|
currentLinkFolder:
|
|
112
119
|
this.props.mode === 'multiple'
|
|
113
120
|
? '/'
|
|
114
121
|
: this.props.mode === 'link' && this.props.data?.href
|
|
115
|
-
|
|
116
|
-
|
|
122
|
+
? getParentURL(this.props.data.href)
|
|
123
|
+
: '/',
|
|
117
124
|
parentFolder: '',
|
|
118
125
|
selectedImage:
|
|
119
126
|
this.props.mode === 'multiple'
|
|
120
127
|
? ''
|
|
121
128
|
: this.props.mode === 'image' && this.props.data?.url
|
|
122
|
-
|
|
123
|
-
|
|
129
|
+
? flattenToAppURL(this.props.data.url)
|
|
130
|
+
: '',
|
|
124
131
|
selectedHref:
|
|
125
132
|
this.props.mode === 'multiple'
|
|
126
133
|
? ''
|
|
127
134
|
: this.props.mode === 'link' && this.props.data?.href
|
|
128
|
-
|
|
129
|
-
|
|
135
|
+
? flattenToAppURL(this.props.data.href)
|
|
136
|
+
: '',
|
|
130
137
|
showSearchInput: false,
|
|
131
138
|
// In image mode, the searchable types default to the image types which
|
|
132
139
|
// can be overridden with the property if specified.
|
|
@@ -152,8 +159,8 @@ class ObjectBrowserBody extends Component {
|
|
|
152
159
|
mode === 'multiple'
|
|
153
160
|
? ''
|
|
154
161
|
: mode === 'image'
|
|
155
|
-
|
|
156
|
-
|
|
162
|
+
? this.state.selectedImage
|
|
163
|
+
: this.state.selectedHref;
|
|
157
164
|
if (currentSelected && isInternalURL(currentSelected)) {
|
|
158
165
|
this.props.searchContent(
|
|
159
166
|
getParentURL(currentSelected),
|
|
@@ -299,9 +306,15 @@ class ObjectBrowserBody extends Component {
|
|
|
299
306
|
};
|
|
300
307
|
|
|
301
308
|
isSelectable = (item) => {
|
|
302
|
-
return
|
|
303
|
-
|
|
304
|
-
:
|
|
309
|
+
return isSelectableObjectBrowserItem({
|
|
310
|
+
item,
|
|
311
|
+
selectableTypes: this.props.selectableTypes,
|
|
312
|
+
onlyFolderishSelectable: this.props.onlyFolderishSelectable,
|
|
313
|
+
maximumSelectionSize: this.props.maximumSelectionSize,
|
|
314
|
+
data: this.props.data,
|
|
315
|
+
mode: this.props.mode,
|
|
316
|
+
normalize: flattenToAppURL,
|
|
317
|
+
});
|
|
305
318
|
};
|
|
306
319
|
|
|
307
320
|
handleClickOnItem = (item) => {
|
|
@@ -318,15 +331,23 @@ class ObjectBrowserBody extends Component {
|
|
|
318
331
|
!this.props.maximumSelectionSize ||
|
|
319
332
|
this.props.mode === 'multiple' ||
|
|
320
333
|
!this.props.data ||
|
|
321
|
-
this.props.data.length
|
|
334
|
+
this.props.data.length <= this.props.maximumSelectionSize
|
|
322
335
|
) {
|
|
323
|
-
|
|
324
|
-
|
|
336
|
+
const isDeselecting =
|
|
337
|
+
this.props.mode === 'multiple' &&
|
|
338
|
+
isItemAlreadySelected({
|
|
339
|
+
data: this.props.data,
|
|
340
|
+
item,
|
|
341
|
+
normalize: flattenToAppURL,
|
|
342
|
+
});
|
|
325
343
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
344
|
+
this.onSelectItem(item);
|
|
345
|
+
const stopSelecting = shouldCloseAfterObjectBrowserSelection({
|
|
346
|
+
mode: this.props.mode,
|
|
347
|
+
maximumSelectionSize: this.props.maximumSelectionSize,
|
|
348
|
+
currentLength: this.props.data ? this.props.data.length : 0,
|
|
349
|
+
isDeselecting,
|
|
350
|
+
});
|
|
330
351
|
|
|
331
352
|
if (stopSelecting) {
|
|
332
353
|
this.props.closeObjectBrowser();
|
|
@@ -3,7 +3,8 @@ import { Button, Segment, Popup } from 'semantic-ui-react';
|
|
|
3
3
|
import { useIntl, defineMessages } from 'react-intl';
|
|
4
4
|
import cx from 'classnames';
|
|
5
5
|
import Icon from '@plone/volto/components/theme/Icon/Icon';
|
|
6
|
-
import { flattenToAppURL
|
|
6
|
+
import { flattenToAppURL } from '@plone/volto/helpers/Url/Url';
|
|
7
|
+
import { getContentIcon } from '@plone/volto/helpers/Content/Content';
|
|
7
8
|
import { Image } from 'semantic-ui-react';
|
|
8
9
|
import config from '@plone/volto/registry';
|
|
9
10
|
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
//
|
|
1
|
+
// Backport of https://github.com/plone/volto/pull/5520 for Volto 17.
|
|
2
|
+
// On Volto 18 this shadow is identical to upstream — keep while V17 is supported.
|
|
2
3
|
import React from 'react';
|
|
3
|
-
import {
|
|
4
|
+
import { createPortal } from 'react-dom';
|
|
4
5
|
import { CSSTransition } from 'react-transition-group';
|
|
5
6
|
import PropTypes from 'prop-types';
|
|
6
|
-
import
|
|
7
|
+
import doesNodeContainClick from 'semantic-ui-react/dist/commonjs/lib/doesNodeContainClick';
|
|
7
8
|
|
|
8
9
|
const DEFAULT_TIMEOUT = 500;
|
|
9
10
|
|
|
@@ -17,10 +18,24 @@ const SidebarPopup = (props) => {
|
|
|
17
18
|
onClose();
|
|
18
19
|
};
|
|
19
20
|
|
|
21
|
+
const handleEscapeKey = (e) => {
|
|
22
|
+
if (open && e.key === 'Escape') {
|
|
23
|
+
onClose();
|
|
24
|
+
e.stopPropagation();
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const [isClient, setIsClient] = React.useState(false);
|
|
29
|
+
React.useEffect(() => {
|
|
30
|
+
setIsClient(true);
|
|
31
|
+
}, []);
|
|
32
|
+
|
|
20
33
|
React.useEffect(() => {
|
|
21
34
|
document.addEventListener('mousedown', handleClickOutside, false);
|
|
35
|
+
document.addEventListener('keyup', handleEscapeKey, false);
|
|
22
36
|
return () => {
|
|
23
37
|
document.removeEventListener('mousedown', handleClickOutside, false);
|
|
38
|
+
document.removeEventListener('keyup', handleEscapeKey, false);
|
|
24
39
|
};
|
|
25
40
|
});
|
|
26
41
|
|
|
@@ -33,9 +48,13 @@ const SidebarPopup = (props) => {
|
|
|
33
48
|
classNames="overlay-container"
|
|
34
49
|
unmountOnExit
|
|
35
50
|
>
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
51
|
+
<>
|
|
52
|
+
{document?.body &&
|
|
53
|
+
createPortal(
|
|
54
|
+
<div className="overlay-container"></div>,
|
|
55
|
+
document?.body,
|
|
56
|
+
)}
|
|
57
|
+
</>
|
|
39
58
|
</CSSTransition>
|
|
40
59
|
)}
|
|
41
60
|
<CSSTransition
|
|
@@ -44,24 +63,27 @@ const SidebarPopup = (props) => {
|
|
|
44
63
|
classNames="sidebar-container"
|
|
45
64
|
unmountOnExit
|
|
46
65
|
>
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
66
|
+
<>
|
|
67
|
+
{isClient &&
|
|
68
|
+
createPortal(
|
|
69
|
+
<aside
|
|
70
|
+
role="presentation"
|
|
71
|
+
onClick={(e) => {
|
|
72
|
+
e.stopPropagation();
|
|
73
|
+
}}
|
|
74
|
+
onKeyDown={(e) => {
|
|
75
|
+
e.stopPropagation();
|
|
76
|
+
}}
|
|
77
|
+
ref={asideElement}
|
|
78
|
+
key="sidebarpopup"
|
|
79
|
+
className="sidebar-container"
|
|
80
|
+
style={{ overflowY: 'auto' }}
|
|
81
|
+
>
|
|
82
|
+
{children}
|
|
83
|
+
</aside>,
|
|
84
|
+
document.body,
|
|
85
|
+
)}
|
|
86
|
+
</>
|
|
65
87
|
</CSSTransition>
|
|
66
88
|
</>
|
|
67
89
|
);
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export const isItemAlreadySelected = ({
|
|
2
|
+
data,
|
|
3
|
+
item,
|
|
4
|
+
normalize = (value) => value,
|
|
5
|
+
}) => {
|
|
6
|
+
const selectedItems = Array.isArray(data) ? data : [];
|
|
7
|
+
|
|
8
|
+
return selectedItems.some(
|
|
9
|
+
(selectedItem) =>
|
|
10
|
+
normalize(selectedItem?.['@id']) === normalize(item?.['@id']),
|
|
11
|
+
);
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const isSelectableObjectBrowserItem = ({
|
|
15
|
+
item,
|
|
16
|
+
selectableTypes = [],
|
|
17
|
+
onlyFolderishSelectable = false,
|
|
18
|
+
maximumSelectionSize,
|
|
19
|
+
data,
|
|
20
|
+
mode,
|
|
21
|
+
normalize = (value) => value,
|
|
22
|
+
}) => {
|
|
23
|
+
if (onlyFolderishSelectable && !item?.is_folderish) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (
|
|
28
|
+
maximumSelectionSize &&
|
|
29
|
+
Array.isArray(data) &&
|
|
30
|
+
mode === 'multiple' &&
|
|
31
|
+
maximumSelectionSize <= data.length
|
|
32
|
+
) {
|
|
33
|
+
return isItemAlreadySelected({ data, item, normalize });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return selectableTypes.length > 0
|
|
37
|
+
? selectableTypes.indexOf(item?.['@type']) >= 0
|
|
38
|
+
: true;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const shouldCloseAfterObjectBrowserSelection = ({
|
|
42
|
+
mode,
|
|
43
|
+
maximumSelectionSize,
|
|
44
|
+
currentLength = 0,
|
|
45
|
+
isDeselecting = false,
|
|
46
|
+
}) => {
|
|
47
|
+
let stopSelecting = mode !== 'multiple';
|
|
48
|
+
|
|
49
|
+
if (isDeselecting && !stopSelecting) {
|
|
50
|
+
stopSelecting =
|
|
51
|
+
maximumSelectionSize > 0 && currentLength - 1 >= maximumSelectionSize;
|
|
52
|
+
} else {
|
|
53
|
+
stopSelecting =
|
|
54
|
+
maximumSelectionSize > 0 && currentLength + 1 >= maximumSelectionSize;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return stopSelecting;
|
|
58
|
+
};
|
|
@@ -9,22 +9,20 @@ import PropTypes from 'prop-types';
|
|
|
9
9
|
import { connect } from 'react-redux';
|
|
10
10
|
import { compose } from 'redux';
|
|
11
11
|
import { Link, withRouter } from 'react-router-dom';
|
|
12
|
-
import
|
|
12
|
+
import find from 'lodash/find';
|
|
13
13
|
import { toast } from 'react-toastify';
|
|
14
|
-
import
|
|
14
|
+
import Toast from '@plone/volto/components/manage/Toast/Toast';
|
|
15
15
|
import { Pluggable, Plug } from '@plone/volto/components/manage/Pluggable';
|
|
16
|
-
import
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
Workflow,
|
|
21
|
-
} from '@plone/volto/components';
|
|
16
|
+
import FormattedDate from '@plone/volto/components/theme/FormattedDate/FormattedDate';
|
|
17
|
+
import Icon from '@plone/volto/components/theme/Icon/Icon';
|
|
18
|
+
import Display from '@plone/volto/components/manage/Display/Display';
|
|
19
|
+
import Workflow from '@plone/volto/components/manage/Workflow/Workflow';
|
|
22
20
|
import {
|
|
23
21
|
applyWorkingCopy,
|
|
24
22
|
createWorkingCopy,
|
|
25
23
|
removeWorkingCopy,
|
|
26
|
-
} from '@plone/volto/actions';
|
|
27
|
-
import { flattenToAppURL, getBaseUrl } from '@plone/volto/helpers';
|
|
24
|
+
} from '@plone/volto/actions//workingcopy/workingcopy';
|
|
25
|
+
import { flattenToAppURL, getBaseUrl } from '@plone/volto/helpers/Url/Url';
|
|
28
26
|
import config from '@plone/volto/registry';
|
|
29
27
|
|
|
30
28
|
import rightArrowSVG from '@plone/volto/icons/right-key.svg';
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import React from 'react';
|
|
7
7
|
import PropTypes from 'prop-types';
|
|
8
8
|
import { Input } from 'semantic-ui-react';
|
|
9
|
-
import
|
|
9
|
+
import FormFieldWrapper from '@plone/volto/components/manage/Widgets/FormFieldWrapper';
|
|
10
10
|
import { injectIntl } from 'react-intl';
|
|
11
11
|
|
|
12
12
|
/**
|
|
@@ -2,10 +2,15 @@ import React from 'react';
|
|
|
2
2
|
import { render, screen, fireEvent } from '@testing-library/react';
|
|
3
3
|
import { Provider } from 'react-intl-redux';
|
|
4
4
|
import configureStore from 'redux-mock-store';
|
|
5
|
-
import '@testing-library/jest-dom
|
|
5
|
+
import '@testing-library/jest-dom';
|
|
6
6
|
|
|
7
7
|
import NumberWidget from './NumberWidget';
|
|
8
8
|
|
|
9
|
+
jest.mock('@plone/volto/components/manage/Widgets/FormFieldWrapper', () => ({
|
|
10
|
+
__esModule: true,
|
|
11
|
+
default: ({ children }) => <>{children}</>,
|
|
12
|
+
}));
|
|
13
|
+
|
|
9
14
|
const mockStore = configureStore();
|
|
10
15
|
|
|
11
16
|
const store = mockStore({
|