@plone/volto 17.0.0-alpha.16 → 17.0.0-alpha.18
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/.yarn/install-state.gz +0 -0
- package/CHANGELOG.md +59 -0
- package/cypress/support/commands.js +17 -0
- package/locales/it/LC_MESSAGES/volto.po +1 -1
- package/locales/it.json +1 -1
- package/package.json +2 -2
- package/packages/volto-slate/package.json +1 -1
- package/packages/volto-slate/src/blocks/Table/TableBlockEdit.jsx +21 -212
- package/packages/volto-slate/src/blocks/Table/schema.js +122 -0
- package/packages/volto-slate/src/editor/plugins/StyleMenu/utils.js +14 -5
- package/packages/volto-slate/src/utils/blocks.js +7 -0
- package/packages/volto-slate/src/widgets/RichTextWidget.jsx +15 -8
- package/src/components/manage/Blocks/Grid/View.jsx +1 -0
- package/src/components/manage/Blocks/Listing/withQuerystringResults.jsx +3 -4
- package/src/components/manage/Blocks/Search/components/Facets.jsx +6 -2
- package/src/components/manage/Blocks/Teaser/Body.jsx +0 -1
- package/src/components/manage/Blocks/Teaser/DefaultBody.jsx +20 -15
- package/src/components/manage/Blocks/ToC/Schema.jsx +5 -1
- package/src/components/manage/Blocks/ToC/variations/HorizontalMenu.jsx +142 -8
- package/src/components/manage/UniversalLink/UniversalLink.jsx +2 -6
- package/src/components/manage/UniversalLink/UniversalLink.test.jsx +36 -0
- package/src/components/theme/Anontools/Anontools.jsx +3 -4
- package/src/components/theme/Breadcrumbs/Breadcrumbs.jsx +52 -99
- package/src/components/theme/Breadcrumbs/Breadcrumbs.stories.jsx +14 -13
- package/src/components/theme/Comments/CommentEditModal.jsx +63 -115
- package/src/components/theme/ContactForm/ContactForm.jsx +108 -192
- package/src/components/theme/ContactForm/ContactForm.stories.jsx +1 -1
- package/src/components/theme/ContactForm/ContactForm.test.jsx +2 -3
- package/src/components/theme/Header/Header.jsx +2 -2
- package/src/components/theme/Login/Login.jsx +1 -1
- package/src/components/theme/SearchWidget/SearchWidget.jsx +38 -98
- package/src/components/theme/View/AlbumView.jsx +9 -1
- package/src/components/theme/View/EventView.jsx +6 -2
- package/src/components/theme/View/FileView.jsx +23 -18
- package/src/components/theme/View/ImageView.jsx +37 -32
- package/src/components/theme/View/LinkView.jsx +53 -78
- package/src/components/theme/View/ListingView.jsx +33 -27
- package/src/components/theme/View/SummaryView.jsx +47 -38
- package/src/components/theme/View/TabularView.jsx +59 -53
- package/src/hooks/client/useClient.js +11 -0
- package/src/hooks/index.js +1 -1
- package/theme/themes/pastanaga/extras/main.less +2 -1
- package/theme/themes/pastanaga/extras/toc.less +29 -0
- package/src/hooks/content/useContent.js +0 -31
- package/src/hooks/userSession/useToken.js +0 -5
|
@@ -1,12 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
* View toc block.
|
|
3
|
-
* @module components/manage/Blocks/ToC/View
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import React from 'react';
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
7
2
|
import PropTypes from 'prop-types';
|
|
8
3
|
import { map } from 'lodash';
|
|
9
|
-
import { Menu } from 'semantic-ui-react';
|
|
4
|
+
import { Menu, Dropdown } from 'semantic-ui-react';
|
|
10
5
|
import { FormattedMessage, injectIntl } from 'react-intl';
|
|
11
6
|
import AnchorLink from 'react-anchor-link-smooth-scroll';
|
|
12
7
|
import Slugger from 'github-slugger';
|
|
@@ -36,6 +31,131 @@ const RenderMenuItems = ({ items }) => {
|
|
|
36
31
|
* @extends Component
|
|
37
32
|
*/
|
|
38
33
|
const View = ({ data, tocEntries }) => {
|
|
34
|
+
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
|
35
|
+
// When the page is resized to prevent items from the TOC from going out of the viewport,
|
|
36
|
+
// a dropdown menu is added containing all the items that don't fit.
|
|
37
|
+
const handleResize = () => {
|
|
38
|
+
const menuElement = document.querySelector('.responsive-menu');
|
|
39
|
+
const containerWidth = menuElement.offsetWidth;
|
|
40
|
+
|
|
41
|
+
// Get all divs that contain the items from the TOC, except the dropdown button
|
|
42
|
+
const nested = document.querySelectorAll(
|
|
43
|
+
'.responsive-menu .item:not(.dropdown)',
|
|
44
|
+
);
|
|
45
|
+
const nestedArray = Object.values(nested);
|
|
46
|
+
const middle = Math.ceil(nestedArray.length / 2);
|
|
47
|
+
const firstHalfNested = nestedArray.slice(0, middle);
|
|
48
|
+
const secondHalfNested = nestedArray.slice(middle);
|
|
49
|
+
|
|
50
|
+
const dropdown = document.querySelector('.dropdown');
|
|
51
|
+
const dropdownWidth = dropdown.offsetWidth;
|
|
52
|
+
|
|
53
|
+
const firstHalfNestedHiddenItems = [];
|
|
54
|
+
|
|
55
|
+
// Add a 'hidden' class for the items that should be in the dropdown
|
|
56
|
+
firstHalfNested.forEach((item) => {
|
|
57
|
+
const itemOffsetLeft = item.offsetLeft;
|
|
58
|
+
const itemOffsetWidth = item.offsetWidth;
|
|
59
|
+
if (itemOffsetLeft + itemOffsetWidth > containerWidth - dropdownWidth) {
|
|
60
|
+
item.classList.add('hidden');
|
|
61
|
+
firstHalfNestedHiddenItems.push(item);
|
|
62
|
+
} else {
|
|
63
|
+
item.classList.remove('hidden');
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
secondHalfNested.forEach((item) => item.classList.add('hidden-dropdown'));
|
|
68
|
+
|
|
69
|
+
const diff = firstHalfNested.length - firstHalfNestedHiddenItems.length;
|
|
70
|
+
const secondHalfNestedShownItems = secondHalfNested.slice(diff);
|
|
71
|
+
secondHalfNestedShownItems.forEach((item) =>
|
|
72
|
+
item.classList.remove('hidden-dropdown'),
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
// If there are elements that should be displayed in the dropdown, show the dropdown button
|
|
76
|
+
if (secondHalfNestedShownItems.length > 0)
|
|
77
|
+
dropdown.classList.remove('hidden-dropdown');
|
|
78
|
+
else {
|
|
79
|
+
dropdown.classList.add('hidden-dropdown');
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const handleDropdownKeyDown = (event) => {
|
|
84
|
+
const dropdownMenu = document.querySelector('.menu.transition');
|
|
85
|
+
if (event.key === 'ArrowDown' && isDropdownOpen) {
|
|
86
|
+
event.preventDefault();
|
|
87
|
+
const menuItems = dropdownMenu.querySelectorAll(
|
|
88
|
+
'.item:not(.hidden-dropdown)',
|
|
89
|
+
);
|
|
90
|
+
const focusedItem = dropdownMenu.querySelector('.item.focused');
|
|
91
|
+
const focusedIndex = Array.from(menuItems).indexOf(focusedItem);
|
|
92
|
+
|
|
93
|
+
if (focusedIndex === -1) {
|
|
94
|
+
// No item is currently focused, so focus the first item
|
|
95
|
+
menuItems[0].classList.add('focused');
|
|
96
|
+
} else if (focusedIndex === menuItems.length - 1) {
|
|
97
|
+
// Remove focus from the currently focused item and close the dropdown
|
|
98
|
+
focusedItem.classList.remove('focused');
|
|
99
|
+
setIsDropdownOpen(false);
|
|
100
|
+
|
|
101
|
+
// Focus the next element on the page
|
|
102
|
+
const nextElement = dropdownMenu.nextElementSibling;
|
|
103
|
+
if (nextElement) {
|
|
104
|
+
nextElement.focus();
|
|
105
|
+
}
|
|
106
|
+
} else {
|
|
107
|
+
// Remove focus from the currently focused item
|
|
108
|
+
focusedItem.classList.remove('focused');
|
|
109
|
+
|
|
110
|
+
// Focus the next item or wrap around to the first item
|
|
111
|
+
const nextIndex = (focusedIndex + 1) % menuItems.length;
|
|
112
|
+
menuItems[nextIndex].classList.add('focused');
|
|
113
|
+
}
|
|
114
|
+
} else if (event.key === 'Enter' && isDropdownOpen) {
|
|
115
|
+
const focusedItem = dropdownMenu.querySelector('.item.focused');
|
|
116
|
+
if (focusedItem) {
|
|
117
|
+
focusedItem.querySelector('a').click();
|
|
118
|
+
focusedItem.classList.remove('focused');
|
|
119
|
+
}
|
|
120
|
+
} else if (event.key === 'Tab') {
|
|
121
|
+
const focusedItem = dropdownMenu.querySelector('.item.focused');
|
|
122
|
+
if (focusedItem) {
|
|
123
|
+
focusedItem.classList.remove('focused');
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
useEffect(() => {
|
|
129
|
+
if (data.sticky) {
|
|
130
|
+
const toc = document.querySelector('.horizontalMenu');
|
|
131
|
+
const tocPos = toc ? toc.offsetTop : 0;
|
|
132
|
+
|
|
133
|
+
const handleScroll = () => {
|
|
134
|
+
let scrollPos = window.scrollY;
|
|
135
|
+
if (scrollPos > tocPos && toc) {
|
|
136
|
+
toc.classList.add('sticky-toc');
|
|
137
|
+
} else if (scrollPos <= tocPos && toc) {
|
|
138
|
+
toc.classList.remove('sticky-toc');
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
window.addEventListener('scroll', handleScroll);
|
|
143
|
+
|
|
144
|
+
return () => {
|
|
145
|
+
window.removeEventListener('scroll', handleScroll);
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
}, [data.sticky]);
|
|
149
|
+
|
|
150
|
+
useEffect(() => {
|
|
151
|
+
handleResize();
|
|
152
|
+
window.addEventListener('resize', handleResize);
|
|
153
|
+
|
|
154
|
+
return () => {
|
|
155
|
+
window.removeEventListener('resize', handleResize);
|
|
156
|
+
};
|
|
157
|
+
});
|
|
158
|
+
|
|
39
159
|
return (
|
|
40
160
|
<>
|
|
41
161
|
{data.title && !data.hide_title ? (
|
|
@@ -50,8 +170,22 @@ const View = ({ data, tocEntries }) => {
|
|
|
50
170
|
) : (
|
|
51
171
|
''
|
|
52
172
|
)}
|
|
53
|
-
<Menu>
|
|
173
|
+
<Menu className="responsive-menu">
|
|
54
174
|
<RenderMenuItems items={tocEntries} />
|
|
175
|
+
<Dropdown
|
|
176
|
+
item
|
|
177
|
+
text="More"
|
|
178
|
+
className="hidden-dropdown"
|
|
179
|
+
open={isDropdownOpen}
|
|
180
|
+
onOpen={() => setIsDropdownOpen(true)}
|
|
181
|
+
onClose={() => setIsDropdownOpen(false)}
|
|
182
|
+
tabIndex={0}
|
|
183
|
+
onKeyDown={handleDropdownKeyDown}
|
|
184
|
+
>
|
|
185
|
+
<Dropdown.Menu>
|
|
186
|
+
<RenderMenuItems items={tocEntries} />
|
|
187
|
+
</Dropdown.Menu>
|
|
188
|
+
</Dropdown>
|
|
55
189
|
</Menu>
|
|
56
190
|
</>
|
|
57
191
|
);
|
|
@@ -12,7 +12,6 @@ import {
|
|
|
12
12
|
isInternalURL,
|
|
13
13
|
URLUtils,
|
|
14
14
|
} from '@plone/volto/helpers/Url/Url';
|
|
15
|
-
import { matchPath } from 'react-router';
|
|
16
15
|
|
|
17
16
|
import config from '@plone/volto/registry';
|
|
18
17
|
|
|
@@ -66,11 +65,8 @@ const UniversalLink = ({
|
|
|
66
65
|
}
|
|
67
66
|
}
|
|
68
67
|
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
matchPath(flattenToAppURL(url), route.match),
|
|
72
|
-
)?.length > 0;
|
|
73
|
-
const isExternal = !isInternalURL(url) || isBlacklisted;
|
|
68
|
+
const isExternal = !isInternalURL(url);
|
|
69
|
+
|
|
74
70
|
const isDownload = (!isExternal && url.includes('@@download')) || download;
|
|
75
71
|
const isDisplayFile =
|
|
76
72
|
(!isExternal && url.includes('@@display-file')) || false;
|
|
@@ -157,6 +157,42 @@ describe('UniversalLink', () => {
|
|
|
157
157
|
);
|
|
158
158
|
});
|
|
159
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 noreferrer',
|
|
193
|
+
);
|
|
194
|
+
});
|
|
195
|
+
|
|
160
196
|
it('check UniversalLink does not break with error in item', () => {
|
|
161
197
|
const component = renderer.create(
|
|
162
198
|
<Provider store={store}>
|
|
@@ -3,13 +3,12 @@ import { Link } from 'react-router-dom';
|
|
|
3
3
|
import { Menu } from 'semantic-ui-react';
|
|
4
4
|
import { FormattedMessage } from 'react-intl';
|
|
5
5
|
import { flattenToAppURL } from '@plone/volto/helpers';
|
|
6
|
-
import { useToken } from '@plone/volto/hooks/userSession/useToken';
|
|
7
|
-
import { useContent } from '@plone/volto/hooks/content/useContent';
|
|
8
6
|
import config from '@plone/volto/registry';
|
|
7
|
+
import { useSelector, shallowEqual } from 'react-redux';
|
|
9
8
|
|
|
10
9
|
const Anontools = () => {
|
|
11
|
-
const token =
|
|
12
|
-
const
|
|
10
|
+
const token = useSelector((state) => state.userSession.token, shallowEqual);
|
|
11
|
+
const content = useSelector((state) => state.content.data, shallowEqual);
|
|
13
12
|
|
|
14
13
|
const { settings } = config;
|
|
15
14
|
return (
|
|
@@ -1,20 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
* Breadcrumbs components.
|
|
3
|
-
* @module components/theme/Breadcrumbs/Breadcrumbs
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import React, { Component } from 'react';
|
|
1
|
+
import { useEffect } from 'react';
|
|
7
2
|
import PropTypes from 'prop-types';
|
|
8
|
-
import { connect } from 'react-redux';
|
|
9
|
-
import { compose } from 'redux';
|
|
10
3
|
import { Link } from 'react-router-dom';
|
|
11
4
|
import { Breadcrumb, Container, Segment } from 'semantic-ui-react';
|
|
12
|
-
import { defineMessages,
|
|
5
|
+
import { defineMessages, useIntl } from 'react-intl';
|
|
6
|
+
import { useDispatch, useSelector, shallowEqual } from 'react-redux';
|
|
13
7
|
|
|
14
|
-
import { Icon } from '@plone/volto/components';
|
|
15
8
|
import { getBreadcrumbs } from '@plone/volto/actions';
|
|
16
9
|
import { getBaseUrl, hasApiExpander } from '@plone/volto/helpers';
|
|
17
|
-
|
|
10
|
+
import { Icon } from '@plone/volto/components';
|
|
18
11
|
import homeSVG from '@plone/volto/icons/home.svg';
|
|
19
12
|
|
|
20
13
|
const messages = defineMessages({
|
|
@@ -28,96 +21,56 @@ const messages = defineMessages({
|
|
|
28
21
|
},
|
|
29
22
|
});
|
|
30
23
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
export class BreadcrumbsComponent extends Component {
|
|
35
|
-
/**
|
|
36
|
-
* Property types.
|
|
37
|
-
* @property {Object} propTypes Property types.
|
|
38
|
-
* @static
|
|
39
|
-
*/
|
|
40
|
-
static propTypes = {
|
|
41
|
-
getBreadcrumbs: PropTypes.func.isRequired,
|
|
42
|
-
pathname: PropTypes.string.isRequired,
|
|
43
|
-
root: PropTypes.string,
|
|
44
|
-
items: PropTypes.arrayOf(
|
|
45
|
-
PropTypes.shape({
|
|
46
|
-
title: PropTypes.string,
|
|
47
|
-
url: PropTypes.string,
|
|
48
|
-
}),
|
|
49
|
-
).isRequired,
|
|
50
|
-
};
|
|
24
|
+
const BreadcrumbsComponent = ({ pathname }) => {
|
|
25
|
+
const intl = useIntl();
|
|
26
|
+
const dispatch = useDispatch();
|
|
51
27
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
this.props.getBreadcrumbs(getBaseUrl(this.props.pathname));
|
|
55
|
-
}
|
|
56
|
-
}
|
|
28
|
+
const items = useSelector((state) => state.breadcrumbs.items, shallowEqual);
|
|
29
|
+
const root = useSelector((state) => state.breadcrumbs.root);
|
|
57
30
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
* @param {Object} nextProps Next properties
|
|
62
|
-
* @returns {undefined}
|
|
63
|
-
*/
|
|
64
|
-
UNSAFE_componentWillReceiveProps(nextProps) {
|
|
65
|
-
if (nextProps.pathname !== this.props.pathname) {
|
|
66
|
-
if (!hasApiExpander('breadcrumbs', getBaseUrl(this.props.pathname))) {
|
|
67
|
-
this.props.getBreadcrumbs(getBaseUrl(nextProps.pathname));
|
|
68
|
-
}
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
if (!hasApiExpander('breadcrumbs', getBaseUrl(pathname))) {
|
|
33
|
+
dispatch(getBreadcrumbs(getBaseUrl(pathname)));
|
|
69
34
|
}
|
|
70
|
-
}
|
|
35
|
+
}, [dispatch, pathname]);
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<Segment
|
|
39
|
+
role="navigation"
|
|
40
|
+
aria-label={intl.formatMessage(messages.breadcrumbs)}
|
|
41
|
+
className="breadcrumbs"
|
|
42
|
+
secondary
|
|
43
|
+
vertical
|
|
44
|
+
>
|
|
45
|
+
<Container>
|
|
46
|
+
<Breadcrumb>
|
|
47
|
+
<Link
|
|
48
|
+
to={root || '/'}
|
|
49
|
+
className="section"
|
|
50
|
+
title={intl.formatMessage(messages.home)}
|
|
51
|
+
>
|
|
52
|
+
<Icon name={homeSVG} size="18px" />
|
|
53
|
+
</Link>
|
|
54
|
+
{items.map((item, index, items) => [
|
|
55
|
+
<Breadcrumb.Divider key={`divider-${item.url}`} />,
|
|
56
|
+
index < items.length - 1 ? (
|
|
57
|
+
<Link key={item.url} to={item.url} className="section">
|
|
58
|
+
{item.title}
|
|
59
|
+
</Link>
|
|
60
|
+
) : (
|
|
61
|
+
<Breadcrumb.Section key={item.url} active>
|
|
62
|
+
{item.title}
|
|
63
|
+
</Breadcrumb.Section>
|
|
64
|
+
),
|
|
65
|
+
])}
|
|
66
|
+
</Breadcrumb>
|
|
67
|
+
</Container>
|
|
68
|
+
</Segment>
|
|
69
|
+
);
|
|
70
|
+
};
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
* @returns {string} Markup for the component.
|
|
76
|
-
*/
|
|
77
|
-
render() {
|
|
78
|
-
return (
|
|
79
|
-
<Segment
|
|
80
|
-
role="navigation"
|
|
81
|
-
aria-label={this.props.intl.formatMessage(messages.breadcrumbs)}
|
|
82
|
-
className="breadcrumbs"
|
|
83
|
-
secondary
|
|
84
|
-
vertical
|
|
85
|
-
>
|
|
86
|
-
<Container>
|
|
87
|
-
<Breadcrumb>
|
|
88
|
-
<Link
|
|
89
|
-
to={this.props.root || '/'}
|
|
90
|
-
className="section"
|
|
91
|
-
title={this.props.intl.formatMessage(messages.home)}
|
|
92
|
-
>
|
|
93
|
-
<Icon name={homeSVG} size="18px" />
|
|
94
|
-
</Link>
|
|
95
|
-
{this.props.items.map((item, index, items) => [
|
|
96
|
-
<Breadcrumb.Divider key={`divider-${item.url}`} />,
|
|
97
|
-
index < items.length - 1 ? (
|
|
98
|
-
<Link key={item.url} to={item.url} className="section">
|
|
99
|
-
{item.title}
|
|
100
|
-
</Link>
|
|
101
|
-
) : (
|
|
102
|
-
<Breadcrumb.Section key={item.url} active>
|
|
103
|
-
{item.title}
|
|
104
|
-
</Breadcrumb.Section>
|
|
105
|
-
),
|
|
106
|
-
])}
|
|
107
|
-
</Breadcrumb>
|
|
108
|
-
</Container>
|
|
109
|
-
</Segment>
|
|
110
|
-
);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
72
|
+
BreadcrumbsComponent.propTypes = {
|
|
73
|
+
pathname: PropTypes.string.isRequired,
|
|
74
|
+
};
|
|
113
75
|
|
|
114
|
-
export default
|
|
115
|
-
injectIntl,
|
|
116
|
-
connect(
|
|
117
|
-
(state) => ({
|
|
118
|
-
items: state.breadcrumbs.items,
|
|
119
|
-
root: state.breadcrumbs.root,
|
|
120
|
-
}),
|
|
121
|
-
{ getBreadcrumbs },
|
|
122
|
-
),
|
|
123
|
-
)(BreadcrumbsComponent);
|
|
76
|
+
export default BreadcrumbsComponent;
|
|
@@ -1,23 +1,24 @@
|
|
|
1
1
|
import { injectIntl } from 'react-intl';
|
|
2
2
|
import React from 'react';
|
|
3
|
-
import
|
|
3
|
+
import BreadcrumbsComponent from './Breadcrumbs';
|
|
4
4
|
import Wrapper from '@plone/volto/storybook';
|
|
5
5
|
|
|
6
6
|
export const Breadcrumb = injectIntl(({ children, ...args }) => {
|
|
7
7
|
return (
|
|
8
|
-
<Wrapper
|
|
8
|
+
<Wrapper
|
|
9
|
+
anonymous
|
|
10
|
+
location={{ pathname: '/folder2/folder21/doc212' }}
|
|
11
|
+
customStore={{
|
|
12
|
+
breadcrumbs: {
|
|
13
|
+
items: [
|
|
14
|
+
{ title: 'Blog', url: '/blog' },
|
|
15
|
+
{ title: 'My first blog', url: '/blog/my-first-blog' },
|
|
16
|
+
],
|
|
17
|
+
},
|
|
18
|
+
}}
|
|
19
|
+
>
|
|
9
20
|
<div className="ui segment form attached" style={{ width: '400px' }}>
|
|
10
|
-
<BreadcrumbsComponent
|
|
11
|
-
pathname=""
|
|
12
|
-
items={[
|
|
13
|
-
{
|
|
14
|
-
'@id': 'https://volto.kitconcept.com/api/Members',
|
|
15
|
-
title: 'Users',
|
|
16
|
-
},
|
|
17
|
-
]}
|
|
18
|
-
getBreadcrumbs={() => {}}
|
|
19
|
-
{...args}
|
|
20
|
-
/>
|
|
21
|
+
<BreadcrumbsComponent pathname="" />
|
|
21
22
|
</div>
|
|
22
23
|
</Wrapper>
|
|
23
24
|
);
|
|
@@ -1,14 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
* Comment edit modal.
|
|
3
|
-
* @module components/theme/Comments/CommentEditModal
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import React, { Component } from 'react';
|
|
1
|
+
import { useEffect } from 'react';
|
|
7
2
|
import PropTypes from 'prop-types';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import { defineMessages, injectIntl } from 'react-intl';
|
|
3
|
+
import { useDispatch, useSelector, shallowEqual } from 'react-redux';
|
|
4
|
+
import { defineMessages, useIntl } from 'react-intl';
|
|
11
5
|
|
|
6
|
+
import { usePrevious } from '@plone/volto/helpers';
|
|
12
7
|
import { updateComment } from '@plone/volto/actions';
|
|
13
8
|
import { ModalForm } from '@plone/volto/components';
|
|
14
9
|
|
|
@@ -27,116 +22,69 @@ const messages = defineMessages({
|
|
|
27
22
|
},
|
|
28
23
|
});
|
|
29
24
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
class CommentEditModal extends Component {
|
|
36
|
-
/**
|
|
37
|
-
* Property types.
|
|
38
|
-
* @property {Object} propTypes Property types.
|
|
39
|
-
* @static
|
|
40
|
-
*/
|
|
41
|
-
static propTypes = {
|
|
42
|
-
updateComment: PropTypes.func.isRequired,
|
|
43
|
-
id: PropTypes.string,
|
|
44
|
-
text: PropTypes.string,
|
|
45
|
-
request: PropTypes.shape({
|
|
46
|
-
loading: PropTypes.bool,
|
|
47
|
-
loaded: PropTypes.bool,
|
|
48
|
-
}).isRequired,
|
|
49
|
-
open: PropTypes.bool.isRequired,
|
|
50
|
-
onOk: PropTypes.func.isRequired,
|
|
51
|
-
onCancel: PropTypes.func.isRequired,
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Default properties.
|
|
56
|
-
* @property {Object} defaultProps Default properties.
|
|
57
|
-
* @static
|
|
58
|
-
*/
|
|
59
|
-
static defaultProps = {
|
|
60
|
-
id: '',
|
|
61
|
-
text: '',
|
|
62
|
-
};
|
|
25
|
+
const CommentEditModal = (props) => {
|
|
26
|
+
const intl = useIntl();
|
|
27
|
+
const dispatch = useDispatch();
|
|
28
|
+
const { onOk, open, onCancel } = props;
|
|
29
|
+
const request = useSelector((state) => state.comments.update, shallowEqual);
|
|
63
30
|
|
|
64
|
-
|
|
65
|
-
* Constructor
|
|
66
|
-
* @method constructor
|
|
67
|
-
* @param {Object} props Component properties
|
|
68
|
-
* @constructs CommentEditModal
|
|
69
|
-
*/
|
|
70
|
-
constructor(props) {
|
|
71
|
-
super(props);
|
|
72
|
-
this.onSubmit = this.onSubmit.bind(this);
|
|
73
|
-
}
|
|
31
|
+
const prevRequestLoading = usePrevious(request.loading);
|
|
74
32
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
* @param {Object} nextProps Next properties
|
|
79
|
-
* @returns {undefined}
|
|
80
|
-
*/
|
|
81
|
-
UNSAFE_componentWillReceiveProps(nextProps) {
|
|
82
|
-
if (this.props.request.loading && nextProps.request.loaded) {
|
|
83
|
-
this.props.onOk();
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
if (prevRequestLoading && request.loaded) {
|
|
35
|
+
onOk();
|
|
84
36
|
}
|
|
85
|
-
}
|
|
37
|
+
}, [onOk, prevRequestLoading, request.loaded]);
|
|
86
38
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
* @param {Object} data Form data
|
|
91
|
-
* @returns {undefined}
|
|
92
|
-
*/
|
|
93
|
-
onSubmit(data) {
|
|
94
|
-
this.props.updateComment(this.props.id, data.text);
|
|
95
|
-
}
|
|
39
|
+
const onSubmit = (data) => {
|
|
40
|
+
dispatch(updateComment(props.id, data.text));
|
|
41
|
+
};
|
|
96
42
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
],
|
|
119
|
-
properties: {
|
|
120
|
-
text: {
|
|
121
|
-
title: this.props.intl.formatMessage(messages.comment),
|
|
122
|
-
type: 'string',
|
|
123
|
-
description: '',
|
|
124
|
-
},
|
|
43
|
+
return (
|
|
44
|
+
props.open && (
|
|
45
|
+
<ModalForm
|
|
46
|
+
open={open}
|
|
47
|
+
onSubmit={onSubmit}
|
|
48
|
+
onCancel={onCancel}
|
|
49
|
+
formData={{ text: props.text }}
|
|
50
|
+
title={intl.formatMessage(messages.editComment)}
|
|
51
|
+
schema={{
|
|
52
|
+
fieldsets: [
|
|
53
|
+
{
|
|
54
|
+
id: 'default',
|
|
55
|
+
title: intl.formatMessage(messages.default),
|
|
56
|
+
fields: ['text'],
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
properties: {
|
|
60
|
+
text: {
|
|
61
|
+
title: intl.formatMessage(messages.comment),
|
|
62
|
+
type: 'string',
|
|
63
|
+
description: '',
|
|
125
64
|
},
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
)
|
|
131
|
-
|
|
132
|
-
}
|
|
65
|
+
},
|
|
66
|
+
required: ['text'],
|
|
67
|
+
}}
|
|
68
|
+
/>
|
|
69
|
+
)
|
|
70
|
+
);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
CommentEditModal.propTypes = {
|
|
74
|
+
id: PropTypes.string.isRequired,
|
|
75
|
+
text: PropTypes.string.isRequired,
|
|
76
|
+
request: PropTypes.shape({
|
|
77
|
+
loading: PropTypes.bool,
|
|
78
|
+
loaded: PropTypes.bool,
|
|
79
|
+
}),
|
|
80
|
+
open: PropTypes.bool.isRequired,
|
|
81
|
+
onOk: PropTypes.func.isRequired,
|
|
82
|
+
onCancel: PropTypes.func.isRequired,
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
CommentEditModal.defaultProps = {
|
|
86
|
+
id: '',
|
|
87
|
+
text: '',
|
|
88
|
+
};
|
|
133
89
|
|
|
134
|
-
export default
|
|
135
|
-
injectIntl,
|
|
136
|
-
connect(
|
|
137
|
-
(state) => ({
|
|
138
|
-
request: state.comments.update,
|
|
139
|
-
}),
|
|
140
|
-
{ updateComment },
|
|
141
|
-
),
|
|
142
|
-
)(CommentEditModal);
|
|
90
|
+
export default CommentEditModal;
|