@eeacms/volto-cca-policy 0.3.73 → 0.3.74
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 +8 -4
- package/CHANGELOG.md +51 -2
- package/package.json +11 -10
- package/src/components/manage/Blocks/ASTNavigation/schema.js +3 -1
- package/src/components/manage/Blocks/C3SIndicatorsGlossaryBlock/schema.js +3 -1
- package/src/components/manage/Blocks/C3SIndicatorsListingBlock/schema.js +3 -1
- package/src/components/manage/Blocks/C3SIndicatorsOverviewBlock/schema.js +3 -1
- package/src/components/manage/Blocks/CaseStudyExplorer/FeatureDisplay.jsx +6 -1
- package/src/components/manage/Blocks/CaseStudyExplorer/styles.less +1 -1
- package/src/components/manage/Blocks/CollectionStatistics/styles.less +1 -1
- package/src/components/manage/Blocks/ContentLinks/schema.js +3 -1
- package/src/components/manage/Blocks/CountryMapHeatIndex/euro-countries-simplified.js +46197 -0
- package/src/components/manage/Blocks/CountryMapHeatIndex/styles.less +1 -1
- package/src/components/manage/Blocks/CountryMapObservatory/mapstyle.js +28 -26
- package/src/components/manage/Blocks/CountryMapObservatory/styles.less +1 -1
- package/src/components/manage/Blocks/CountryMapProfile/OLView.jsx +5 -5
- package/src/components/manage/Blocks/CountryMapProfile/euro-countries-simplified.js +46197 -0
- package/src/components/manage/Blocks/CountryMapProfile/styles.less +1 -1
- package/src/components/manage/Blocks/CountryProfileDetail/View.test.jsx +1 -2
- package/src/components/manage/Blocks/FlourishEmbedBlock/schema.js +3 -1
- package/src/components/manage/Blocks/RASTBlock/schema.js +3 -1
- package/src/components/manage/Blocks/ReadMore/schema.js +3 -1
- package/src/components/manage/Blocks/RelevantAceContent/schema.js +3 -1
- package/src/components/manage/Blocks/SearchAceContent/schema.js +3 -1
- package/src/components/manage/Blocks/TransRegionSelect/schema.js +3 -1
- package/src/components/manage/Blocks/index.js +0 -1
- package/src/components/theme/ASTNavigation/ASTNavigation.jsx +2 -3
- package/src/components/theme/BannerTitle/BannerTitle.jsx +2 -0
- package/src/components/theme/Header/Header.jsx +32 -22
- package/src/components/theme/Header/LanguageSwitch.jsx +2 -2
- package/src/components/theme/Views/BrokenLinks.jsx +0 -2
- package/src/components/theme/Views/CcaEventView.jsx +2 -4
- package/src/components/theme/Views/DatabaseItemView.test.jsx +1 -2
- package/src/components/theme/Widgets/GeolocationWidget.jsx +0 -2
- package/src/customizations/@plone/volto-slate/utils/volto-blocks.js +2 -5
- package/src/customizations/@plone/volto-slate/widgets/HtmlSlateWidget.jsx +1 -2
- package/src/customizations/volto/components/manage/Blocks/Grid/grid-1.svg +6 -0
- package/src/customizations/volto/components/manage/Blocks/Grid/grid-2.svg +9 -0
- package/src/customizations/volto/components/manage/Blocks/Grid/grid-3.svg +10 -0
- package/src/customizations/volto/components/manage/Blocks/Grid/grid-4.svg +11 -0
- package/src/customizations/volto/components/manage/Blocks/Grid/grid-5.svg +13 -0
- package/src/customizations/volto/components/manage/Blocks/Grid/grid-6.svg +14 -0
- package/src/customizations/volto/components/manage/Blocks/Grid/templates.js +61 -0
- package/src/customizations/volto/components/theme/App/App.jsx +3 -1
- package/src/customizations/volto/helpers/Html/Html.jsx +2 -9
- package/src/customizations/volto/middleware/api.js +190 -186
- package/src/customizations/volto/server.jsx +1 -1
- package/src/express-middleware.js +4 -2
- package/src/helpers/Utils.jsx +15 -2
- package/src/helpers/country_map/euro-countries-simplified.js +3 -1
- package/src/index.js +2 -8
- package/src/search/cca/views.js +3 -1
- package/src/search/health_observatory/views-health.js +3 -1
- package/src/store/middleware.js +38 -37
- package/theme/globals/blocks.less +4 -0
- package/theme/globals/mission.less +2 -9
- package/theme/globals/observatory.less +2 -2
- package/theme/globals/views.less +25 -0
- package/src/components/theme/Views/brokenlinks.less +0 -21
- package/src/components/theme/Widgets/geolocation.css +0 -3
- package/src/customizations/volto/components/manage/UniversalLink/UniversalLink.test.jsx +0 -229
- package/src/customizations/volto/components/manage/Widgets/ArrayWidget.jsx +0 -428
- package/src/customizations/volto/components/manage/Widgets/Readme.md +0 -5
- package/src/customizations/volto/components/theme/Footer/Footer.jsx +0 -96
- package/src/policy.js +0 -135
package/theme/globals/views.less
CHANGED
|
@@ -33,6 +33,10 @@ div.geochars-field {
|
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
.geolocation-field .ol-map {
|
|
37
|
+
cursor: pointer;
|
|
38
|
+
}
|
|
39
|
+
|
|
36
40
|
.btn-right {
|
|
37
41
|
margin: calc(2rem - 0.1em) 0em 1rem;
|
|
38
42
|
float: right;
|
|
@@ -335,3 +339,24 @@ hr {
|
|
|
335
339
|
border-radius: 4px;
|
|
336
340
|
border: 1px solid #e5e5e5;
|
|
337
341
|
}
|
|
342
|
+
|
|
343
|
+
.broken-links-table {
|
|
344
|
+
td {
|
|
345
|
+
max-width: 200px;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
a {
|
|
349
|
+
display: block;
|
|
350
|
+
overflow: hidden;
|
|
351
|
+
text-overflow: ellipsis;
|
|
352
|
+
white-space: nowrap;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
.cursor-pointer {
|
|
356
|
+
cursor: pointer;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
.select-none {
|
|
360
|
+
user-select: none;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
.broken-links-table {
|
|
2
|
-
td {
|
|
3
|
-
max-width: 200px;
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
a {
|
|
7
|
-
display: block;
|
|
8
|
-
overflow: hidden;
|
|
9
|
-
text-overflow: ellipsis;
|
|
10
|
-
white-space: nowrap;
|
|
11
|
-
// max-width: 200px;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
.cursor-pointer {
|
|
15
|
-
cursor: pointer;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
.select-none {
|
|
19
|
-
user-select: none;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
@@ -1,229 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import renderer from 'react-test-renderer';
|
|
3
|
-
import { Provider } from 'react-intl-redux';
|
|
4
|
-
import configureStore from 'redux-mock-store';
|
|
5
|
-
import { render } from '@testing-library/react';
|
|
6
|
-
import { MemoryRouter } from 'react-router-dom';
|
|
7
|
-
import UniversalLink from './UniversalLink';
|
|
8
|
-
import config from '@plone/volto/registry';
|
|
9
|
-
|
|
10
|
-
const mockStore = configureStore();
|
|
11
|
-
const store = mockStore({
|
|
12
|
-
userSession: {
|
|
13
|
-
token: null,
|
|
14
|
-
},
|
|
15
|
-
intl: {
|
|
16
|
-
locale: 'en',
|
|
17
|
-
messages: {},
|
|
18
|
-
},
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
global.console.error = jest.fn();
|
|
22
|
-
|
|
23
|
-
describe('UniversalLink', () => {
|
|
24
|
-
it('renders a UniversalLink component with internal link', () => {
|
|
25
|
-
const component = renderer.create(
|
|
26
|
-
<Provider store={store}>
|
|
27
|
-
<MemoryRouter>
|
|
28
|
-
<UniversalLink href={'/en/welcome-to-volto'}>
|
|
29
|
-
<h1>Title</h1>
|
|
30
|
-
</UniversalLink>
|
|
31
|
-
</MemoryRouter>
|
|
32
|
-
</Provider>,
|
|
33
|
-
);
|
|
34
|
-
const json = component.toJSON();
|
|
35
|
-
expect(json).toMatchSnapshot();
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it('renders a UniversalLink component with external link', () => {
|
|
39
|
-
const component = renderer.create(
|
|
40
|
-
<Provider store={store}>
|
|
41
|
-
<MemoryRouter>
|
|
42
|
-
<UniversalLink href="https://github.com/plone/volto">
|
|
43
|
-
<h1>Title</h1>
|
|
44
|
-
</UniversalLink>
|
|
45
|
-
</MemoryRouter>
|
|
46
|
-
</Provider>,
|
|
47
|
-
);
|
|
48
|
-
const json = component.toJSON();
|
|
49
|
-
expect(json).toMatchSnapshot();
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it('renders a UniversalLink component if no external(href) link passed', () => {
|
|
53
|
-
const component = renderer.create(
|
|
54
|
-
<Provider store={store}>
|
|
55
|
-
<MemoryRouter>
|
|
56
|
-
<UniversalLink
|
|
57
|
-
item={{
|
|
58
|
-
'@id': 'http://localhost:3000/en/welcome-to-volto',
|
|
59
|
-
}}
|
|
60
|
-
>
|
|
61
|
-
<h1>Title</h1>
|
|
62
|
-
</UniversalLink>
|
|
63
|
-
</MemoryRouter>
|
|
64
|
-
</Provider>,
|
|
65
|
-
);
|
|
66
|
-
const json = component.toJSON();
|
|
67
|
-
expect(json).toMatchSnapshot();
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it('check UniversalLink set rel attribute for ext links', () => {
|
|
71
|
-
const { getByTitle } = render(
|
|
72
|
-
<Provider store={store}>
|
|
73
|
-
<MemoryRouter>
|
|
74
|
-
<UniversalLink
|
|
75
|
-
href="https://github.com/plone/volto"
|
|
76
|
-
title="Volto GitHub repository"
|
|
77
|
-
>
|
|
78
|
-
<h1>Title</h1>
|
|
79
|
-
</UniversalLink>
|
|
80
|
-
</MemoryRouter>
|
|
81
|
-
</Provider>,
|
|
82
|
-
);
|
|
83
|
-
|
|
84
|
-
expect(getByTitle('Volto GitHub repository').getAttribute('rel')).toBe(
|
|
85
|
-
'noopener',
|
|
86
|
-
);
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it('check UniversalLink set target attribute for ext links', () => {
|
|
90
|
-
const { getByTitle } = render(
|
|
91
|
-
<Provider store={store}>
|
|
92
|
-
<MemoryRouter>
|
|
93
|
-
<UniversalLink
|
|
94
|
-
href="https://github.com/plone/volto"
|
|
95
|
-
title="Volto GitHub repository"
|
|
96
|
-
>
|
|
97
|
-
<h1>Title</h1>
|
|
98
|
-
</UniversalLink>
|
|
99
|
-
</MemoryRouter>
|
|
100
|
-
</Provider>,
|
|
101
|
-
);
|
|
102
|
-
|
|
103
|
-
expect(getByTitle('Volto GitHub repository').getAttribute('target')).toBe(
|
|
104
|
-
'_blank',
|
|
105
|
-
);
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
it('check UniversalLink can unset target for ext links with prop', () => {
|
|
109
|
-
const { getByTitle } = render(
|
|
110
|
-
<Provider store={store}>
|
|
111
|
-
<MemoryRouter>
|
|
112
|
-
<UniversalLink
|
|
113
|
-
href="https://github.com/plone/volto"
|
|
114
|
-
title="Volto GitHub repository"
|
|
115
|
-
openLinkInNewTab={false}
|
|
116
|
-
>
|
|
117
|
-
<h1>Title</h1>
|
|
118
|
-
</UniversalLink>
|
|
119
|
-
</MemoryRouter>
|
|
120
|
-
</Provider>,
|
|
121
|
-
);
|
|
122
|
-
|
|
123
|
-
expect(
|
|
124
|
-
getByTitle('Volto GitHub repository').getAttribute('target'),
|
|
125
|
-
).toBeNull();
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
it('check UniversalLink renders ext link for blacklisted urls', () => {
|
|
129
|
-
config.settings.externalRoutes = [
|
|
130
|
-
{
|
|
131
|
-
match: {
|
|
132
|
-
path: '/external-app',
|
|
133
|
-
exact: true,
|
|
134
|
-
strict: false,
|
|
135
|
-
},
|
|
136
|
-
url(payload) {
|
|
137
|
-
return payload.location.pathname;
|
|
138
|
-
},
|
|
139
|
-
},
|
|
140
|
-
];
|
|
141
|
-
|
|
142
|
-
const { getByTitle } = render(
|
|
143
|
-
<Provider store={store}>
|
|
144
|
-
<MemoryRouter>
|
|
145
|
-
<UniversalLink
|
|
146
|
-
href="http://localhost:3000/external-app"
|
|
147
|
-
title="Blacklisted route"
|
|
148
|
-
>
|
|
149
|
-
<h1>Title</h1>
|
|
150
|
-
</UniversalLink>
|
|
151
|
-
</MemoryRouter>
|
|
152
|
-
</Provider>,
|
|
153
|
-
);
|
|
154
|
-
|
|
155
|
-
expect(getByTitle('Blacklisted route').getAttribute('target')).toBe(
|
|
156
|
-
'_blank',
|
|
157
|
-
);
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
it('UniversalLink renders external link where link is blacklisted', () => {
|
|
161
|
-
const notInEN = /^(?!.*(#|\/en|\/static|\/controlpanel|\/cypress|\/login|\/logout|\/contact-form)).*$/;
|
|
162
|
-
config.settings.externalRoutes = [
|
|
163
|
-
{
|
|
164
|
-
match: {
|
|
165
|
-
path: notInEN,
|
|
166
|
-
exact: false,
|
|
167
|
-
strict: false,
|
|
168
|
-
},
|
|
169
|
-
url(payload) {
|
|
170
|
-
return payload.location.pathname;
|
|
171
|
-
},
|
|
172
|
-
},
|
|
173
|
-
];
|
|
174
|
-
|
|
175
|
-
const { getByTitle } = render(
|
|
176
|
-
<Provider store={store}>
|
|
177
|
-
<MemoryRouter>
|
|
178
|
-
<UniversalLink
|
|
179
|
-
href="http://localhost:3000/blacklisted-app"
|
|
180
|
-
title="External blacklisted app"
|
|
181
|
-
>
|
|
182
|
-
<h1>Title</h1>
|
|
183
|
-
</UniversalLink>
|
|
184
|
-
</MemoryRouter>
|
|
185
|
-
</Provider>,
|
|
186
|
-
);
|
|
187
|
-
|
|
188
|
-
expect(getByTitle('External blacklisted app').getAttribute('target')).toBe(
|
|
189
|
-
'_blank',
|
|
190
|
-
);
|
|
191
|
-
expect(getByTitle('External blacklisted app').getAttribute('rel')).toBe(
|
|
192
|
-
'noopener',
|
|
193
|
-
);
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
it('check UniversalLink does not break with error in item', () => {
|
|
197
|
-
const component = renderer.create(
|
|
198
|
-
<Provider store={store}>
|
|
199
|
-
<MemoryRouter>
|
|
200
|
-
<UniversalLink
|
|
201
|
-
item={{
|
|
202
|
-
error: 'Error while fetching content',
|
|
203
|
-
message: 'Something went wrong',
|
|
204
|
-
}}
|
|
205
|
-
>
|
|
206
|
-
<h1>Title</h1>
|
|
207
|
-
</UniversalLink>
|
|
208
|
-
</MemoryRouter>
|
|
209
|
-
</Provider>,
|
|
210
|
-
);
|
|
211
|
-
const json = component.toJSON();
|
|
212
|
-
expect(json).toMatchSnapshot();
|
|
213
|
-
expect(global.console.error).toHaveBeenCalled();
|
|
214
|
-
});
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
it('renders a UniversalLink component when url ends with @@display-file', () => {
|
|
218
|
-
const component = renderer.create(
|
|
219
|
-
<Provider store={store}>
|
|
220
|
-
<MemoryRouter>
|
|
221
|
-
<UniversalLink href="http://localhost:3000/en/welcome-to-volto/@@display-file">
|
|
222
|
-
<h1>Title</h1>
|
|
223
|
-
</UniversalLink>
|
|
224
|
-
</MemoryRouter>
|
|
225
|
-
</Provider>,
|
|
226
|
-
);
|
|
227
|
-
const json = component.toJSON();
|
|
228
|
-
expect(json).toMatchSnapshot();
|
|
229
|
-
});
|
|
@@ -1,428 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ArrayWidget component.
|
|
3
|
-
* @module components/manage/Widgets/ArrayWidget
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import React, { Component } from 'react';
|
|
7
|
-
import { defineMessages, injectIntl } from 'react-intl';
|
|
8
|
-
import PropTypes from 'prop-types';
|
|
9
|
-
import { compose } from 'redux';
|
|
10
|
-
import { connect } from 'react-redux';
|
|
11
|
-
import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable';
|
|
12
|
-
import { find, isObject } from 'lodash';
|
|
13
|
-
|
|
14
|
-
import {
|
|
15
|
-
getVocabFromHint,
|
|
16
|
-
getVocabFromField,
|
|
17
|
-
getVocabFromItems,
|
|
18
|
-
} from '@plone/volto/helpers';
|
|
19
|
-
import { getVocabulary } from '@plone/volto/actions';
|
|
20
|
-
|
|
21
|
-
import {
|
|
22
|
-
Option,
|
|
23
|
-
DropdownIndicator,
|
|
24
|
-
ClearIndicator,
|
|
25
|
-
selectTheme,
|
|
26
|
-
customSelectStyles,
|
|
27
|
-
MenuList,
|
|
28
|
-
SortableMultiValue,
|
|
29
|
-
SortableMultiValueLabel,
|
|
30
|
-
MultiValueContainer,
|
|
31
|
-
} from '@plone/volto/components/manage/Widgets/SelectStyling';
|
|
32
|
-
|
|
33
|
-
import { FormFieldWrapper } from '@plone/volto/components';
|
|
34
|
-
|
|
35
|
-
const messages = defineMessages({
|
|
36
|
-
select: {
|
|
37
|
-
id: 'Select…',
|
|
38
|
-
defaultMessage: 'Select…',
|
|
39
|
-
},
|
|
40
|
-
no_value: {
|
|
41
|
-
id: 'No value',
|
|
42
|
-
defaultMessage: 'No value',
|
|
43
|
-
},
|
|
44
|
-
no_options: {
|
|
45
|
-
id: 'No options',
|
|
46
|
-
defaultMessage: 'No options',
|
|
47
|
-
},
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
function arrayMove(array, from, to) {
|
|
51
|
-
const slicedArray = array.slice();
|
|
52
|
-
slicedArray.splice(
|
|
53
|
-
to < 0 ? array.length + to : to,
|
|
54
|
-
0,
|
|
55
|
-
slicedArray.splice(from, 1)[0],
|
|
56
|
-
);
|
|
57
|
-
return slicedArray;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function normalizeArrayValue(choices, value) {
|
|
61
|
-
if (!value || !Array.isArray(value)) return [];
|
|
62
|
-
if (value.length === 0) return value;
|
|
63
|
-
|
|
64
|
-
if (typeof value[0] === 'string') {
|
|
65
|
-
// raw value like ['foo', 'bar']
|
|
66
|
-
return value.map((v) => {
|
|
67
|
-
return {
|
|
68
|
-
label: find(choices, (c) => c.value === v)?.label || v,
|
|
69
|
-
value: v,
|
|
70
|
-
};
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
if (
|
|
75
|
-
isObject(value[0]) &&
|
|
76
|
-
Object.keys(value[0]).includes('token') // Array of objects, w/ label+value
|
|
77
|
-
) {
|
|
78
|
-
return value
|
|
79
|
-
.map((v) => {
|
|
80
|
-
const item = find(choices, (c) => c.value === v.token);
|
|
81
|
-
return item
|
|
82
|
-
? {
|
|
83
|
-
label: item.label || item.title || item.token,
|
|
84
|
-
value: v.token,
|
|
85
|
-
}
|
|
86
|
-
: {
|
|
87
|
-
// avoid a crash if choices doesn't include this item
|
|
88
|
-
label: v.label,
|
|
89
|
-
value: v.token,
|
|
90
|
-
};
|
|
91
|
-
})
|
|
92
|
-
.filter((f) => !!f);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
return [];
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function normalizeChoices(choices) {
|
|
99
|
-
if (Array.isArray(choices) && choices.length && Array.isArray(choices[0])) {
|
|
100
|
-
return choices.map((option) => ({
|
|
101
|
-
value: option[0],
|
|
102
|
-
label:
|
|
103
|
-
// Fix "None" on the serializer, to remove when fixed in p.restapi
|
|
104
|
-
option[1] !== 'None' && option[1] ? option[1] : option[0],
|
|
105
|
-
}));
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return choices;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Compare values and return true if equal.
|
|
113
|
-
* Consider upper and lower case.
|
|
114
|
-
* @method compareOption
|
|
115
|
-
* @param {*} inputValue
|
|
116
|
-
* @param {*} option
|
|
117
|
-
* @param {*} accessors
|
|
118
|
-
* @returns {boolean}
|
|
119
|
-
*/
|
|
120
|
-
const compareOption = (inputValue = '', option, accessors) => {
|
|
121
|
-
const candidate = String(inputValue);
|
|
122
|
-
const optionValue = String(accessors.getOptionValue(option));
|
|
123
|
-
const optionLabel = String(accessors.getOptionLabel(option));
|
|
124
|
-
return optionValue === candidate || optionLabel === candidate;
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* ArrayWidget component class.
|
|
129
|
-
* @class ArrayWidget
|
|
130
|
-
* @extends Component
|
|
131
|
-
*
|
|
132
|
-
* A createable select array widget will be rendered if the named vocabulary is
|
|
133
|
-
* in the widget definition (hint) like:
|
|
134
|
-
*
|
|
135
|
-
* ```
|
|
136
|
-
* list_field_voc_unconstrained = schema.List(
|
|
137
|
-
* title=u"List field with values from vocabulary but not constrained to them.",
|
|
138
|
-
* description=u"zope.schema.List",
|
|
139
|
-
* value_type=schema.TextLine(),
|
|
140
|
-
* required=False,
|
|
141
|
-
* missing_value=[],
|
|
142
|
-
* )
|
|
143
|
-
* directives.widget(
|
|
144
|
-
* "list_field_voc_unconstrained",
|
|
145
|
-
* AjaxSelectFieldWidget,
|
|
146
|
-
* vocabulary="plone.app.vocabularies.PortalTypes",
|
|
147
|
-
* )
|
|
148
|
-
* ```
|
|
149
|
-
*/
|
|
150
|
-
class ArrayWidget extends Component {
|
|
151
|
-
/**
|
|
152
|
-
* Property types.
|
|
153
|
-
* @property {Object} propTypes Property types.
|
|
154
|
-
* @static
|
|
155
|
-
*/
|
|
156
|
-
static propTypes = {
|
|
157
|
-
id: PropTypes.string.isRequired,
|
|
158
|
-
title: PropTypes.string.isRequired,
|
|
159
|
-
description: PropTypes.string,
|
|
160
|
-
required: PropTypes.bool,
|
|
161
|
-
error: PropTypes.arrayOf(PropTypes.string),
|
|
162
|
-
getVocabulary: PropTypes.func.isRequired,
|
|
163
|
-
choices: PropTypes.arrayOf(
|
|
164
|
-
PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
|
|
165
|
-
),
|
|
166
|
-
vocabLoading: PropTypes.bool,
|
|
167
|
-
vocabLoaded: PropTypes.bool,
|
|
168
|
-
items: PropTypes.shape({
|
|
169
|
-
vocabulary: PropTypes.object,
|
|
170
|
-
}),
|
|
171
|
-
widgetOptions: PropTypes.shape({
|
|
172
|
-
vocabulary: PropTypes.object,
|
|
173
|
-
}),
|
|
174
|
-
value: PropTypes.arrayOf(
|
|
175
|
-
PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
|
|
176
|
-
),
|
|
177
|
-
placeholder: PropTypes.string,
|
|
178
|
-
onChange: PropTypes.func.isRequired,
|
|
179
|
-
wrapped: PropTypes.bool,
|
|
180
|
-
creatable: PropTypes.bool, //if widget has no vocab and you want to be creatable
|
|
181
|
-
};
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* Default properties
|
|
185
|
-
* @property {Object} defaultProps Default properties.
|
|
186
|
-
* @static
|
|
187
|
-
*/
|
|
188
|
-
static defaultProps = {
|
|
189
|
-
description: null,
|
|
190
|
-
required: false,
|
|
191
|
-
items: {
|
|
192
|
-
vocabulary: null,
|
|
193
|
-
},
|
|
194
|
-
widgetOptions: {
|
|
195
|
-
vocabulary: null,
|
|
196
|
-
},
|
|
197
|
-
error: [],
|
|
198
|
-
choices: [],
|
|
199
|
-
value: null,
|
|
200
|
-
creatable: false,
|
|
201
|
-
};
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* Constructor
|
|
205
|
-
* @method constructor
|
|
206
|
-
* @param {Object} props Component properties
|
|
207
|
-
* @constructs Actions
|
|
208
|
-
*/
|
|
209
|
-
constructor(props) {
|
|
210
|
-
super(props);
|
|
211
|
-
|
|
212
|
-
this.handleChange = this.handleChange.bind(this);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
/**
|
|
216
|
-
* Component did mount
|
|
217
|
-
* @method componentDidMount
|
|
218
|
-
* @returns {undefined}
|
|
219
|
-
*/
|
|
220
|
-
componentDidMount() {
|
|
221
|
-
if (
|
|
222
|
-
!this.props.items?.choices?.length &&
|
|
223
|
-
!this.props.choices?.length &&
|
|
224
|
-
this.props.vocabBaseUrl
|
|
225
|
-
) {
|
|
226
|
-
this.props.getVocabulary({
|
|
227
|
-
vocabNameOrURL: this.props.vocabBaseUrl,
|
|
228
|
-
size: -1,
|
|
229
|
-
subrequest: this.props.lang,
|
|
230
|
-
});
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
componentDidUpdate() {
|
|
235
|
-
if (
|
|
236
|
-
!this.props.items?.choices?.length &&
|
|
237
|
-
!this.props.choices?.length &&
|
|
238
|
-
this.props.vocabLoading === undefined &&
|
|
239
|
-
!this.props.vocabLoaded &&
|
|
240
|
-
this.props.vocabBaseUrl
|
|
241
|
-
) {
|
|
242
|
-
this.props.getVocabulary({
|
|
243
|
-
vocabNameOrURL: this.props.vocabBaseUrl,
|
|
244
|
-
size: -1,
|
|
245
|
-
subrequest: this.props.lang,
|
|
246
|
-
});
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
/**
|
|
251
|
-
* Handle the field change, store it in the local state and back to simple
|
|
252
|
-
* array of tokens for correct serialization
|
|
253
|
-
* @method handleChange
|
|
254
|
-
* @param {array} selectedOption The selected options (already aggregated).
|
|
255
|
-
* @returns {undefined}
|
|
256
|
-
*/
|
|
257
|
-
handleChange(selectedOption) {
|
|
258
|
-
this.props.onChange(
|
|
259
|
-
this.props.id,
|
|
260
|
-
selectedOption ? selectedOption.map((item) => item.value) : null,
|
|
261
|
-
);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
onSortEnd = (selectedOption, { oldIndex, newIndex }) => {
|
|
265
|
-
const newValue = arrayMove(selectedOption, oldIndex, newIndex);
|
|
266
|
-
|
|
267
|
-
this.handleChange(newValue);
|
|
268
|
-
};
|
|
269
|
-
|
|
270
|
-
/**
|
|
271
|
-
* Render method.
|
|
272
|
-
* @method render
|
|
273
|
-
* @returns {string} Markup for the component.
|
|
274
|
-
*/
|
|
275
|
-
render() {
|
|
276
|
-
const choices = normalizeChoices(this.props?.choices || []);
|
|
277
|
-
const selectedOption = normalizeArrayValue(choices, this.props.value);
|
|
278
|
-
|
|
279
|
-
const CreatableSelect = this.props.reactSelectCreateable.default;
|
|
280
|
-
const { SortableContainer } = this.props.reactSortableHOC;
|
|
281
|
-
const Select = this.props.reactSelect.default;
|
|
282
|
-
const SortableSelect =
|
|
283
|
-
// It will be only createable if the named vocabulary is in the widget definition
|
|
284
|
-
// (hint) like:
|
|
285
|
-
// list_field_voc_unconstrained = schema.List(
|
|
286
|
-
// title=u"List field with values from vocabulary but not constrained to them.",
|
|
287
|
-
// description=u"zope.schema.List",
|
|
288
|
-
// value_type=schema.TextLine(),
|
|
289
|
-
// required=False,
|
|
290
|
-
// missing_value=[],
|
|
291
|
-
// )
|
|
292
|
-
// directives.widget(
|
|
293
|
-
// "list_field_voc_unconstrained",
|
|
294
|
-
// AjaxSelectFieldWidget,
|
|
295
|
-
// vocabulary="plone.app.vocabularies.PortalTypes",
|
|
296
|
-
// )
|
|
297
|
-
this.props?.choices &&
|
|
298
|
-
!getVocabFromHint(this.props) &&
|
|
299
|
-
!this.props.creatable
|
|
300
|
-
? SortableContainer(Select)
|
|
301
|
-
: SortableContainer(CreatableSelect);
|
|
302
|
-
|
|
303
|
-
return (
|
|
304
|
-
<FormFieldWrapper {...this.props}>
|
|
305
|
-
<SortableSelect
|
|
306
|
-
useDragHandle
|
|
307
|
-
// react-sortable-hoc props:
|
|
308
|
-
axis="xy"
|
|
309
|
-
onSortEnd={this.onSortEnd}
|
|
310
|
-
menuShouldScrollIntoView={false}
|
|
311
|
-
distance={4}
|
|
312
|
-
// small fix for https://github.com/clauderic/react-sortable-hoc/pull/352:
|
|
313
|
-
getHelperDimensions={({ node }) => node.getBoundingClientRect()}
|
|
314
|
-
id={`field-${this.props.id}`}
|
|
315
|
-
key={this.props.id}
|
|
316
|
-
isDisabled={this.props.disabled || this.props.isDisabled}
|
|
317
|
-
className="react-select-container"
|
|
318
|
-
classNamePrefix="react-select"
|
|
319
|
-
/* eslint-disable jsx-a11y/no-autofocus */
|
|
320
|
-
// start customization
|
|
321
|
-
// autoFocus={this.props.focus}
|
|
322
|
-
// end customization
|
|
323
|
-
/* eslint-enable jsx-a11y/no-autofocus */
|
|
324
|
-
options={
|
|
325
|
-
this.props.vocabBaseUrl
|
|
326
|
-
? choices
|
|
327
|
-
: this.props.choices
|
|
328
|
-
? [
|
|
329
|
-
...choices,
|
|
330
|
-
...(this.props.noValueOption &&
|
|
331
|
-
(this.props.default === undefined ||
|
|
332
|
-
this.props.default === null)
|
|
333
|
-
? [
|
|
334
|
-
{
|
|
335
|
-
label: this.props.intl.formatMessage(
|
|
336
|
-
messages.no_value,
|
|
337
|
-
),
|
|
338
|
-
value: 'no-value',
|
|
339
|
-
},
|
|
340
|
-
]
|
|
341
|
-
: []),
|
|
342
|
-
]
|
|
343
|
-
: [
|
|
344
|
-
{
|
|
345
|
-
label: this.props.intl.formatMessage(messages.no_value),
|
|
346
|
-
value: 'no-value',
|
|
347
|
-
},
|
|
348
|
-
]
|
|
349
|
-
}
|
|
350
|
-
styles={customSelectStyles}
|
|
351
|
-
theme={selectTheme}
|
|
352
|
-
components={{
|
|
353
|
-
...(this.props.choices?.length > 25 && {
|
|
354
|
-
MenuList,
|
|
355
|
-
}),
|
|
356
|
-
MultiValueContainer,
|
|
357
|
-
MultiValue: SortableMultiValue,
|
|
358
|
-
MultiValueLabel: SortableMultiValueLabel,
|
|
359
|
-
DropdownIndicator,
|
|
360
|
-
ClearIndicator,
|
|
361
|
-
Option,
|
|
362
|
-
}}
|
|
363
|
-
value={selectedOption || []}
|
|
364
|
-
placeholder={
|
|
365
|
-
this.props.placeholder ??
|
|
366
|
-
this.props.intl.formatMessage(messages.select)
|
|
367
|
-
}
|
|
368
|
-
onChange={this.handleChange}
|
|
369
|
-
isValidNewOption={(
|
|
370
|
-
inputValue,
|
|
371
|
-
selectValue,
|
|
372
|
-
selectOptions,
|
|
373
|
-
accessors,
|
|
374
|
-
) =>
|
|
375
|
-
!(
|
|
376
|
-
!inputValue ||
|
|
377
|
-
selectValue.some((option) =>
|
|
378
|
-
compareOption(inputValue, option, accessors),
|
|
379
|
-
) ||
|
|
380
|
-
selectOptions.some((option) =>
|
|
381
|
-
compareOption(inputValue, option, accessors),
|
|
382
|
-
)
|
|
383
|
-
)
|
|
384
|
-
}
|
|
385
|
-
isClearable
|
|
386
|
-
isMulti
|
|
387
|
-
/>
|
|
388
|
-
</FormFieldWrapper>
|
|
389
|
-
);
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
export const ArrayWidgetComponent = injectIntl(ArrayWidget);
|
|
394
|
-
|
|
395
|
-
export default compose(
|
|
396
|
-
injectIntl,
|
|
397
|
-
injectLazyLibs(['reactSelect', 'reactSelectCreateable', 'reactSortableHOC']),
|
|
398
|
-
connect(
|
|
399
|
-
(state, props) => {
|
|
400
|
-
const vocabBaseUrl =
|
|
401
|
-
getVocabFromHint(props) ||
|
|
402
|
-
getVocabFromField(props) ||
|
|
403
|
-
getVocabFromItems(props);
|
|
404
|
-
|
|
405
|
-
const vocabState =
|
|
406
|
-
state.vocabularies?.[vocabBaseUrl]?.subrequests?.[state.intl.locale];
|
|
407
|
-
|
|
408
|
-
// If the schema already has the choices in it, then do not try to get the vocab,
|
|
409
|
-
// even if there is one
|
|
410
|
-
if (props.items?.choices) {
|
|
411
|
-
return {
|
|
412
|
-
choices: props.items.choices,
|
|
413
|
-
lang: state.intl.locale,
|
|
414
|
-
};
|
|
415
|
-
} else if (vocabState) {
|
|
416
|
-
return {
|
|
417
|
-
choices: vocabState.items,
|
|
418
|
-
vocabBaseUrl,
|
|
419
|
-
vocabLoading: vocabState.loading,
|
|
420
|
-
vocabLoaded: vocabState.loaded,
|
|
421
|
-
lang: state.intl.locale,
|
|
422
|
-
};
|
|
423
|
-
}
|
|
424
|
-
return { vocabBaseUrl, lang: state.intl.locale };
|
|
425
|
-
},
|
|
426
|
-
{ getVocabulary },
|
|
427
|
-
),
|
|
428
|
-
)(ArrayWidget);
|