@plone/volto 17.0.0-alpha.17 → 17.0.0-alpha.19

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.
Files changed (74) hide show
  1. package/.yarn/install-state.gz +0 -0
  2. package/CHANGELOG.md +57 -0
  3. package/cypress/support/commands.js +17 -0
  4. package/locales/ca/LC_MESSAGES/volto.po +39 -0
  5. package/locales/ca.json +1 -1
  6. package/locales/de/LC_MESSAGES/volto.po +39 -0
  7. package/locales/de.json +1 -1
  8. package/locales/en/LC_MESSAGES/volto.po +39 -0
  9. package/locales/en.json +1 -1
  10. package/locales/es/LC_MESSAGES/volto.po +39 -0
  11. package/locales/es.json +1 -1
  12. package/locales/eu/LC_MESSAGES/volto.po +39 -0
  13. package/locales/eu.json +1 -1
  14. package/locales/fi/LC_MESSAGES/volto.po +39 -0
  15. package/locales/fi.json +1 -1
  16. package/locales/fr/LC_MESSAGES/volto.po +39 -0
  17. package/locales/fr.json +1 -1
  18. package/locales/it/LC_MESSAGES/volto.po +40 -1
  19. package/locales/it.json +1 -1
  20. package/locales/ja/LC_MESSAGES/volto.po +39 -0
  21. package/locales/ja.json +1 -1
  22. package/locales/nl/LC_MESSAGES/volto.po +39 -0
  23. package/locales/nl.json +1 -1
  24. package/locales/pt/LC_MESSAGES/volto.po +39 -0
  25. package/locales/pt.json +1 -1
  26. package/locales/pt_BR/LC_MESSAGES/volto.po +39 -0
  27. package/locales/pt_BR.json +1 -1
  28. package/locales/ro/LC_MESSAGES/volto.po +39 -0
  29. package/locales/ro.json +1 -1
  30. package/locales/volto.pot +39 -0
  31. package/locales/zh_CN/LC_MESSAGES/volto.po +39 -0
  32. package/locales/zh_CN.json +1 -1
  33. package/package.json +2 -2
  34. package/packages/volto-slate/package.json +1 -1
  35. package/packages/volto-slate/src/blocks/Table/TableBlockEdit.jsx +21 -212
  36. package/packages/volto-slate/src/blocks/Table/schema.js +122 -0
  37. package/packages/volto-slate/src/editor/plugins/StyleMenu/utils.js +14 -5
  38. package/packages/volto-slate/src/utils/blocks.js +7 -0
  39. package/packages/volto-slate/src/widgets/RichTextWidget.jsx +15 -8
  40. package/src/components/index.js +1 -0
  41. package/src/components/manage/Blocks/Search/components/Facets.jsx +6 -2
  42. package/src/components/manage/Blocks/Search/components/SearchInput.jsx +9 -2
  43. package/src/components/manage/Blocks/Search/hocs/withSearch.jsx +12 -1
  44. package/src/components/manage/Blocks/ToC/Schema.jsx +5 -1
  45. package/src/components/manage/Blocks/ToC/variations/HorizontalMenu.jsx +142 -8
  46. package/src/components/manage/LinksToItem/LinksToItem.jsx +209 -0
  47. package/src/components/manage/LinksToItem/LinksToItem.test.jsx +97 -0
  48. package/src/components/manage/Toolbar/More.jsx +15 -0
  49. package/src/components/manage/Widgets/RecurrenceWidget/RecurrenceWidget.jsx +1 -1
  50. package/src/components/theme/Breadcrumbs/Breadcrumbs.jsx +52 -99
  51. package/src/components/theme/Breadcrumbs/Breadcrumbs.stories.jsx +14 -13
  52. package/src/components/theme/Comments/CommentEditModal.jsx +63 -115
  53. package/src/components/theme/ContactForm/ContactForm.jsx +108 -192
  54. package/src/components/theme/ContactForm/ContactForm.stories.jsx +1 -1
  55. package/src/components/theme/ContactForm/ContactForm.test.jsx +2 -3
  56. package/src/components/theme/Login/Login.jsx +1 -1
  57. package/src/components/theme/SearchWidget/SearchWidget.jsx +38 -98
  58. package/src/components/theme/View/LinkView.jsx +51 -79
  59. package/src/config/NonContentRoutes.jsx +1 -0
  60. package/src/config/index.js +2 -0
  61. package/src/config/server.js +2 -0
  62. package/src/express-middleware/ok.js +16 -0
  63. package/src/hooks/client/useClient.js +11 -0
  64. package/src/hooks/index.js +1 -0
  65. package/src/routes.js +9 -0
  66. package/theme/themes/pastanaga/extras/main.less +2 -1
  67. package/theme/themes/pastanaga/extras/toc.less +29 -0
  68. package/news/4351.bugfix +0 -1
  69. package/news/4725.bugfix +0 -1
  70. package/news/4932.bugfix +0 -1
  71. package/news/4941.documentation +0 -1
  72. package/news/4951.breaking +0 -1
  73. package/news/4962.feature +0 -1
  74. package/news/4964.bugfix +0 -1
