@plone/volto 17.0.0-alpha.24 → 17.0.0-alpha.26

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 (130) hide show
  1. package/.yarn/install-state.gz +0 -0
  2. package/CHANGELOG.md +92 -4
  3. package/CONTRIBUTING.md +5 -1
  4. package/README.md +9 -7
  5. package/cypress/support/commands.js +12 -9
  6. package/cypress.config.js +1 -0
  7. package/locales/ca/LC_MESSAGES/volto.po +41 -15
  8. package/locales/ca.json +1 -1
  9. package/locales/de/LC_MESSAGES/volto.po +41 -15
  10. package/locales/de.json +1 -1
  11. package/locales/en/LC_MESSAGES/volto.po +40 -14
  12. package/locales/en.json +1 -1
  13. package/locales/es/LC_MESSAGES/volto.po +69 -43
  14. package/locales/es.json +1 -1
  15. package/locales/eu/LC_MESSAGES/volto.po +40 -14
  16. package/locales/eu.json +1 -1
  17. package/locales/fi/LC_MESSAGES/volto.po +40 -14
  18. package/locales/fi.json +1 -1
  19. package/locales/fr/LC_MESSAGES/volto.po +41 -15
  20. package/locales/fr.json +1 -1
  21. package/locales/it/LC_MESSAGES/volto.po +40 -14
  22. package/locales/it.json +1 -1
  23. package/locales/ja/LC_MESSAGES/volto.po +40 -14
  24. package/locales/ja.json +1 -1
  25. package/locales/nl/LC_MESSAGES/volto.po +41 -15
  26. package/locales/nl.json +1 -1
  27. package/locales/pt/LC_MESSAGES/volto.po +41 -15
  28. package/locales/pt.json +1 -1
  29. package/locales/pt_BR/LC_MESSAGES/volto.po +40 -14
  30. package/locales/pt_BR.json +1 -1
  31. package/locales/ro/LC_MESSAGES/volto.po +41 -15
  32. package/locales/ro.json +1 -1
  33. package/locales/volto.pot +41 -15
  34. package/locales/zh_CN/LC_MESSAGES/volto.po +41 -15
  35. package/locales/zh_CN.json +1 -1
  36. package/package.json +4 -4
  37. package/packages/volto-slate/build/messages/src/blocks/Table/TableBlockEdit.json +90 -0
  38. package/packages/volto-slate/build/messages/src/blocks/Text/DefaultTextBlockEditor.json +6 -0
  39. package/packages/volto-slate/build/messages/src/blocks/Text/DetachedTextBlockEditor.json +6 -0
  40. package/packages/volto-slate/build/messages/src/blocks/Text/SlashMenu.json +6 -0
  41. package/packages/volto-slate/build/messages/src/editor/plugins/AdvancedLink/index.json +10 -0
  42. package/packages/volto-slate/build/messages/src/editor/plugins/Link/index.json +10 -0
  43. package/packages/volto-slate/build/messages/src/editor/plugins/Table/index.json +30 -0
  44. package/packages/volto-slate/build/messages/src/elementEditor/messages.json +10 -0
  45. package/packages/volto-slate/build/messages/src/widgets/HtmlSlateWidget.json +6 -0
  46. package/packages/volto-slate/build/messages/src/widgets/RichTextWidgetView.json +6 -0
  47. package/packages/volto-slate/package.json +1 -1
  48. package/packages/volto-slate/src/editor/render.jsx +2 -3
  49. package/src/actions/index.js +4 -0
  50. package/src/actions/navroot/navroot.js +16 -0
  51. package/src/actions/navroot/navroot.test.js +15 -0
  52. package/src/actions/relations/relations.js +17 -0
  53. package/src/actions/site/site.js +16 -0
  54. package/src/actions/site/site.test.js +15 -0
  55. package/src/actions/userSession/userSession.js +17 -1
  56. package/src/components/manage/Blocks/Block/Settings.jsx +2 -0
  57. package/src/components/manage/Blocks/Block/Settings.test.jsx +90 -0
  58. package/src/components/manage/Blocks/Image/schema.js +5 -1
  59. package/src/components/manage/Blocks/Listing/withQuerystringResults.jsx +18 -11
  60. package/src/components/manage/Blocks/Search/hocs/withSearch.jsx +42 -25
  61. package/src/components/manage/Blocks/ToC/View.jsx +75 -13
  62. package/src/components/manage/Blocks/ToC/variations/DefaultTocRenderer.jsx +4 -13
  63. package/src/components/manage/Blocks/ToC/variations/DefaultTocRenderer.test.jsx +44 -0
  64. package/src/components/manage/Contents/Contents.jsx +27 -0
  65. package/src/components/manage/Controlpanels/Groups/GroupsControlpanel.jsx +65 -38
  66. package/src/components/manage/Controlpanels/Relations/BrokenRelations.jsx +11 -9
  67. package/src/components/manage/Controlpanels/Relations/Relations.jsx +3 -3
  68. package/src/components/manage/Controlpanels/Relations/RelationsListing.jsx +8 -7
  69. package/src/components/manage/Controlpanels/Relations/RelationsMatrix.jsx +15 -9
  70. package/src/components/manage/Controlpanels/Rules/AddRule.jsx +1 -1
  71. package/src/components/manage/Controlpanels/Rules/EditRule.jsx +1 -1
  72. package/src/components/manage/Controlpanels/Users/RenderUsers.jsx +95 -5
  73. package/src/components/manage/Controlpanels/Users/UsersControlpanel.jsx +127 -99
  74. package/src/components/manage/Diff/DiffField.jsx +25 -1
  75. package/src/components/manage/Form/BlockDataForm.jsx +3 -2
  76. package/src/components/manage/Form/BlockDataForm.test.jsx +34 -2
  77. package/src/components/manage/LinksToItem/LinksToItem.jsx +1 -1
  78. package/src/components/manage/LinksToItem/LinksToItem.test.jsx +5 -2
  79. package/src/components/manage/Messages/Messages.jsx +32 -99
  80. package/src/components/manage/Messages/Messages.test.jsx +0 -1
  81. package/src/components/manage/Sharing/Sharing.jsx +50 -21
  82. package/src/components/manage/UniversalLink/UniversalLink.jsx +4 -6
  83. package/src/components/manage/Widgets/ArrayWidget.jsx +3 -1
  84. package/src/components/manage/Widgets/ArrayWidget.test.jsx +45 -1
  85. package/src/components/manage/Widgets/FormFieldWrapper.jsx +1 -1
  86. package/src/components/manage/Widgets/RegistryImageWidget.jsx +210 -0
  87. package/src/components/manage/Widgets/RegistryImageWidget.test.jsx +91 -0
  88. package/src/components/manage/Widgets/SelectWidget.jsx +15 -1
  89. package/src/components/manage/Widgets/SelectWidget.test.jsx +45 -1
  90. package/src/components/theme/Comments/Comment.stories.jsx +84 -0
  91. package/src/components/theme/Comments/Comments.jsx +273 -378
  92. package/src/components/theme/ContentMetadataTags/ContentMetadataTags.jsx +37 -3
  93. package/src/components/theme/Login/Login.jsx +159 -241
  94. package/src/components/theme/Logo/Logo.Multilingual.test.jsx +131 -1
  95. package/src/components/theme/Logo/Logo.jsx +35 -29
  96. package/src/components/theme/Logo/Logo.test.jsx +135 -1
  97. package/src/components/theme/Logout/Logout.jsx +36 -83
  98. package/src/components/theme/Navigation/Navigation.jsx +86 -171
  99. package/src/components/theme/Search/SearchTags.jsx +30 -60
  100. package/src/components/theme/SearchWidget/SearchWidget.jsx +15 -3
  101. package/src/components/theme/SearchWidget/SearchWidget.test.jsx +8 -0
  102. package/src/components/theme/Sitemap/Sitemap.jsx +24 -13
  103. package/src/components/theme/Sitemap/Sitemap.test.jsx +23 -2
  104. package/src/components/theme/View/View.jsx +2 -0
  105. package/src/config/ControlPanels.js +0 -1
  106. package/src/config/Widgets.jsx +2 -0
  107. package/src/config/index.js +15 -3
  108. package/src/constants/ActionTypes.js +4 -0
  109. package/src/express-middleware/images.js +1 -0
  110. package/src/helpers/MessageLabels/MessageLabels.js +26 -4
  111. package/src/helpers/Site/index.js +21 -0
  112. package/src/helpers/index.js +1 -0
  113. package/src/reducers/index.js +4 -0
  114. package/src/reducers/navroot/navroot.js +79 -0
  115. package/src/reducers/navroot/navroot.test.js +110 -0
  116. package/src/reducers/relations/relations.js +74 -46
  117. package/src/reducers/site/site.js +51 -0
  118. package/src/reducers/site/site.test.js +67 -0
  119. package/src/reducers/userSession/userSession.js +15 -1
  120. package/src/server.jsx +9 -0
  121. package/test-setup-config.js +1 -0
  122. package/theme/themes/pastanaga/collections/form.overrides +46 -0
  123. package/theme/themes/pastanaga/elements/input.overrides +10 -0
  124. package/theme/themes/pastanaga/elements/label.overrides +10 -0
  125. package/theme/themes/pastanaga/extras/login.less +3 -0
  126. package/webpack-plugins/webpack-less-plugin.js +19 -0
  127. package/.gitignore~ +0 -71
  128. package/news/4547.breaking~ +0 -1
  129. package/package.json~ +0 -444
  130. package/src/config/index.js~ +0 -223
