@eeacms/volto-eea-website-theme 3.7.0 → 3.9.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 +19 -2
- package/package.json +3 -1
- package/src/actions/index.js +1 -0
- package/src/actions/navigation.js +24 -0
- package/src/actions/print.js +9 -1
- package/src/components/manage/Blocks/ContextNavigation/variations/Accordion.jsx +42 -35
- package/src/components/manage/Blocks/LayoutSettings/LayoutSettingsEdit.test.jsx +383 -0
- package/src/components/manage/Blocks/Title/variations/WebReportPage.test.jsx +232 -0
- package/src/components/theme/Banner/View.jsx +11 -92
- package/src/components/theme/PrintLoader/PrintLoader.jsx +56 -0
- package/src/components/theme/PrintLoader/PrintLoader.test.jsx +91 -0
- package/src/components/theme/PrintLoader/style.less +12 -0
- package/src/components/theme/WebReport/WebReportSectionView.test.jsx +462 -0
- package/src/components/theme/Widgets/ImageViewWidget.test.jsx +26 -0
- package/src/components/theme/Widgets/NavigationBehaviorWidget.jsx +601 -0
- package/src/components/theme/Widgets/NavigationBehaviorWidget.test.jsx +507 -0
- package/src/components/theme/Widgets/SimpleArrayWidget.jsx +183 -0
- package/src/components/theme/Widgets/SimpleArrayWidget.test.jsx +283 -0
- package/src/constants/ActionTypes.js +2 -0
- package/src/customizations/volto/components/manage/History/History.diff +207 -0
- package/src/customizations/volto/components/manage/History/History.jsx +444 -0
- package/src/customizations/volto/components/theme/Comments/Comments.jsx +9 -2
- package/src/customizations/volto/components/theme/Comments/Comments.test.jsx +4 -4
- package/src/customizations/volto/components/theme/Comments/comments.less +16 -0
- package/src/customizations/volto/components/theme/Header/Header.jsx +60 -1
- package/src/customizations/volto/components/theme/View/DefaultView.jsx +42 -33
- package/src/customizations/volto/helpers/Html/Html.jsx +212 -0
- package/src/customizations/volto/helpers/Html/Readme.md +1 -0
- package/src/customizations/volto/server.jsx +375 -0
- package/src/helpers/loadLazyImages.js +11 -0
- package/src/helpers/loadLazyImages.test.js +22 -0
- package/src/helpers/setupPrintView.js +134 -0
- package/src/helpers/setupPrintView.test.js +49 -0
- package/src/index.js +11 -1
- package/src/index.test.js +6 -0
- package/src/middleware/voltoCustom.test.js +282 -0
- package/src/reducers/index.js +2 -1
- package/src/reducers/navigation/navigation.js +47 -0
- package/src/reducers/navigation/navigation.test.js +348 -0
- package/src/reducers/navigation.js +55 -0
- package/src/reducers/print.js +18 -8
- package/src/reducers/print.test.js +117 -0
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* History component.
|
|
3
|
+
* @module components/manage/History/History
|
|
4
|
+
* Customized to enable complex versioning
|
|
5
|
+
* https://taskman.eionet.europa.eu/issues/289335?issue_count=4&issue_position=1&next_issue_id=285635
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React, { Component } from 'react';
|
|
9
|
+
import PropTypes from 'prop-types';
|
|
10
|
+
import Helmet from '@plone/volto/helpers/Helmet/Helmet';
|
|
11
|
+
import { Link } from 'react-router-dom';
|
|
12
|
+
import { connect } from 'react-redux';
|
|
13
|
+
import { compose } from 'redux';
|
|
14
|
+
import {
|
|
15
|
+
Container as SemanticContainer,
|
|
16
|
+
Dropdown,
|
|
17
|
+
Icon,
|
|
18
|
+
Message,
|
|
19
|
+
Segment,
|
|
20
|
+
Table,
|
|
21
|
+
} from 'semantic-ui-react';
|
|
22
|
+
import concat from 'lodash/concat';
|
|
23
|
+
import map from 'lodash/map';
|
|
24
|
+
import reverse from 'lodash/reverse';
|
|
25
|
+
import find from 'lodash/find';
|
|
26
|
+
import { createPortal } from 'react-dom';
|
|
27
|
+
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
|
|
28
|
+
import { asyncConnect } from '@plone/volto/helpers/AsyncConnect';
|
|
29
|
+
|
|
30
|
+
import FormattedDate from '@plone/volto/components/theme/FormattedDate/FormattedDate';
|
|
31
|
+
import IconNext from '@plone/volto/components/theme/Icon/Icon';
|
|
32
|
+
import Toolbar from '@plone/volto/components/manage/Toolbar/Toolbar';
|
|
33
|
+
import Forbidden from '@plone/volto/components/theme/Forbidden/Forbidden';
|
|
34
|
+
import Unauthorized from '@plone/volto/components/theme/Unauthorized/Unauthorized';
|
|
35
|
+
import {
|
|
36
|
+
getHistory,
|
|
37
|
+
revertHistory,
|
|
38
|
+
} from '@plone/volto/actions/history/history';
|
|
39
|
+
import { listActions } from '@plone/volto/actions/actions/actions';
|
|
40
|
+
import { getBaseUrl } from '@plone/volto/helpers/Url/Url';
|
|
41
|
+
import config from '@plone/volto/registry';
|
|
42
|
+
|
|
43
|
+
import backSVG from '@plone/volto/icons/back.svg';
|
|
44
|
+
|
|
45
|
+
const messages = defineMessages({
|
|
46
|
+
back: {
|
|
47
|
+
id: 'Back',
|
|
48
|
+
defaultMessage: 'Back',
|
|
49
|
+
},
|
|
50
|
+
history: {
|
|
51
|
+
id: 'History',
|
|
52
|
+
defaultMessage: 'History',
|
|
53
|
+
},
|
|
54
|
+
newerVersionAvailable: {
|
|
55
|
+
id: 'Newer version available',
|
|
56
|
+
defaultMessage: 'Newer version available',
|
|
57
|
+
},
|
|
58
|
+
thereIsNewerVersionAt: {
|
|
59
|
+
id: 'There is a newer version at {link}',
|
|
60
|
+
defaultMessage: 'There is a newer version at {link}',
|
|
61
|
+
},
|
|
62
|
+
olderVersionAvailable: {
|
|
63
|
+
id: 'Older version available',
|
|
64
|
+
defaultMessage: 'Older version available',
|
|
65
|
+
},
|
|
66
|
+
thereIsOlderVersionAt: {
|
|
67
|
+
id: 'There is an older version at {link}',
|
|
68
|
+
defaultMessage: 'There is an older version at {link}',
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* History class.
|
|
74
|
+
* @class History
|
|
75
|
+
* @extends Component
|
|
76
|
+
*/
|
|
77
|
+
class History extends Component {
|
|
78
|
+
/**
|
|
79
|
+
* Property types.
|
|
80
|
+
* @property {Object} propTypes Property types.
|
|
81
|
+
* @static
|
|
82
|
+
*/
|
|
83
|
+
static propTypes = {
|
|
84
|
+
getHistory: PropTypes.func.isRequired,
|
|
85
|
+
revertHistory: PropTypes.func.isRequired,
|
|
86
|
+
revertRequest: PropTypes.shape({
|
|
87
|
+
loaded: PropTypes.bool,
|
|
88
|
+
loading: PropTypes.bool,
|
|
89
|
+
}).isRequired,
|
|
90
|
+
pathname: PropTypes.string.isRequired,
|
|
91
|
+
entries: PropTypes.arrayOf(
|
|
92
|
+
PropTypes.shape({
|
|
93
|
+
transition_title: PropTypes.string,
|
|
94
|
+
type: PropTypes.string,
|
|
95
|
+
action: PropTypes.string,
|
|
96
|
+
state_title: PropTypes.string,
|
|
97
|
+
time: PropTypes.string,
|
|
98
|
+
comments: PropTypes.string,
|
|
99
|
+
actor: PropTypes.shape({ fullname: PropTypes.string }),
|
|
100
|
+
}),
|
|
101
|
+
).isRequired,
|
|
102
|
+
title: PropTypes.string.isRequired,
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Constructor
|
|
107
|
+
* @method constructor
|
|
108
|
+
* @param {Object} props Component properties
|
|
109
|
+
* @constructs Workflow
|
|
110
|
+
*/
|
|
111
|
+
constructor(props) {
|
|
112
|
+
super(props);
|
|
113
|
+
this.onRevert = this.onRevert.bind(this);
|
|
114
|
+
this.state = { isClient: false };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Component did mount
|
|
119
|
+
* @method componentDidMount
|
|
120
|
+
* @returns {undefined}
|
|
121
|
+
*/
|
|
122
|
+
componentDidMount() {
|
|
123
|
+
this.props.getHistory(getBaseUrl(this.props.pathname));
|
|
124
|
+
this.setState({ isClient: true });
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Component will receive props
|
|
129
|
+
* @method componentWillReceiveProps
|
|
130
|
+
* @param {Object} nextProps Next properties
|
|
131
|
+
* @returns {undefined}
|
|
132
|
+
*/
|
|
133
|
+
UNSAFE_componentWillReceiveProps(nextProps) {
|
|
134
|
+
if (this.props.revertRequest.loading && nextProps.revertRequest.loaded) {
|
|
135
|
+
this.props.getHistory(getBaseUrl(this.props.pathname));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* On revert
|
|
141
|
+
* @method onRevert
|
|
142
|
+
* @param {object} event Event object
|
|
143
|
+
* @param {number} value Value
|
|
144
|
+
* @returns {undefined}
|
|
145
|
+
*/
|
|
146
|
+
onRevert(event, { value }) {
|
|
147
|
+
this.props.revertHistory(getBaseUrl(this.props.pathname), value);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
processHistoryEntries = () => {
|
|
151
|
+
// Getting the history entries from the props
|
|
152
|
+
// No clue why the reverse(concat()) is necessary
|
|
153
|
+
const entries = reverse(concat(this.props.entries));
|
|
154
|
+
let title = entries.length > 0 ? entries[0].state_title : '';
|
|
155
|
+
for (let x = 1; x < entries.length; x += 1) {
|
|
156
|
+
entries[x].prev_state_title = title;
|
|
157
|
+
title = entries[x].state_title || title;
|
|
158
|
+
}
|
|
159
|
+
// We reverse them again
|
|
160
|
+
reverse(entries);
|
|
161
|
+
|
|
162
|
+
// We identify the latest 'versioning' entry and mark it
|
|
163
|
+
const current_version = find(entries, (item) => item.type === 'versioning');
|
|
164
|
+
if (current_version) {
|
|
165
|
+
current_version.is_current = true;
|
|
166
|
+
}
|
|
167
|
+
return entries;
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Render method.
|
|
172
|
+
* @method render
|
|
173
|
+
* @returns {string} Markup for the component.
|
|
174
|
+
*/
|
|
175
|
+
render() {
|
|
176
|
+
const historyAction = find(this.props.objectActions, {
|
|
177
|
+
id: 'history',
|
|
178
|
+
});
|
|
179
|
+
const entries = this.processHistoryEntries();
|
|
180
|
+
|
|
181
|
+
const Container =
|
|
182
|
+
config.getComponent({ name: 'Container' }).component || SemanticContainer;
|
|
183
|
+
return !historyAction ? (
|
|
184
|
+
<>
|
|
185
|
+
{this.props.token ? (
|
|
186
|
+
<Forbidden
|
|
187
|
+
pathname={this.props.pathname}
|
|
188
|
+
staticContext={this.props.staticContext}
|
|
189
|
+
/>
|
|
190
|
+
) : (
|
|
191
|
+
<Unauthorized
|
|
192
|
+
pathname={this.props.pathname}
|
|
193
|
+
staticContext={this.props.staticContext}
|
|
194
|
+
/>
|
|
195
|
+
)}
|
|
196
|
+
</>
|
|
197
|
+
) : (
|
|
198
|
+
<Container id="page-history">
|
|
199
|
+
<Helmet title={this.props.intl.formatMessage(messages.history)} />
|
|
200
|
+
<Segment.Group raised>
|
|
201
|
+
<Segment className="primary">
|
|
202
|
+
<FormattedMessage
|
|
203
|
+
id="History of {title}"
|
|
204
|
+
defaultMessage="History of {title}"
|
|
205
|
+
values={{
|
|
206
|
+
title: <q>{this.props.title}</q>,
|
|
207
|
+
}}
|
|
208
|
+
/>
|
|
209
|
+
</Segment>
|
|
210
|
+
<Segment secondary>
|
|
211
|
+
<FormattedMessage
|
|
212
|
+
id="You can view the history of your item below."
|
|
213
|
+
defaultMessage="You can view the history of your item below."
|
|
214
|
+
/>
|
|
215
|
+
</Segment>
|
|
216
|
+
{this.props.content?.copied_to && (
|
|
217
|
+
<Message info icon attached="top">
|
|
218
|
+
<Icon name="arrow right" />
|
|
219
|
+
<Message.Content>
|
|
220
|
+
<Message.Header>
|
|
221
|
+
<FormattedMessage {...messages.newerVersionAvailable} />
|
|
222
|
+
</Message.Header>
|
|
223
|
+
<FormattedMessage
|
|
224
|
+
{...messages.thereIsNewerVersionAt}
|
|
225
|
+
values={{
|
|
226
|
+
link: (
|
|
227
|
+
<Link to={new URL(this.props.content.copied_to).pathname}>
|
|
228
|
+
{new URL(this.props.content.copied_to).pathname
|
|
229
|
+
.split('/')
|
|
230
|
+
.pop() || 'newer version'}
|
|
231
|
+
</Link>
|
|
232
|
+
),
|
|
233
|
+
}}
|
|
234
|
+
/>
|
|
235
|
+
</Message.Content>
|
|
236
|
+
</Message>
|
|
237
|
+
)}
|
|
238
|
+
<Table
|
|
239
|
+
selectable
|
|
240
|
+
compact
|
|
241
|
+
attached
|
|
242
|
+
style={{
|
|
243
|
+
tableLayout: 'fixed',
|
|
244
|
+
width: '100%',
|
|
245
|
+
wordWrap: 'break-word',
|
|
246
|
+
}}
|
|
247
|
+
>
|
|
248
|
+
<Table.Header>
|
|
249
|
+
<Table.Row>
|
|
250
|
+
<Table.HeaderCell style={{ width: '5%' }}>
|
|
251
|
+
<FormattedMessage
|
|
252
|
+
id="History Version Number"
|
|
253
|
+
defaultMessage="#"
|
|
254
|
+
/>
|
|
255
|
+
</Table.HeaderCell>
|
|
256
|
+
<Table.HeaderCell style={{ width: '25%' }}>
|
|
257
|
+
<FormattedMessage id="What" defaultMessage="What" />
|
|
258
|
+
</Table.HeaderCell>
|
|
259
|
+
<Table.HeaderCell style={{ width: '15%' }}>
|
|
260
|
+
<FormattedMessage id="Who" defaultMessage="Who" />
|
|
261
|
+
</Table.HeaderCell>
|
|
262
|
+
<Table.HeaderCell style={{ width: '15%' }}>
|
|
263
|
+
<FormattedMessage id="When" defaultMessage="When" />
|
|
264
|
+
</Table.HeaderCell>
|
|
265
|
+
<Table.HeaderCell style={{ width: '25%' }}>
|
|
266
|
+
<FormattedMessage
|
|
267
|
+
id="Change Note"
|
|
268
|
+
defaultMessage="Change Note"
|
|
269
|
+
/>
|
|
270
|
+
</Table.HeaderCell>
|
|
271
|
+
<Table.HeaderCell style={{ width: '15%' }} />
|
|
272
|
+
</Table.Row>
|
|
273
|
+
</Table.Header>
|
|
274
|
+
<Table.Body>
|
|
275
|
+
{map(entries, (entry) => (
|
|
276
|
+
<Table.Row key={entry.time}>
|
|
277
|
+
<Table.Cell>
|
|
278
|
+
{('version' in entry && entry.version > 0 && (
|
|
279
|
+
<Link
|
|
280
|
+
className="item"
|
|
281
|
+
to={`${getBaseUrl(this.props.pathname)}/diff?one=${
|
|
282
|
+
entry.version - 1
|
|
283
|
+
}&two=${entry.version}`}
|
|
284
|
+
>
|
|
285
|
+
{entry.version}
|
|
286
|
+
</Link>
|
|
287
|
+
)) || <span>{entry.version}</span>}
|
|
288
|
+
</Table.Cell>
|
|
289
|
+
<Table.Cell>
|
|
290
|
+
{('version' in entry && entry.version > 0 && (
|
|
291
|
+
<Link
|
|
292
|
+
className="item"
|
|
293
|
+
to={`${getBaseUrl(this.props.pathname)}/diff?one=${
|
|
294
|
+
entry.version - 1
|
|
295
|
+
}&two=${entry.version}`}
|
|
296
|
+
>
|
|
297
|
+
{entry.transition_title}
|
|
298
|
+
</Link>
|
|
299
|
+
)) || (
|
|
300
|
+
<span>
|
|
301
|
+
{entry.transition_title}
|
|
302
|
+
{entry.type === 'workflow' &&
|
|
303
|
+
` (${
|
|
304
|
+
entry.action ? `${entry.prev_state_title} → ` : ''
|
|
305
|
+
}${entry.state_title})`}
|
|
306
|
+
</span>
|
|
307
|
+
)}
|
|
308
|
+
</Table.Cell>
|
|
309
|
+
<Table.Cell>{entry.actor.fullname}</Table.Cell>
|
|
310
|
+
<Table.Cell>
|
|
311
|
+
<FormattedDate date={entry.time} />
|
|
312
|
+
</Table.Cell>
|
|
313
|
+
<Table.Cell>{entry.comments}</Table.Cell>
|
|
314
|
+
<Table.Cell>
|
|
315
|
+
{entry.type === 'versioning' && (
|
|
316
|
+
<Dropdown icon="ellipsis horizontal">
|
|
317
|
+
<Dropdown.Menu className="left">
|
|
318
|
+
{'version' in entry && entry.version > 0 && (
|
|
319
|
+
<Link
|
|
320
|
+
className="item"
|
|
321
|
+
to={`${getBaseUrl(
|
|
322
|
+
this.props.pathname,
|
|
323
|
+
)}/diff?one=${entry.version - 1}&two=${
|
|
324
|
+
entry.version
|
|
325
|
+
}`}
|
|
326
|
+
>
|
|
327
|
+
<Icon name="copy" />{' '}
|
|
328
|
+
<FormattedMessage
|
|
329
|
+
id="View changes"
|
|
330
|
+
defaultMessage="View changes"
|
|
331
|
+
/>
|
|
332
|
+
</Link>
|
|
333
|
+
)}
|
|
334
|
+
{'version' in entry && (
|
|
335
|
+
<Link
|
|
336
|
+
className="item"
|
|
337
|
+
to={`${getBaseUrl(this.props.pathname)}?version=${
|
|
338
|
+
entry.version
|
|
339
|
+
}`}
|
|
340
|
+
>
|
|
341
|
+
<Icon name="eye" />{' '}
|
|
342
|
+
<FormattedMessage
|
|
343
|
+
id="View this revision"
|
|
344
|
+
defaultMessage="View this revision"
|
|
345
|
+
/>
|
|
346
|
+
</Link>
|
|
347
|
+
)}
|
|
348
|
+
{'version' in entry &&
|
|
349
|
+
entry.may_revert &&
|
|
350
|
+
!entry.is_current && (
|
|
351
|
+
<Dropdown.Item
|
|
352
|
+
value={entry.version}
|
|
353
|
+
onClick={this.onRevert}
|
|
354
|
+
>
|
|
355
|
+
<Icon name="undo" />{' '}
|
|
356
|
+
<FormattedMessage
|
|
357
|
+
id="Revert to this revision"
|
|
358
|
+
defaultMessage="Revert to this revision"
|
|
359
|
+
/>
|
|
360
|
+
</Dropdown.Item>
|
|
361
|
+
)}
|
|
362
|
+
</Dropdown.Menu>
|
|
363
|
+
</Dropdown>
|
|
364
|
+
)}
|
|
365
|
+
</Table.Cell>
|
|
366
|
+
</Table.Row>
|
|
367
|
+
))}
|
|
368
|
+
</Table.Body>
|
|
369
|
+
</Table>
|
|
370
|
+
{this.props.content?.copied_from && (
|
|
371
|
+
<Message warning icon attached="bottom">
|
|
372
|
+
<Icon name="arrow left" />
|
|
373
|
+
<Message.Content>
|
|
374
|
+
<Message.Header>
|
|
375
|
+
<FormattedMessage {...messages.olderVersionAvailable} />
|
|
376
|
+
</Message.Header>
|
|
377
|
+
<FormattedMessage
|
|
378
|
+
{...messages.thereIsOlderVersionAt}
|
|
379
|
+
values={{
|
|
380
|
+
link: (
|
|
381
|
+
<Link
|
|
382
|
+
to={new URL(this.props.content.copied_from).pathname}
|
|
383
|
+
>
|
|
384
|
+
{new URL(this.props.content.copied_from).pathname
|
|
385
|
+
.split('/')
|
|
386
|
+
.pop() || 'older version'}
|
|
387
|
+
</Link>
|
|
388
|
+
),
|
|
389
|
+
}}
|
|
390
|
+
/>
|
|
391
|
+
</Message.Content>
|
|
392
|
+
</Message>
|
|
393
|
+
)}
|
|
394
|
+
</Segment.Group>
|
|
395
|
+
{this.state.isClient &&
|
|
396
|
+
createPortal(
|
|
397
|
+
<Toolbar
|
|
398
|
+
pathname={this.props.pathname}
|
|
399
|
+
hideDefaultViewButtons
|
|
400
|
+
inner={
|
|
401
|
+
<Link
|
|
402
|
+
to={`${getBaseUrl(this.props.pathname)}`}
|
|
403
|
+
className="item"
|
|
404
|
+
>
|
|
405
|
+
<IconNext
|
|
406
|
+
name={backSVG}
|
|
407
|
+
className="contents circled"
|
|
408
|
+
size="30px"
|
|
409
|
+
title={this.props.intl.formatMessage(messages.back)}
|
|
410
|
+
/>
|
|
411
|
+
</Link>
|
|
412
|
+
}
|
|
413
|
+
/>,
|
|
414
|
+
document.getElementById('toolbar'),
|
|
415
|
+
)}
|
|
416
|
+
</Container>
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
export default compose(
|
|
422
|
+
injectIntl,
|
|
423
|
+
asyncConnect([
|
|
424
|
+
{
|
|
425
|
+
key: 'actions',
|
|
426
|
+
// Dispatch async/await to make the operation synchronous, otherwise it returns
|
|
427
|
+
// before the promise is resolved
|
|
428
|
+
promise: async ({ location, store: { dispatch } }) =>
|
|
429
|
+
await dispatch(listActions(getBaseUrl(location.pathname))),
|
|
430
|
+
},
|
|
431
|
+
]),
|
|
432
|
+
connect(
|
|
433
|
+
(state, props) => ({
|
|
434
|
+
objectActions: state.actions.actions.object,
|
|
435
|
+
token: state.userSession.token,
|
|
436
|
+
entries: state.history.entries,
|
|
437
|
+
pathname: props.location.pathname,
|
|
438
|
+
title: state.content.data?.title,
|
|
439
|
+
content: state.content.data,
|
|
440
|
+
revertRequest: state.history.revert,
|
|
441
|
+
}),
|
|
442
|
+
{ getHistory, revertHistory },
|
|
443
|
+
),
|
|
444
|
+
)(History);
|
|
@@ -21,6 +21,7 @@ import { Button, Comment, Container, Icon } from 'semantic-ui-react';
|
|
|
21
21
|
import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable';
|
|
22
22
|
import { formatRelativeDate } from '@plone/volto/helpers/Utils/Date';
|
|
23
23
|
import config from '@plone/volto/registry';
|
|
24
|
+
import './comments.less';
|
|
24
25
|
// import { Button, Grid, Segment, Container } from 'semantic-ui-react';
|
|
25
26
|
|
|
26
27
|
const messages = defineMessages({
|
|
@@ -341,7 +342,8 @@ class Comments extends Component {
|
|
|
341
342
|
<Comment.Actions>
|
|
342
343
|
{comment.can_reply && (
|
|
343
344
|
<Comment.Action
|
|
344
|
-
as="
|
|
345
|
+
as="button"
|
|
346
|
+
type="button"
|
|
345
347
|
aria-label={this.props.intl.formatMessage(messages.reply)}
|
|
346
348
|
onClick={() => this.setReplyTo(comment.comment_id)}
|
|
347
349
|
>
|
|
@@ -350,6 +352,8 @@ class Comments extends Component {
|
|
|
350
352
|
)}
|
|
351
353
|
{comment.is_editable && (
|
|
352
354
|
<Comment.Action
|
|
355
|
+
as="button"
|
|
356
|
+
type="button"
|
|
353
357
|
onClick={() =>
|
|
354
358
|
this.onEdit({
|
|
355
359
|
id: flattenToAppURL(comment['@id']),
|
|
@@ -367,6 +371,8 @@ class Comments extends Component {
|
|
|
367
371
|
)}
|
|
368
372
|
{comment.is_deletable && (
|
|
369
373
|
<Comment.Action
|
|
374
|
+
as="button"
|
|
375
|
+
type="button"
|
|
370
376
|
aria-label={this.props.intl.formatMessage(messages.delete)}
|
|
371
377
|
onClick={() => this.onDelete(flattenToAppURL(comment['@id']))}
|
|
372
378
|
color="red"
|
|
@@ -380,7 +386,8 @@ class Comments extends Component {
|
|
|
380
386
|
</Comment.Action>
|
|
381
387
|
)}
|
|
382
388
|
<Comment.Action
|
|
383
|
-
as="
|
|
389
|
+
as="button"
|
|
390
|
+
type="button"
|
|
384
391
|
onClick={() => this.hideReply(comment.comment_id)}
|
|
385
392
|
>
|
|
386
393
|
{allCommentsWithCildren[comment.comment_id].children.length >
|
|
@@ -362,11 +362,11 @@ describe('Comments', () => {
|
|
|
362
362
|
</Provider>,
|
|
363
363
|
);
|
|
364
364
|
|
|
365
|
-
const actions = container.querySelectorAll('.actions
|
|
365
|
+
const actions = container.querySelectorAll('.actions button');
|
|
366
366
|
|
|
367
|
-
fireEvent.click(container.querySelector('
|
|
368
|
-
fireEvent.click(container.querySelector('
|
|
369
|
-
fireEvent.click(container.querySelector('
|
|
367
|
+
fireEvent.click(container.querySelector('button[aria-label="Reply"]'));
|
|
368
|
+
fireEvent.click(container.querySelector('button[aria-label="Edit"]'));
|
|
369
|
+
fireEvent.click(container.querySelector('button[aria-label="Delete"]'));
|
|
370
370
|
fireEvent.click(actions[actions.length - 1]);
|
|
371
371
|
fireEvent.click(
|
|
372
372
|
container.querySelector('#comment-add-id button[aria-label="Comment"]'),
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
.ui.comments .comment .actions button {
|
|
2
|
+
display: inline-block;
|
|
3
|
+
padding: 0;
|
|
4
|
+
border: none;
|
|
5
|
+
margin: 0em 0.75em 0em 0em;
|
|
6
|
+
background: none;
|
|
7
|
+
color: #000000;
|
|
8
|
+
cursor: pointer;
|
|
9
|
+
font: inherit;
|
|
10
|
+
text-decoration: none;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.ui.comments .comment .actions button:focus {
|
|
14
|
+
outline: 1px solid #2185d0;
|
|
15
|
+
outline-offset: 1px;
|
|
16
|
+
}
|
|
@@ -11,6 +11,7 @@ import { withRouter } from 'react-router-dom';
|
|
|
11
11
|
import { UniversalLink } from '@plone/volto/components';
|
|
12
12
|
import { getBaseUrl, hasApiExpander } from '@plone/volto/helpers';
|
|
13
13
|
import { getNavigation } from '@plone/volto/actions';
|
|
14
|
+
import { getNavigationSettings } from '@eeacms/volto-eea-website-theme/actions';
|
|
14
15
|
import { Header, Logo } from '@eeacms/volto-eea-design-system/ui';
|
|
15
16
|
import { usePrevious } from '@eeacms/volto-eea-design-system/helpers';
|
|
16
17
|
import eeaFlag from '@eeacms/volto-eea-design-system/../theme/themes/eea/assets/images/Header/eea.png';
|
|
@@ -58,6 +59,58 @@ const EEAHeader = ({ pathname, token, items, history, subsite }) => {
|
|
|
58
59
|
const width = useSelector((state) => state.screen?.width);
|
|
59
60
|
const dispatch = useDispatch();
|
|
60
61
|
const previousToken = usePrevious(token);
|
|
62
|
+
const navigationSettings = useSelector(
|
|
63
|
+
(state) => state.navigationSettings?.settings || {},
|
|
64
|
+
);
|
|
65
|
+
const navigationLoaded = useSelector(
|
|
66
|
+
(state) => state.navigationSettings?.loaded,
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
// Combine navigation settings from backend with config fallback
|
|
70
|
+
const configLayouts = config.settings?.menuItemsLayouts || {};
|
|
71
|
+
const enhancedLayouts = { ...configLayouts };
|
|
72
|
+
|
|
73
|
+
// Map navigation settings to menu item URLs
|
|
74
|
+
if (items) {
|
|
75
|
+
items.forEach((menuItem) => {
|
|
76
|
+
// Check if we have navigation settings for any route that might match this menu item
|
|
77
|
+
Object.keys(navigationSettings).forEach((routeId) => {
|
|
78
|
+
const route = navigationSettings[routeId];
|
|
79
|
+
const backendSettings = {};
|
|
80
|
+
|
|
81
|
+
if (route.hideChildrenFromNavigation !== undefined) {
|
|
82
|
+
backendSettings.hideChildrenFromNavigation =
|
|
83
|
+
route.hideChildrenFromNavigation;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (route.menuItemChildrenListColumns !== undefined) {
|
|
87
|
+
// Convert strings back to integers for header usage
|
|
88
|
+
backendSettings.menuItemChildrenListColumns = Array.isArray(
|
|
89
|
+
route.menuItemChildrenListColumns,
|
|
90
|
+
)
|
|
91
|
+
? route.menuItemChildrenListColumns
|
|
92
|
+
.map((val) =>
|
|
93
|
+
typeof val === 'string' ? parseInt(val, 10) : val,
|
|
94
|
+
)
|
|
95
|
+
.filter((val) => !isNaN(val))
|
|
96
|
+
: route.menuItemChildrenListColumns;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (route.menuItemColumns !== undefined) {
|
|
100
|
+
// Use menuItemColumns directly as they're already in semantic UI format
|
|
101
|
+
backendSettings.menuItemColumns = route.menuItemColumns;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (Object.keys(backendSettings).length > 0) {
|
|
105
|
+
// Override the config setting with backend data
|
|
106
|
+
enhancedLayouts[routeId] = {
|
|
107
|
+
...enhancedLayouts[routeId],
|
|
108
|
+
...backendSettings,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
}
|
|
61
114
|
|
|
62
115
|
React.useEffect(() => {
|
|
63
116
|
const base_url = getBaseUrl(pathname);
|
|
@@ -72,7 +125,12 @@ const EEAHeader = ({ pathname, token, items, history, subsite }) => {
|
|
|
72
125
|
if (token !== previousToken) {
|
|
73
126
|
dispatch(getNavigation(base_url, settings.navDepth));
|
|
74
127
|
}
|
|
75
|
-
|
|
128
|
+
|
|
129
|
+
// Fetch navigation settings
|
|
130
|
+
if (!navigationLoaded) {
|
|
131
|
+
dispatch(getNavigationSettings(pathname));
|
|
132
|
+
}
|
|
133
|
+
}, [pathname, token, dispatch, previousToken, navigationLoaded]);
|
|
76
134
|
|
|
77
135
|
return (
|
|
78
136
|
<Header menuItems={items}>
|
|
@@ -174,6 +232,7 @@ const EEAHeader = ({ pathname, token, items, history, subsite }) => {
|
|
|
174
232
|
</div>
|
|
175
233
|
}
|
|
176
234
|
menuItems={items}
|
|
235
|
+
menuItemsLayouts={enhancedLayouts}
|
|
177
236
|
renderGlobalMenuItem={(item, { onClick }) => (
|
|
178
237
|
<a
|
|
179
238
|
href={item.url || '/'}
|