@@ -0,0 +1,209 @@
1
+ /**
2
+ * LinksToItem component
3
+ * @module components/manage/LinksToItem/LinksToItem
4
+ */
5
+ import { useEffect } from 'react';
6
+ import { find } from 'lodash';
7
+ import { Helmet } from '@plone/volto/helpers';
8
+ import { Link } from 'react-router-dom';
9
+ import { Portal } from 'react-portal';
10
+ import { Container, Segment, Table } from 'semantic-ui-react';
11
+ import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
12
+ import { useDispatch, useSelector } from 'react-redux';
13
+ import { getContent, queryRelations } from '@plone/volto/actions';
14
+ import {
15
+ Icon as IconNext,
16
+ Toolbar,
17
+ UniversalLink,
18
+ } from '@plone/volto/components';
19
+
20
+ import { getBaseUrl } from '@plone/volto/helpers';
21
+ import backSVG from '@plone/volto/icons/back.svg';
22
+ import settingsSVG from '@plone/volto/icons/settings.svg';
23
+
24
+ const messages = defineMessages({
25
+ back: {
26
+ id: 'Back',
27
+ defaultMessage: 'Back',
28
+ },
29
+ linkstoitem: {
30
+ id: 'Links and references',
31
+ defaultMessage: 'Links and references',
32
+ },
33
+ helpLinkRelationsControlPanel: {
34
+ id: 'Overview of relations of all content items',
35
+ defaultMessage: 'Overview of relations of all content items',
36
+ },
37
+ });
38
+
39
+ const LinksToItem = (props) => {
40
+ const intl = useIntl();
41
+ const dispatch = useDispatch();
42
+ const pathname = props.location.pathname;
43
+ const itempath = getBaseUrl(pathname);
44
+
45
+ const title = useSelector((state) => state.content.data?.title || '');
46
+ const myrelations = useSelector(
47
+ (state) => state.relations.subrequests[itempath]?.relations,
48
+ );
49
+ const actions = useSelector((state) => state.actions?.actions ?? {});
50
+ const ploneSetupAction = find(actions.user, {
51
+ id: 'plone_setup',
52
+ });
53
+
54
+ useEffect(() => {
55
+ dispatch(queryRelations(null, false, itempath, null, [itempath]));
56
+ }, [dispatch, itempath]);
57
+
58
+ useEffect(() => {
59
+ if (!title) dispatch(getContent(itempath));
60
+ }, [dispatch, itempath, title]);
61
+
62
+ let links = {};
63
+ if (myrelations) {
64
+ Object.keys(myrelations).forEach((relationtype) => {
65
+ links[relationtype] = {};
66
+ myrelations[relationtype].items.forEach((item) => {
67
+ links[relationtype][item.source.UID] = item.source;
68
+ });
69
+ });
70
+ }
71
+
72
+ let links_ordered = {};
73
+ Object.keys(links).forEach((relationtype) => {
74
+ links_ordered[relationtype] = Object.values(links[relationtype]).sort(
75
+ (link) => link['@id'],
76
+ );
77
+ });
78
+
79
+ const relations_found = Object.keys(links_ordered).length > 0;
80
+ return (
81
+ <Container id="linkstoitem">
82
+ <Helmet title={intl.formatMessage(messages.linkstoitem)} />
83
+ <Segment.Group raised>
84
+ <Segment className="primary">
85
+ <FormattedMessage
86
+ id="Content that links to or references {title}"
87
+ defaultMessage="Content that links to or references {title}"
88
+ values={{ title: <q>{title}</q> }}
89
+ />
90
+ </Segment>
91
+ {relations_found ? (
92
+ <Table selectable compact singleLine attached>
93
+ {
94
+ <Table.Body>
95
+ {Object.keys(links_ordered).map((relationtype) => {
96
+ return [].concat(
97
+ [
98
+ <Table.Row>
99
+ <Table.HeaderCell>
100
+ {relationtype === 'isReferencing' ? (
101
+ <FormattedMessage
102
+ id="Linking this item with hyperlink in text"
103
+ defaultMessage="Linking this item with hyperlink in text"
104
+ />
105
+ ) : relationtype === 'relatedItems' ? (
106
+ <FormattedMessage
107
+ id="Referencing this item as related item"
108
+ defaultMessage="Referencing this item as related item"
109
+ />
110
+ ) : (
111
+ <>
112
+ <FormattedMessage
113
+ id="Referencing this item with {relationship}"
114
+ defaultMessage="Referencing this item with {relationship}"
115
+ values={{ relationship: <q>{relationtype}</q> }}
116
+ />
117
+ </>
118
+ )}
119
+ </Table.HeaderCell>
120
+ <Table.HeaderCell>
121
+ <FormattedMessage
122
+ id="Review state"
123
+ defaultMessage="Review state"
124
+ />
125
+ </Table.HeaderCell>
126
+ <Table.HeaderCell>
127
+ <FormattedMessage id="Type" defaultMessage="Type" />
128
+ </Table.HeaderCell>
129
+ </Table.Row>,
130
+ ],
131
+ links_ordered[relationtype].map((link) => {
132
+ return (
133
+ <Table.Row key={link['@id']}>
134
+ <Table.Cell>
135
+ <UniversalLink
136
+ href={link['@id']}
137
+ className={`source ${link.review_state}`}
138
+ >
139
+ <span className="label" title={link.type_title}>
140
+ {link.title}
141
+ </span>
142
+ </UniversalLink>
143
+ </Table.Cell>
144
+ <Table.Cell>
145
+ <span>{link.review_state}</span>
146
+ </Table.Cell>
147
+ <Table.Cell>
148
+ <span>{link.type_title || ''}</span>
149
+ </Table.Cell>
150
+ </Table.Row>
151
+ );
152
+ }),
153
+ );
154
+ })}
155
+ </Table.Body>
156
+ }
157
+ </Table>
158
+ ) : (
159
+ <Segment secondary>
160
+ <FormattedMessage
161
+ id="No links to this item found."
162
+ defaultMessage="No links to this item found."
163
+ />
164
+ </Segment>
165
+ )}
166
+ </Segment.Group>
167
+ {__CLIENT__ && (
168
+ <Portal node={document.getElementById('toolbar')}>
169
+ <Toolbar
170
+ pathname={pathname}
171
+ hideDefaultViewButtons
172
+ inner={
173
+ <>
174
+ <Link to={itempath} className="item">
175
+ <IconNext
176
+ name={backSVG}
177
+ className="contents circled"
178
+ size="30px"
179
+ title={intl.formatMessage(messages.back)}
180
+ />
181
+ </Link>
182
+
183
+ <>
184
+ {ploneSetupAction ? (
185
+ <Link to="/controlpanel/relations" className="relations">
186
+ <IconNext
187
+ name={settingsSVG}
188
+ className="circled"
189
+ aria-label={intl.formatMessage(
190
+ messages.helpLinkRelationsControlPanel,
191
+ )}
192
+ size="30px"
193
+ title={intl.formatMessage(
194
+ messages.helpLinkRelationsControlPanel,
195
+ )}
196
+ />
197
+ </Link>
198
+ ) : null}
199
+ </>
200
+ </>
201
+ }
202
+ />
203
+ </Portal>
204
+ )}
205
+ </Container>
206
+ );
207
+ };
208
+
209
+ export default LinksToItem;
@@ -0,0 +1,97 @@
1
+ import React from 'react';
2
+ import renderer from 'react-test-renderer';
3
+ import { Provider } from 'react-intl-redux';
4
+ import configureMockStore from 'redux-mock-store';
5
+ import thunk from 'redux-thunk';
6
+
7
+ import LinksToItem from './LinksToItem';
8
+
9
+ const middlewares = [thunk];
10
+ const mockStore = configureMockStore(middlewares);
11
+
12
+ jest.mock('react-portal', () => ({
13
+ Portal: jest.fn(() => <div id="Portal" />),
14
+ }));
15
+ jest.mock('../Toolbar/More', () => jest.fn(() => <div className="More" />));
16
+
17
+ describe('LinksToItem', () => {
18
+ it('renders "links and references" view', () => {
19
+ const store = mockStore({
20
+ relations: {
21
+ subrequests: {
22
+ '/page-1': {
23
+ relations: {
24
+ isReferencing: {
25
+ items: [
26
+ {
27
+ source: {
28
+ '@id': 'http://localhost:3000/page-basil',
29
+ '@type': 'Document',
30
+ UID: 'SOMEUID008',
31
+ description: '',
32
+ review_state: 'published',
33
+ title: 'Basil',
34
+ type_title: 'Document',
35
+ },
36
+ target: {
37
+ '@id': 'http://localhost:3000/page-tomato',
38
+ '@type': 'Document',
39
+ UID: 'SOMEUID007',
40
+ description: '',
41
+ review_state: 'published',
42
+ title: 'Tomato',
43
+ type_title: 'Document',
44
+ },
45
+ },
46
+ ],
47
+ items_total: 1,
48
+ },
49
+ relatedItems: {
50
+ items: [
51
+ {
52
+ source: {
53
+ '@id': 'http://localhost:3000/page-cucumber',
54
+ '@type': 'Document',
55
+ UID: 'SOMEUID008',
56
+ description: '',
57
+ review_state: 'published',
58
+ title: 'Cucumber',
59
+ type_title: 'Document',
60
+ },
61
+ target: {
62
+ '@id': 'http://localhost:3000/page-tomato',
63
+ '@type': 'Document',
64
+ UID: 'SOMEUID007',
65
+ description: '',
66
+ review_state: 'published',
67
+ title: 'Tomato',
68
+ type_title: 'Document',
69
+ },
70
+ },
71
+ ],
72
+ items_total: 1,
73
+ },
74
+ },
75
+ },
76
+ },
77
+ },
78
+ content: {
79
+ data: {
80
+ UID: 'SOMEUID007',
81
+ title: 'page #1',
82
+ },
83
+ },
84
+ intl: {
85
+ locale: 'en',
86
+ messages: {},
87
+ },
88
+ });
89
+ const component = renderer.create(
90
+ <Provider store={store}>
91
+ <LinksToItem location={{ pathname: '/page-1/links-to-item' }} />
92
+ </Provider>,
93
+ );
94
+ const json = component.toJSON();
95
+ expect(json).toMatchSnapshot();
96
+ });
97
+ });
@@ -53,6 +53,10 @@ const messages = defineMessages({
53
53
  id: 'URL Management',
54
54
  defaultMessage: 'URL Management',
55
55
  },
56
+ linkstoitem: {
57
+ id: 'Links and references',
58
+ defaultMessage: 'Links and references',
59
+ },
56
60
  ManageTranslations: {
57
61
  id: 'Manage Translations',
58
62
  defaultMessage: 'Manage Translations',
@@ -227,6 +231,7 @@ class More extends Component {
227
231
  const aliasesAction = find(this.props.actions.object_buttons, {
228
232
  id: 'redirection',
229
233
  });
234
+
230
235
  const { content, intl } = this.props;
231
236
 
232
237
  const dateOptions = {
@@ -317,6 +322,16 @@ class More extends Component {
317
322
  </li>
318
323
  )}
319
324
  </Plug>
325
+ {path !== '' && !config.settings.excludeLinksAndReferencesMenuItem && (
326
+ <Plug pluggable="toolbar-more-menu-list" id="linkstoitems">
327
+ <li>
328
+ <Link to={`${path}/links-to-item`}>
329
+ {this.props.intl.formatMessage(messages.linkstoitem)}
330
+ <Icon name={rightArrowSVG} size="24px" />
331
+ </Link>
332
+ </li>
333
+ </Plug>
334
+ )}
320
335
  <Plug pluggable="toolbar-more-menu-list" id="rules">
321
336
  {rulesAction && (
322
337
  <li>
@@ -427,7 +427,7 @@ class RecurrenceWidget extends Component {
427
427
  break;
428
428
  }
429
429
 
430
- if (value) {
430
+ if (value === 0 || value) {
431
431
  //set value
432
432
  values[field] = value;
433
433
  } else {
@@ -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, injectIntl } from 'react-intl';
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
- * Breadcrumbs container class.
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
- componentDidMount() {
53
- if (!hasApiExpander('breadcrumbs', getBaseUrl(this.props.pathname))) {
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
- * Component will receive props
60
- * @method componentWillReceiveProps
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
- * Render method.
74
- * @method render
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 compose(
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 { BreadcrumbsComponent } from './Breadcrumbs';
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 location={{ pathname: '/folder2/folder21/doc212' }}>
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
  );