@@ -0,0 +1,15 @@
1
+ import { getSite } from './site';
2
+ import { GET_SITE } from '@plone/volto/constants/ActionTypes';
3
+
4
+ describe('Site action', () => {
5
+ describe('getSite', () => {
6
+ it('should create an action to get the site config', () => {
7
+ const url = 'http://localhost';
8
+ const action = getSite(url);
9
+
10
+ expect(action.type).toEqual(GET_SITE);
11
+ expect(action.request.op).toEqual('get');
12
+ expect(action.request.path).toEqual(`/@site`);
13
+ });
14
+ });
15
+ });
@@ -3,7 +3,12 @@
3
3
  * @module actions/search/search
4
4
  */
5
5
 
6
- import { LOGIN, LOGIN_RENEW, LOGOUT } from '@plone/volto/constants/ActionTypes';
6
+ import {
7
+ LOGIN,
8
+ LOGIN_RENEW,
9
+ LOGOUT,
10
+ RESET_LOGIN_REQUEST,
11
+ } from '@plone/volto/constants/ActionTypes';
7
12
 
8
13
  /**
9
14
  * Login function.
@@ -52,3 +57,14 @@ export function logout() {
52
57
  },
53
58
  };
54
59
  }
60
+
61
+ /**
62
+ * Reset login request data function
63
+ * @function resetLoginRequest
64
+ * @returns {Object} Get content action
65
+ */
66
+ export function resetLoginRequest() {
67
+ return {
68
+ type: RESET_LOGIN_REQUEST,
69
+ };
70
+ }
@@ -14,7 +14,9 @@ const Settings = ({ data, block, onChangeBlock, schema }) => {
14
14
  [id]: value,
15
15
  });
