@eeacms/volto-eea-website-theme 1.30.0 → 1.31.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/CHANGELOG.md
CHANGED
@@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file. Dates are d
|
|
4
4
|
|
5
5
|
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
6
6
|
|
7
|
+
### [1.31.0](https://github.com/eea/volto-eea-website-theme/compare/1.30.0...1.31.0) - 14 March 2024
|
8
|
+
|
9
|
+
#### :rocket: New Features
|
10
|
+
|
11
|
+
- feat: Put default alt as the rights field than image title - refs #159551 [dobri1408 - [`4f0e60b`](https://github.com/eea/volto-eea-website-theme/commit/4f0e60b02ce1167d92de3b294e75d2577c892c85)]
|
12
|
+
|
13
|
+
#### :hammer_and_wrench: Others
|
14
|
+
|
15
|
+
- Release 1.31.0 [alin - [`36690f7`](https://github.com/eea/volto-eea-website-theme/commit/36690f75e4773e40f5ab4e4ce4b956b650df62b5)]
|
7
16
|
### [1.30.0](https://github.com/eea/volto-eea-website-theme/compare/1.29.0...1.30.0) - 13 March 2024
|
8
17
|
|
9
18
|
#### :rocket: New Features
|
package/package.json
CHANGED
@@ -0,0 +1,511 @@
|
|
1
|
+
/* this customization is used to put default alt as the rights field
|
2
|
+
*/
|
3
|
+
import React, { Component } from 'react';
|
4
|
+
import PropTypes from 'prop-types';
|
5
|
+
import { compose } from 'redux';
|
6
|
+
import { connect } from 'react-redux';
|
7
|
+
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
|
8
|
+
import { Input, Segment, Breadcrumb } from 'semantic-ui-react';
|
9
|
+
|
10
|
+
import { join } from 'lodash';
|
11
|
+
|
12
|
+
// These absolute imports (without using the corresponding centralized index.js) are required
|
13
|
+
// to cut circular import problems, this file should never use them. This is because of
|
14
|
+
// the very nature of the functionality of the component and its relationship with others
|
15
|
+
import { searchContent } from '@plone/volto/actions/search/search';
|
16
|
+
import Icon from '@plone/volto/components/theme/Icon/Icon';
|
17
|
+
import { flattenToAppURL, isInternalURL } from '@plone/volto/helpers/Url/Url';
|
18
|
+
import config from '@plone/volto/registry';
|
19
|
+
|
20
|
+
import backSVG from '@plone/volto/icons/back.svg';
|
21
|
+
import folderSVG from '@plone/volto/icons/folder.svg';
|
22
|
+
import clearSVG from '@plone/volto/icons/clear.svg';
|
23
|
+
import searchSVG from '@plone/volto/icons/zoom.svg';
|
24
|
+
import linkSVG from '@plone/volto/icons/link.svg';
|
25
|
+
import homeSVG from '@plone/volto/icons/home.svg';
|
26
|
+
|
27
|
+
import ObjectBrowserNav from '@plone/volto/components/manage/Sidebar/ObjectBrowserNav';
|
28
|
+
|
29
|
+
const messages = defineMessages({
|
30
|
+
SearchInputPlaceholder: {
|
31
|
+
id: 'Search content',
|
32
|
+
defaultMessage: 'Search content',
|
33
|
+
},
|
34
|
+
SelectedItems: {
|
35
|
+
id: 'Selected items',
|
36
|
+
defaultMessage: 'Selected items',
|
37
|
+
},
|
38
|
+
back: {
|
39
|
+
id: 'Back',
|
40
|
+
defaultMessage: 'Back',
|
41
|
+
},
|
42
|
+
search: {
|
43
|
+
id: 'Search SVG',
|
44
|
+
defaultMessage: 'Search SVG',
|
45
|
+
},
|
46
|
+
of: { id: 'Selected items - x of y', defaultMessage: 'of' },
|
47
|
+
});
|
48
|
+
|
49
|
+
function getParentURL(url) {
|
50
|
+
return flattenToAppURL(`${join(url.split('/').slice(0, -1), '/')}`) || '/';
|
51
|
+
}
|
52
|
+
|
53
|
+
/**
|
54
|
+
* ObjectBrowserBody container class.
|
55
|
+
* @class ObjectBrowserBody
|
56
|
+
* @extends Component
|
57
|
+
*/
|
58
|
+
class ObjectBrowserBody extends Component {
|
59
|
+
/**
|
60
|
+
* Property types.
|
61
|
+
* @property {Object} propTypes Property types.
|
62
|
+
* @static
|
63
|
+
*/
|
64
|
+
static propTypes = {
|
65
|
+
block: PropTypes.string.isRequired,
|
66
|
+
mode: PropTypes.string.isRequired,
|
67
|
+
data: PropTypes.any.isRequired,
|
68
|
+
searchSubrequests: PropTypes.objectOf(PropTypes.any).isRequired,
|
69
|
+
searchContent: PropTypes.func.isRequired,
|
70
|
+
closeObjectBrowser: PropTypes.func.isRequired,
|
71
|
+
onChangeBlock: PropTypes.func.isRequired,
|
72
|
+
onSelectItem: PropTypes.func,
|
73
|
+
dataName: PropTypes.string,
|
74
|
+
maximumSelectionSize: PropTypes.number,
|
75
|
+
contextURL: PropTypes.string,
|
76
|
+
searchableTypes: PropTypes.arrayOf(PropTypes.string),
|
77
|
+
};
|
78
|
+
|
79
|
+
/**
|
80
|
+
* Default properties.
|
81
|
+
* @property {Object} defaultProps Default properties.
|
82
|
+
* @static
|
83
|
+
*/
|
84
|
+
static defaultProps = {
|
85
|
+
image: '',
|
86
|
+
href: '',
|
87
|
+
onSelectItem: null,
|
88
|
+
dataName: null,
|
89
|
+
selectableTypes: [],
|
90
|
+
searchableTypes: null,
|
91
|
+
maximumSelectionSize: null,
|
92
|
+
};
|
93
|
+
|
94
|
+
/**
|
95
|
+
* Constructor
|
96
|
+
* @method constructor
|
97
|
+
* @param {Object} props Component properties
|
98
|
+
* @constructs WysiwygEditor
|
99
|
+
*/
|
100
|
+
constructor(props) {
|
101
|
+
super(props);
|
102
|
+
this.state = {
|
103
|
+
currentFolder:
|
104
|
+
this.props.mode === 'multiple' ? '/' : this.props.contextURL || '/',
|
105
|
+
currentImageFolder:
|
106
|
+
this.props.mode === 'multiple'
|
107
|
+
? '/'
|
108
|
+
: this.props.mode === 'image' && this.props.data?.url
|
109
|
+
? getParentURL(this.props.data.url)
|
110
|
+
: '/',
|
111
|
+
currentLinkFolder:
|
112
|
+
this.props.mode === 'multiple'
|
113
|
+
? '/'
|
114
|
+
: this.props.mode === 'link' && this.props.data?.href
|
115
|
+
? getParentURL(this.props.data.href)
|
116
|
+
: '/',
|
117
|
+
parentFolder: '',
|
118
|
+
selectedImage:
|
119
|
+
this.props.mode === 'multiple'
|
120
|
+
? ''
|
121
|
+
: this.props.mode === 'image' && this.props.data?.url
|
122
|
+
? flattenToAppURL(this.props.data.url)
|
123
|
+
: '',
|
124
|
+
selectedHref:
|
125
|
+
this.props.mode === 'multiple'
|
126
|
+
? ''
|
127
|
+
: this.props.mode === 'link' && this.props.data?.href
|
128
|
+
? flattenToAppURL(this.props.data.href)
|
129
|
+
: '',
|
130
|
+
showSearchInput: false,
|
131
|
+
// In image mode, the searchable types default to the image types which
|
132
|
+
// can be overridden with the property if specified.
|
133
|
+
searchableTypes:
|
134
|
+
this.props.mode === 'image'
|
135
|
+
? this.props.searchableTypes || config.settings.imageObjects
|
136
|
+
: this.props.searchableTypes,
|
137
|
+
};
|
138
|
+
this.searchInputRef = React.createRef();
|
139
|
+
}
|
140
|
+
|
141
|
+
/**
|
142
|
+
* Component did mount
|
143
|
+
* @method componentDidMount
|
144
|
+
* @returns {undefined}
|
145
|
+
*/
|
146
|
+
componentDidMount() {
|
147
|
+
this.initialSearch(this.props.mode);
|
148
|
+
}
|
149
|
+
|
150
|
+
initialSearch = (mode) => {
|
151
|
+
const currentSelected =
|
152
|
+
mode === 'multiple'
|
153
|
+
? ''
|
154
|
+
: mode === 'image'
|
155
|
+
? this.state.selectedImage
|
156
|
+
: this.state.selectedHref;
|
157
|
+
if (currentSelected && isInternalURL(currentSelected)) {
|
158
|
+
this.props.searchContent(
|
159
|
+
getParentURL(currentSelected),
|
160
|
+
{
|
161
|
+
'path.depth': 1,
|
162
|
+
sort_on: 'getObjPositionInParent',
|
163
|
+
metadata_fields: '_all',
|
164
|
+
b_size: 1000,
|
165
|
+
},
|
166
|
+
`${this.props.block}-${mode}`,
|
167
|
+
);
|
168
|
+
} else {
|
169
|
+
this.props.searchContent(
|
170
|
+
this.state.currentFolder,
|
171
|
+
{
|
172
|
+
'path.depth': 1,
|
173
|
+
sort_on: 'getObjPositionInParent',
|
174
|
+
metadata_fields: '_all',
|
175
|
+
b_size: 1000,
|
176
|
+
},
|
177
|
+
`${this.props.block}-${mode}`,
|
178
|
+
);
|
179
|
+
}
|
180
|
+
};
|
181
|
+
|
182
|
+
navigateTo = (id) => {
|
183
|
+
this.props.searchContent(
|
184
|
+
id,
|
185
|
+
{
|
186
|
+
'path.depth': 1,
|
187
|
+
sort_on: 'getObjPositionInParent',
|
188
|
+
metadata_fields: '_all',
|
189
|
+
b_size: 1000,
|
190
|
+
},
|
191
|
+
`${this.props.block}-${this.props.mode}`,
|
192
|
+
);
|
193
|
+
const parent = `${join(id.split('/').slice(0, -1), '/')}` || '/';
|
194
|
+
this.setState(() => ({
|
195
|
+
parentFolder: parent,
|
196
|
+
currentFolder: id || '/',
|
197
|
+
}));
|
198
|
+
};
|
199
|
+
|
200
|
+
toggleSearchInput = () =>
|
201
|
+
this.setState(
|
202
|
+
(prevState) => ({
|
203
|
+
showSearchInput: !prevState.showSearchInput,
|
204
|
+
}),
|
205
|
+
() => {
|
206
|
+
if (this.searchInputRef?.current) this.searchInputRef.current.focus();
|
207
|
+
},
|
208
|
+
);
|
209
|
+
|
210
|
+
onSearch = (e) => {
|
211
|
+
const text = flattenToAppURL(e.target.value);
|
212
|
+
if (text.startsWith('/')) {
|
213
|
+
this.setState({ currentFolder: text });
|
214
|
+
this.props.searchContent(
|
215
|
+
text,
|
216
|
+
{
|
217
|
+
'path.depth': 1,
|
218
|
+
sort_on: 'getObjPositionInParent',
|
219
|
+
metadata_fields: '_all',
|
220
|
+
portal_type: this.state.searchableTypes,
|
221
|
+
},
|
222
|
+
`${this.props.block}-${this.props.mode}`,
|
223
|
+
);
|
224
|
+
} else {
|
225
|
+
text.length > 2
|
226
|
+
? this.props.searchContent(
|
227
|
+
'/',
|
228
|
+
{
|
229
|
+
SearchableText: `${text}*`,
|
230
|
+
metadata_fields: '_all',
|
231
|
+
portal_type: this.state.searchableTypes,
|
232
|
+
},
|
233
|
+
`${this.props.block}-${this.props.mode}`,
|
234
|
+
)
|
235
|
+
: this.props.searchContent(
|
236
|
+
'/',
|
237
|
+
{
|
238
|
+
'path.depth': 1,
|
239
|
+
sort_on: 'getObjPositionInParent',
|
240
|
+
metadata_fields: '_all',
|
241
|
+
portal_type: this.state.searchableTypes,
|
242
|
+
},
|
243
|
+
`${this.props.block}-${this.props.mode}`,
|
244
|
+
);
|
245
|
+
}
|
246
|
+
};
|
247
|
+
|
248
|
+
onSelectItem = (item) => {
|
249
|
+
const url = item['@id'];
|
250
|
+
const { block, data, mode, dataName, onChangeBlock } = this.props;
|
251
|
+
|
252
|
+
const updateState = (mode) => {
|
253
|
+
switch (mode) {
|
254
|
+
case 'image':
|
255
|
+
this.setState({
|
256
|
+
selectedImage: url,
|
257
|
+
currentImageFolder: getParentURL(url),
|
258
|
+
});
|
259
|
+
break;
|
260
|
+
case 'link':
|
261
|
+
this.setState({
|
262
|
+
selectedHref: url,
|
263
|
+
currentLinkFolder: getParentURL(url),
|
264
|
+
});
|
265
|
+
break;
|
266
|
+
default:
|
267
|
+
break;
|
268
|
+
}
|
269
|
+
};
|
270
|
+
|
271
|
+
if (dataName) {
|
272
|
+
onChangeBlock(block, {
|
273
|
+
...data,
|
274
|
+
[dataName]: url,
|
275
|
+
});
|
276
|
+
} else if (this.props.onSelectItem) {
|
277
|
+
this.props.onSelectItem(url, item);
|
278
|
+
} else if (mode === 'image') {
|
279
|
+
onChangeBlock(block, {
|
280
|
+
...data,
|
281
|
+
url: flattenToAppURL(item.getURL),
|
282
|
+
alt: item.title || item.description || '',
|
283
|
+
copyright: item.rights || '',
|
284
|
+
});
|
285
|
+
} else if (mode === 'link') {
|
286
|
+
onChangeBlock(block, {
|
287
|
+
...data,
|
288
|
+
href: flattenToAppURL(url),
|
289
|
+
});
|
290
|
+
}
|
291
|
+
updateState(mode);
|
292
|
+
};
|
293
|
+
|
294
|
+
onChangeBlockData = (key, value) => {
|
295
|
+
this.props.onChangeBlock(this.props.block, {
|
296
|
+
...this.props.data,
|
297
|
+
[key]: value,
|
298
|
+
});
|
299
|
+
};
|
300
|
+
|
301
|
+
isSelectable = (item) => {
|
302
|
+
return this.props.selectableTypes.length > 0
|
303
|
+
? this.props.selectableTypes.indexOf(item['@type']) >= 0
|
304
|
+
: true;
|
305
|
+
};
|
306
|
+
|
307
|
+
handleClickOnItem = (item) => {
|
308
|
+
if (this.props.mode === 'image') {
|
309
|
+
if (item.is_folderish) {
|
310
|
+
this.navigateTo(item['@id']);
|
311
|
+
}
|
312
|
+
if (config.settings.imageObjects.includes(item['@type'])) {
|
313
|
+
this.onSelectItem(item);
|
314
|
+
}
|
315
|
+
} else {
|
316
|
+
if (this.isSelectable(item)) {
|
317
|
+
if (
|
318
|
+
!this.props.maximumSelectionSize ||
|
319
|
+
this.props.mode === 'multiple' ||
|
320
|
+
!this.props.data ||
|
321
|
+
this.props.data.length < this.props.maximumSelectionSize
|
322
|
+
) {
|
323
|
+
this.onSelectItem(item);
|
324
|
+
let length = this.props.data ? this.props.data.length : 0;
|
325
|
+
|
326
|
+
let stopSelecting =
|
327
|
+
this.props.mode !== 'multiple' ||
|
328
|
+
(this.props.maximumSelectionSize > 0 &&
|
329
|
+
length + 1 >= this.props.maximumSelectionSize);
|
330
|
+
|
331
|
+
if (stopSelecting) {
|
332
|
+
this.props.closeObjectBrowser();
|
333
|
+
}
|
334
|
+
} else {
|
335
|
+
this.props.closeObjectBrowser();
|
336
|
+
}
|
337
|
+
} else {
|
338
|
+
this.navigateTo(item['@id']);
|
339
|
+
}
|
340
|
+
}
|
341
|
+
};
|
342
|
+
|
343
|
+
handleDoubleClickOnItem = (item) => {
|
344
|
+
if (this.props.mode === 'image') {
|
345
|
+
if (item.is_folderish) {
|
346
|
+
this.navigateTo(item['@id']);
|
347
|
+
}
|
348
|
+
if (config.settings.imageObjects.includes(item['@type'])) {
|
349
|
+
this.onSelectItem(item);
|
350
|
+
this.props.closeObjectBrowser();
|
351
|
+
}
|
352
|
+
} else {
|
353
|
+
if (this.isSelectable(item)) {
|
354
|
+
if (this.props.data.length < this.props.maximumSelectionSize) {
|
355
|
+
this.onSelectItem(item);
|
356
|
+
}
|
357
|
+
this.props.closeObjectBrowser();
|
358
|
+
} else {
|
359
|
+
this.navigateTo(item['@id']);
|
360
|
+
}
|
361
|
+
}
|
362
|
+
};
|
363
|
+
|
364
|
+
/**
|
365
|
+
* Render method.
|
366
|
+
* @method render
|
367
|
+
* @returns {string} Markup for the component.
|
368
|
+
*/
|
369
|
+
render() {
|
370
|
+
return (
|
371
|
+
<Segment.Group raised>
|
372
|
+
<header className="header pulled">
|
373
|
+
<div className="vertical divider" />
|
374
|
+
{this.state.currentFolder === '/' ? (
|
375
|
+
<>
|
376
|
+
{this.props.mode === 'image' ? (
|
377
|
+
<Icon name={folderSVG} size="24px" />
|
378
|
+
) : (
|
379
|
+
<Icon name={linkSVG} size="24px" />
|
380
|
+
)}
|
381
|
+
</>
|
382
|
+
) : (
|
383
|
+
<button
|
384
|
+
aria-label={this.props.intl.formatMessage(messages.back)}
|
385
|
+
onClick={() => this.navigateTo(this.state.parentFolder)}
|
386
|
+
>
|
387
|
+
<Icon name={backSVG} size="24px" />
|
388
|
+
</button>
|
389
|
+
)}
|
390
|
+
{this.state.showSearchInput ? (
|
391
|
+
<Input
|
392
|
+
className="search"
|
393
|
+
ref={this.searchInputRef}
|
394
|
+
onChange={this.onSearch}
|
395
|
+
placeholder={this.props.intl.formatMessage(
|
396
|
+
messages.SearchInputPlaceholder,
|
397
|
+
)}
|
398
|
+
/>
|
399
|
+
) : this.props.mode === 'image' ? (
|
400
|
+
<h2>
|
401
|
+
<FormattedMessage
|
402
|
+
id="Choose Image"
|
403
|
+
defaultMessage="Choose Image"
|
404
|
+
/>
|
405
|
+
</h2>
|
406
|
+
) : (
|
407
|
+
<h2>
|
408
|
+
<FormattedMessage
|
409
|
+
id="Choose Target"
|
410
|
+
defaultMessage="Choose Target"
|
411
|
+
/>
|
412
|
+
</h2>
|
413
|
+
)}
|
414
|
+
|
415
|
+
<button
|
416
|
+
aria-label={this.props.intl.formatMessage(messages.search)}
|
417
|
+
onClick={this.toggleSearchInput}
|
418
|
+
>
|
419
|
+
<Icon name={searchSVG} size="24px" />
|
420
|
+
</button>
|
421
|
+
<button className="clearSVG" onClick={this.props.closeObjectBrowser}>
|
422
|
+
<Icon name={clearSVG} size="24px" />
|
423
|
+
</button>
|
424
|
+
</header>
|
425
|
+
<Segment secondary className="breadcrumbs" vertical>
|
426
|
+
<Breadcrumb>
|
427
|
+
{this.state.currentFolder !== '/' ? (
|
428
|
+
this.state.currentFolder.split('/').map((item, index, items) => {
|
429
|
+
return (
|
430
|
+
<React.Fragment key={`divider-${item}-${index}`}>
|
431
|
+
{index === 0 ? (
|
432
|
+
<Breadcrumb.Section onClick={() => this.navigateTo('/')}>
|
433
|
+
<Icon
|
434
|
+
className="home-icon"
|
435
|
+
name={homeSVG}
|
436
|
+
size="18px"
|
437
|
+
/>
|
438
|
+
</Breadcrumb.Section>
|
439
|
+
) : (
|
440
|
+
<>
|
441
|
+
<Breadcrumb.Divider key={`divider-${item.url}`} />
|
442
|
+
<Breadcrumb.Section
|
443
|
+
onClick={() =>
|
444
|
+
this.navigateTo(items.slice(0, index + 1).join('/'))
|
445
|
+
}
|
446
|
+
>
|
447
|
+
{item}
|
448
|
+
</Breadcrumb.Section>
|
449
|
+
</>
|
450
|
+
)}
|
451
|
+
</React.Fragment>
|
452
|
+
);
|
453
|
+
})
|
454
|
+
) : (
|
455
|
+
<Breadcrumb.Section onClick={() => this.navigateTo('/')}>
|
456
|
+
<Icon className="home-icon" name={homeSVG} size="18px" />
|
457
|
+
</Breadcrumb.Section>
|
458
|
+
)}
|
459
|
+
</Breadcrumb>
|
460
|
+
</Segment>
|
461
|
+
{this.props.mode === 'multiple' && (
|
462
|
+
<Segment className="infos">
|
463
|
+
{this.props.intl.formatMessage(messages.SelectedItems)}:{' '}
|
464
|
+
{this.props.data?.length}
|
465
|
+
{this.props.maximumSelectionSize > 0 && (
|
466
|
+
<>
|
467
|
+
{' '}
|
468
|
+
{this.props.intl.formatMessage(messages.of)}{' '}
|
469
|
+
{this.props.maximumSelectionSize}
|
470
|
+
</>
|
471
|
+
)}
|
472
|
+
</Segment>
|
473
|
+
)}
|
474
|
+
<ObjectBrowserNav
|
475
|
+
currentSearchResults={
|
476
|
+
this.props.searchSubrequests[
|
477
|
+
`${this.props.block}-${this.props.mode}`
|
478
|
+
]
|
479
|
+
}
|
480
|
+
selected={
|
481
|
+
this.props.mode === 'multiple'
|
482
|
+
? this.props.data
|
483
|
+
: [
|
484
|
+
{
|
485
|
+
'@id':
|
486
|
+
this.props.mode === 'image'
|
487
|
+
? this.state.selectedImage
|
488
|
+
: this.state.selectedHref,
|
489
|
+
},
|
490
|
+
]
|
491
|
+
}
|
492
|
+
handleClickOnItem={this.handleClickOnItem}
|
493
|
+
handleDoubleClickOnItem={this.handleDoubleClickOnItem}
|
494
|
+
mode={this.props.mode}
|
495
|
+
navigateTo={this.navigateTo}
|
496
|
+
isSelectable={this.isSelectable}
|
497
|
+
/>
|
498
|
+
</Segment.Group>
|
499
|
+
);
|
500
|
+
}
|
501
|
+
}
|
502
|
+
|
503
|
+
export default compose(
|
504
|
+
injectIntl,
|
505
|
+
connect(
|
506
|
+
(state) => ({
|
507
|
+
searchSubrequests: state.search.subrequests,
|
508
|
+
}),
|
509
|
+
{ searchContent },
|
510
|
+
),
|
511
|
+
)(ObjectBrowserBody);
|