16
16
  }}
17
+ onChangeBlock={onChangeBlock}
17
18
  formData={data}
19
+ applySchemaEnhancers={false}
18
20
  />
19
21
  );
20
22
  };
@@ -0,0 +1,90 @@
1
+ import React from 'react';
2
+ import Settings from './Settings';
3
+ import { render } from '@testing-library/react';
4
+ import '@testing-library/jest-dom/extend-expect';
5
+ import configureStore from 'redux-mock-store';
6
+ import config from '@plone/volto/registry';
7
+ import { Provider } from 'react-intl-redux';
8
+
9
+ const mockStore = configureStore();
10
+
11
+ const withStateManagement = (Component) => ({ ...props }) => {
12
+ const [formData, onChangeFormData] = React.useState(props.formData || {});
13
+ const onChangeField = (id, value) => {
14
+ onChangeFormData({ ...formData, [id]: value });
15
+ };
16
+
17
+ // NOTE: onChangeBlock here is not "really" implemented
18
+
19
+ return (
20
+ <Component
21
+ {...props}
22
+ onChangeField={onChangeField}
23
+ onChangeBlock={(block, data) => onChangeFormData(data)}
24
+ formData={formData}
25
+ />
26
+ );
27
+ };
28
+
29
+ beforeAll(() => {
30
+ config.widgets = {
31
+ id: {},
32
+ widget: {},
33
+ vocabulary: {},
34
+ choices: (props) => (
35
+ <div className="SelectWidget">{JSON.stringify(props.choices)}</div>
36
+ ),
37
+ type: {},
38
+ default: () => <div className="TextWidget" />,
39
+ };
40
+
41
+ config.blocks.blocksConfig = {
42
+ ...config.blocks.blocksConfig,
43
+
44
+ testBlock: {
45
+ id: 'testBlock',
46
+ variations: [
47
+ {
48
+ id: 'default',
49
+ title: 'Default',
50
+ isDefault: true,
51
+ },
52
+ {
53
+ id: 'extra',
54
+ title: 'Extra',
55
+ },
56
+ ],
57
+ },
58
+ };
59
+ });
60
+
61
+ describe('Settings', () => {
62
+ it('renders block settings form without schema enhancers', () => {
63
+ const WrappedSettings = withStateManagement(Settings);
64
+ const store = mockStore({
65
+ intl: {
66
+ locale: 'en',
67
+ messages: {},
68
+ },
69
+ });
70
+ const testSchema = {
71
+ fieldsets: [{ title: 'Default', id: 'default', fields: [] }],
72
+ properties: {},
73
+ required: [],
74
+ };
75
+ const data = {
76
+ '@type': 'testBlock',
77
+ };
78
+ const { container } = render(
79
+ <Provider store={store}>
80
+ <WrappedSettings
81
+ data={data}
82
+ block="test"
83
+ schema={testSchema}
84
+ onChangeBlock={(block, value) => {}}
85
+ />
86
+ </Provider>,
87
+ );
88
+ expect(container).toMatchSnapshot();
89
+ });
90
+ });
@@ -33,6 +33,10 @@ const messages = defineMessages({
33
33
  id: 'Alt text hint link text',
34
34
  defaultMessage: 'Describe the purpose of the image.',
35
35
  },
36
+ linkSettings: {
37
+ id: 'Link settings',
38
+ defaultMessage: 'Link settings',
39
+ },
36
40
  });
37
41
 
38
42
  export function ImageSchema({ formData, intl }) {
@@ -47,7 +51,7 @@ export function ImageSchema({ formData, intl }) {
47
51
  ? [
48
52
  {
49
53
  id: 'link_settings',
50
- title: 'Link settings',
54
+ title: intl.formatMessage(messages.linkSettings),
51
55
  fields: ['href', 'openLinkInNewTab'],
52
56
  },
53
57
  ]
@@ -23,7 +23,7 @@ export default function withQuerystringResults(WrappedComponent) {
23
23
  } = props;
24
24
  const { settings } = config;
25
25
  const querystring = data.querystring || data; // For backwards compat with data saved before Blocks schema. Note, this is also how the Search block passes data to ListingBody
26
-
26
+ const subrequestID = content?.UID ? `${content?.UID}-${id}` : id;
27
27
  const { b_size = settings.defaultPageSize } = querystring; // batchsize
28
28
 
29
29
  // save the path so it won't trigger dispatch on eager router location change
@@ -52,31 +52,33 @@ export default function withQuerystringResults(WrappedComponent) {
52
52
 
53
53
  const folderItems = content?.is_folderish ? content.items : [];
54
54
  const hasQuery = querystring?.query?.length > 0;
55
- const hasLoaded = hasQuery ? querystringResults?.[id]?.loaded : true;
55
+ const hasLoaded = hasQuery
56
+ ? querystringResults?.[subrequestID]?.loaded
57
+ : true;
56
58
 
57
59
  const listingItems = hasQuery
58
- ? querystringResults?.[id]?.items || []
60
+ ? querystringResults?.[subrequestID]?.items || []
59
61
  : folderItems;
60
62
 
61
63
  const showAsFolderListing = !hasQuery && content?.items_total > b_size;
62
64
  const showAsQueryListing =
63
- hasQuery && querystringResults?.[id]?.total > b_size;
65
+ hasQuery && querystringResults?.[subrequestID]?.total > b_size;
64
66
 
65
67
  const totalPages = showAsFolderListing
66
68
  ? Math.ceil(content.items_total / b_size)
67
69
  : showAsQueryListing
68
- ? Math.ceil(querystringResults[id].total / b_size)
70
+ ? Math.ceil(querystringResults[subrequestID].total / b_size)
69
71
  : 0;
70
72
 
71
73
  const prevBatch = showAsFolderListing
72
74
  ? content.batching?.prev
73
75
  : showAsQueryListing
74
- ? querystringResults[id].batching?.prev
76
+ ? querystringResults[subrequestID].batching?.prev
75
77
  : null;
76
78
  const nextBatch = showAsFolderListing
77
79
  ? content.batching?.next
78
80
  : showAsQueryListing
79
- ? querystringResults[id].batching?.next
81
+ ? querystringResults[subrequestID].batching?.next
80
82
  : null;
81
83
 
82
84
  const isImageGallery =
@@ -86,7 +88,12 @@ export default function withQuerystringResults(WrappedComponent) {
86
88
  useDeepCompareEffect(() => {
87
89
  if (hasQuery) {
88
90
  dispatch(
89
- getQueryStringResults(initialPath, adaptedQuery, id, currentPage),
91
+ getQueryStringResults(
92
+ initialPath,
93
+ adaptedQuery,
94
+ subrequestID,
95
+ currentPage,
96
+ ),
90
97
  );
91
98
  } else if (isImageGallery && !hasQuery) {
92
99
  // when used as image gallery, it doesn't need a query to list children
@@ -104,7 +111,7 @@ export default function withQuerystringResults(WrappedComponent) {
104
111
  },
105
112
  ],
106
113
  },
107
- id,
114
+ subrequestID,
108
115
  ),
109
116
  );
110
117
  } else {
@@ -113,7 +120,7 @@ export default function withQuerystringResults(WrappedComponent) {
113
120
  adaptedQueryRef.current = adaptedQuery;
114
121
  currentPageRef.current = currentPage;
115
122
  }, [
116
- id,
123
+ subrequestID,
117
124
  isImageGallery,
118
125
  adaptedQuery,
119
126
  hasQuery,
@@ -126,7 +133,7 @@ export default function withQuerystringResults(WrappedComponent) {
126
133
  <WrappedComponent
127
134
  {...props}
128
135
  onPaginationChange={(e, { activePage }) => setCurrentPage(activePage)}
129
- total={querystringResults?.[id]?.total}
136
+ total={querystringResults?.[subrequestID]?.total}
130
137
  batch_size={b_size}
131
138
  currentPage={currentPage}
132
139
  totalPages={totalPages}
@@ -5,6 +5,8 @@ import { useLocation, useHistory } from 'react-router-dom';
5
5
 
6
6
  import { resolveExtension } from '@plone/volto/helpers/Extensions/withBlockExtensions';
7
7
  import config from '@plone/volto/registry';
8
+ import { usePrevious } from '@plone/volto/helpers';
9
+ import { isEqual } from 'lodash';
8
10
 
9
11
  function getDisplayName(WrappedComponent) {
10
12
  return WrappedComponent.displayName || WrappedComponent.name || 'Component';
@@ -253,35 +255,50 @@ const withSearch = (options) => (WrappedComponent) => {
253
255
  const multiFacets = data.facets
254
256
  ?.filter((facet) => facet?.multiple)
255
257
  .map((facet) => facet?.field?.value);
256
- const [facets, setFacets] = React.useState(
257
- Object.assign(
258
- {},
259
- ...urlQuery.map(({ i, v }) => ({ [i]: v })), // TODO: the 'o' should be kept. This would be a major refactoring of the facets
260
-
261
- // support for simple filters like ?Subject=something
262
- // TODO: since the move to hash params this is no longer working.
263
- // We'd have to treat the location.search and manage it just like the
264
- // hash, to support it. We can read it, but we'd have to reset it as
265
- // well, so at that point what's the difference to the hash?
266
- ...configuredFacets.map((f) =>
267
- locationSearchData[f]
268
- ? {
269
- [f]:
270
- multiFacets.indexOf(f) > -1
271
- ? [locationSearchData[f]]
272
- : locationSearchData[f],
273
- }
274
- : {},
275
- ),
276
- ),
277
- );
258
+ const [facets, setFacets] = React.useState({});
259
+ const previousUrlQuery = usePrevious(urlQuery);
260
+
261
+ React.useEffect(() => {
262
+ if (!isEqual(urlQuery, previousUrlQuery)) {
263
+ setFacets(
264
+ Object.assign(
265
+ {},
266
+ ...urlQuery.map(({ i, v }) => ({ [i]: v })), // TODO: the 'o' should be kept. This would be a major refactoring of the facets
267
+
268
+ // support for simple filters like ?Subject=something
269
+ // TODO: since the move to hash params this is no longer working.
270
+ // We'd have to treat the location.search and manage it just like the
271
+ // hash, to support it. We can read it, but we'd have to reset it as
272
+ // well, so at that point what's the difference to the hash?
273
+ ...configuredFacets.map((f) =>
274
+ locationSearchData[f]
275
+ ? {
276
+ [f]:
277
+ multiFacets.indexOf(f) > -1
278
+ ? [locationSearchData[f]]
279
+ : locationSearchData[f],
280
+ }
281
+ : {},
282
+ ),
283
+ ),
284
+ );
285
+ }
286
+ }, [
287
+ urlQuery,
288
+ configuredFacets,
289
+ locationSearchData,
290
+ multiFacets,
291
+ previousUrlQuery,
292
+ ]);
278
293
 
279
294
  const [sortOn, setSortOn] = React.useState(data?.query?.sort_on);
280
295
  const [sortOrder, setSortOrder] = React.useState(data?.query?.sort_order);
281
296
 
282
- const [searchData, setSearchData] = React.useState(
283
- getInitialState(data, facets, urlSearchText, id),
284
- );
297
+ const [searchData, setSearchData] = React.useState({});
298
+
299
+ React.useEffect(() => {
300
+ setSearchData(getInitialState(data, facets, urlSearchText, id));
301
+ }, [facets, data, urlSearchText, id]);
285
302
 
286
303
  const timeoutRef = React.useRef();
287
304
  const facetSettings = data?.facets;
@@ -16,16 +16,79 @@ import {
16
16
  getBlocksLayoutFieldname,
17
17
  } from '@plone/volto/helpers';
18
18
 
19
+ export const getBlocksTocEntries = (properties, tocData) => {
20
+ const blocksFieldName = getBlocksFieldname(properties);
21
+ const blocksLayoutFieldname = getBlocksLayoutFieldname(properties);
22
+
23
+ const blocks = properties[blocksFieldName];
24
+ const blocks_layout = properties[blocksLayoutFieldname];
25
+
26
+ const levels =
27
+ tocData.levels?.length > 0
28
+ ? tocData.levels.map((l) => parseInt(l.slice(1)))
29
+ : [1, 2, 3, 4, 5, 6];
30
+ let rootLevel = Infinity;
31
+ let blocksFormEntries = [];
32
+ let tocEntries = {};
33
+ let tocEntriesLayout = [];
34
+
35
+ blocks_layout.items.forEach((id) => {
36
+ const block = blocks[id];
37
+ const blockConfig = config.blocks.blocksConfig[block['@type']];
38
+
39
+ if (!block || !blockConfig) {
40
+ return null;
41
+ }
42
+ if (!blockConfig.tocEntries && !blockConfig.tocEntry) {
43
+ return null;
44
+ }
45
+
46
+ const blockTocEntry = blockConfig.tocEntry?.(block, tocData);
47
+
48
+ const blockTocEntries = [
49
+ ...(blockConfig.tocEntries?.(block, tocData) ||
50
+ (blockTocEntry ? [blockTocEntry] : [])),
51
+ ];
52
+
53
+ blocksFormEntries = [...blocksFormEntries, ...blockTocEntries];
54
+
55
+ blockTocEntries.forEach((entry, index) => {
56
+ const i = `${id}-${index}`;
57
+ const level = entry[0];
58
+ const title = entry[1];
59
+ const items = [];
60
+ if (!level || !levels.includes(level)) return;
61
+ tocEntriesLayout.push(i);
62
+ tocEntries[i] = {
63
+ level,
64
+ title: title || block.plaintext,
65
+ items,
66
+ id: i,
67
+ };
68
+ if (level < rootLevel) {
69
+ rootLevel = level;
70
+ }
71
+ });
72
+ });
73
+
74
+ return {
75
+ rootLevel,
76
+ blocksFormEntries,
77
+ tocEntries,
78
+ tocEntriesLayout,
79
+ };
80
+ };
81
+
19
82
  /**
20
83
  * View toc block class.
21
84
  * @class View
22
85
  * @extends Component
23
86
  */
24
87
  const View = (props) => {
25
- const { properties, data } = props;
88
+ const { data } = props;
26
89
  const { variation } = props;
27
- const blocksFieldname = getBlocksFieldname(properties);
28
- const blocksLayoutFieldname = getBlocksLayoutFieldname(properties);
90
+ const metadata = props.metadata || props.properties;
91
+ const blocksFieldname = getBlocksFieldname(metadata);
29
92
  const levels = React.useMemo(
30
93
  () =>
31
94
  data.levels?.length > 0
@@ -34,14 +97,15 @@ const View = (props) => {
34
97
  [data],
35
98
  );
36
99
  const tocEntries = React.useMemo(() => {
37
- let rootLevel = Infinity;
38
100
  let entries = [];
39
101
  let prevEntry = {};
40
- let tocEntries = {};
41
- let tocEntriesLayout = [];
102
+ const { rootLevel, tocEntries, tocEntriesLayout } = getBlocksTocEntries(
103
+ metadata,
104
+ data,
105
+ );
42
106
 
43
- properties[blocksLayoutFieldname].items.forEach((id) => {
44
- const block = properties[blocksFieldname][id];
107
+ tocEntriesLayout.forEach((id) => {
108
+ const block = metadata[blocksFieldname][id];
45
109
  if (typeof block === 'undefined') {
46
110
  return null;
47
111
  }
@@ -50,6 +114,7 @@ const View = (props) => {
50
114
  block,
51
115
  data,
52
116
  );
117
+
53
118
  if (entry) {
54
119
  const level = entry[0];
55
120
  const title = entry[1];
@@ -64,9 +129,6 @@ const View = (props) => {
64
129
  override_toc: block.override_toc,
65
130
  plaintext: block.plaintext,
66
131
  };
67
- if (level < rootLevel) {
68
- rootLevel = level;
69
- }
70
132
  }
71
133
  });
72
134
 
@@ -98,7 +160,7 @@ const View = (props) => {
98
160
  });
99
161
 
100
162
  return entries;
101
- }, [data, levels, properties, blocksFieldname, blocksLayoutFieldname]);
163
+ }, [data, levels, metadata, blocksFieldname]);
102
164
 
103
165
  const Renderer = variation?.view;
104
166
  return (
@@ -108,7 +170,7 @@ const View = (props) => {
108
170
  )}
109
171
 
110
172
  {Renderer ? (
111
- <Renderer {...props} tocEntries={tocEntries} properties={properties} />
173
+ <Renderer {...props} tocEntries={tocEntries} metadata={metadata} />
112
174
  ) : (
113
175
  <div>View extension not found</div>
114
176
  )}
@@ -8,11 +8,10 @@ import PropTypes from 'prop-types';
8
8
  import { map } from 'lodash';
9
9
  import { List } from 'semantic-ui-react';
10
10
  import { FormattedMessage, injectIntl } from 'react-intl';
11
- import { useHistory } from 'react-router-dom';
12
- import AnchorLink from 'react-anchor-link-smooth-scroll';
13
11
  import Slugger from 'github-slugger';
12
+ import { UniversalLink } from '@plone/volto/components';
14
13
 
15
- const RenderListItems = ({ items, data, history }) => {
14
+ const RenderListItems = ({ items, data }) => {
16
15
  return map(items, (item) => {
17
16
  const { id, level, title, override_toc, plaintext } = item;
18
17
  const slug = override_toc
@@ -21,14 +20,7 @@ const RenderListItems = ({ items, data, history }) => {
21
20
  return (
22
21
  item && (
23
22
  <List.Item key={id} className={`item headline-${level}`} as="li">
24
- <AnchorLink
25
- href={`#${slug}`}
26
- onClick={(e) => {
27
- history.push({ hash: slug });
28
- }}
29
- >
30
- {title}
31
- </AnchorLink>
23
+ <UniversalLink href={`#${slug}`}>{title}</UniversalLink>
32
24
  {item.items?.length > 0 && (
33
25
  <List
34
26
  ordered={data.ordered}
@@ -50,7 +42,6 @@ const RenderListItems = ({ items, data, history }) => {
50
42
  * @extends Component
51
43
  */
52
44
  const View = ({ data, tocEntries }) => {
53
- const history = useHistory();
54
45
  return (
55
46
  <>
56
47
  {data.title && !data.hide_title ? (
@@ -70,7 +61,7 @@ const View = ({ data, tocEntries }) => {
70
61
  bulleted={!data.ordered}
71
62
  as={data.ordered ? 'ol' : 'ul'}
72
63
  >
73
- <RenderListItems items={tocEntries} data={data} history={history} />
64
+ <RenderListItems items={tocEntries} data={data} />
74
65
  </List>
75
66
  </>
76
67
  );
@@ -0,0 +1,44 @@
1
+ import renderer from 'react-test-renderer';
2
+ import configureStore from 'redux-mock-store';
3
+ import { Provider } from 'react-intl-redux';
4
+ import { MemoryRouter } from 'react-router-dom';
5
+ import DefaultTocRenderer from './DefaultTocRenderer';
6
+
7
+ const mockStore = configureStore();
8
+
9
+ const data = { '@type': 'toc', variation: 'default' };
10
+
11
+ const tocEntries = [
12
+ {
13
+ level: 2,
14
+ title: 'Hello this is a sample page',
15
+ items: [
16
+ {
17
+ level: 3,
18
+ title: 'Test level 3',
19
+ items: [],
20
+ id: 'be612682-6df9-4a5e-b3a1-9dec5d82ae14',
21
+ parentId: '3a8bff13-3245-44f6-8a35-e0defef5898e',
22
+ },
23
+ ],
24
+ id: '3a8bff13-3245-44f6-8a35-e0defef5898e',
25
+ },
26
+ ];
27
+
28
+ test('renders a default toc renderer component', () => {
29
+ const store = mockStore({
30
+ intl: {
31
+ locale: 'en',
32
+ messages: {},
33
+ },
34
+ });
35
+ const component = renderer.create(
36
+ <Provider store={store}>
37
+ <MemoryRouter>
38
+ <DefaultTocRenderer data={data} tocEntries={tocEntries} />
39
+ </MemoryRouter>
40
+ </Provider>,
41
+ );
42
+ const json = component.toJSON();
43
+ expect(json).toMatchSnapshot();
44
+ });
@@ -1334,6 +1334,9 @@ class Contents extends Component {
1334
1334
  as={Button}
1335
1335
  onClick={this.upload}
1336
1336
  className="upload"
1337
+ aria-label={this.props.intl.formatMessage(
1338
+ messages.upload,
1339
+ )}
1337
1340
  >
1338
1341
  <Icon
1339
1342
  name={uploadSVG}
@@ -1358,6 +1361,9 @@ class Contents extends Component {
1358
1361
  as={Button}
1359
1362
  onClick={this.rename}
1360
1363
  disabled={!selected}
1364
+ aria-label={this.props.intl.formatMessage(
1365
+ messages.rename,
1366
+ )}
1361
1367
  >
1362
1368
  <Icon
1363
1369
  name={renameSVG}
@@ -1380,6 +1386,9 @@ class Contents extends Component {
1380
1386
  as={Button}
1381
1387
  onClick={this.workflow}
1382
1388
  disabled={!selected}
1389
+ aria-label={this.props.intl.formatMessage(
1390
+ messages.state,
1391
+ )}
1383
1392
  >
1384
1393
  <Icon
1385
1394
  name={semaphoreSVG}
@@ -1402,6 +1411,9 @@ class Contents extends Component {
1402
1411
  as={Button}
1403
1412
  onClick={this.tags}
1404
1413
  disabled={!selected}
1414
+ aria-label={this.props.intl.formatMessage(
1415
+ messages.tags,
1416
+ )}
1405
1417
  >
1406
1418
  <Icon
1407
1419
  name={tagSVG}
@@ -1425,6 +1437,9 @@ class Contents extends Component {
1425
1437
  as={Button}
1426
1438
  onClick={this.properties}
1427
1439
  disabled={!selected}
1440
+ aria-label={this.props.intl.formatMessage(
1441
+ messages.properties,
1442
+ )}
1428
1443
  >
1429
1444
  <Icon
1430
1445
  name={propertiesSVG}
@@ -1449,6 +1464,9 @@ class Contents extends Component {
1449
1464
  as={Button}
1450
1465
  onClick={this.cut}
1451
1466
  disabled={!selected}
1467
+ aria-label={this.props.intl.formatMessage(
1468
+ messages.cut,
1469
+ )}
1452
1470
  >
1453
1471
  <Icon
1454
1472
  name={cutSVG}
@@ -1471,6 +1489,9 @@ class Contents extends Component {
1471
1489
  as={Button}
1472
1490
  onClick={this.copy}
1473
1491
  disabled={!selected}
1492
+ aria-label={this.props.intl.formatMessage(
1493
+ messages.copy,
1494
+ )}
1474
1495
  >
1475
1496
  <Icon
1476
1497
  name={copySVG}
@@ -1494,6 +1515,9 @@ class Contents extends Component {
1494
1515
  as={Button}
1495
1516
  onClick={this.paste}
1496
1517
  disabled={!this.props.action}
1518
+ aria-label={this.props.intl.formatMessage(
1519
+ messages.paste,
1520
+ )}
1497
1521
  >
1498
1522
  <Icon
1499
1523
  name={pasteSVG}
@@ -1517,6 +1541,9 @@ class Contents extends Component {
1517
1541
  as={Button}
1518
1542
  onClick={this.delete}
1519
1543
  disabled={!selected}
1544
+ aria-label={this.props.intl.formatMessage(
1545
+ messages.delete,
1546
+ )}
1520
1547
  >
1521
1548
  <Icon
1522
1549
  name={deleteSVG}