@plone/volto 17.0.0-alpha.1 → 17.0.0-alpha.10

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 (179) hide show
  1. package/.yarn/install-state.gz +0 -0
  2. package/CHANGELOG.md +396 -16
  3. package/CONTRIBUTING.md +1 -1
  4. package/README.md +12 -15
  5. package/addon-registry.js +34 -0
  6. package/create-theme-addons-loader.js +79 -0
  7. package/cypress/support/commands.js +25 -0
  8. package/locales/ca/LC_MESSAGES/volto.po +187 -6
  9. package/locales/ca.json +1 -1
  10. package/locales/de/LC_MESSAGES/volto.po +206 -25
  11. package/locales/de.json +1 -1
  12. package/locales/en/LC_MESSAGES/volto.po +186 -5
  13. package/locales/en.json +1 -1
  14. package/locales/es/LC_MESSAGES/volto.po +187 -6
  15. package/locales/es.json +1 -1
  16. package/locales/eu/LC_MESSAGES/volto.po +187 -6
  17. package/locales/eu.json +1 -1
  18. package/locales/fi/LC_MESSAGES/volto.po +4792 -0
  19. package/locales/fi.json +1 -1
  20. package/locales/fr/LC_MESSAGES/volto.po +187 -6
  21. package/locales/fr.json +1 -1
  22. package/locales/it/LC_MESSAGES/volto.po +187 -6
  23. package/locales/it.json +1 -1
  24. package/locales/ja/LC_MESSAGES/volto.po +187 -6
  25. package/locales/ja.json +1 -1
  26. package/locales/nl/LC_MESSAGES/volto.po +842 -649
  27. package/locales/nl.json +1 -1
  28. package/locales/pt/LC_MESSAGES/volto.po +187 -6
  29. package/locales/pt.json +1 -1
  30. package/locales/pt_BR/LC_MESSAGES/volto.po +195 -14
  31. package/locales/pt_BR.json +1 -1
  32. package/locales/ro/LC_MESSAGES/volto.po +187 -6
  33. package/locales/ro.json +1 -1
  34. package/locales/volto.pot +187 -6
  35. package/locales/zh_CN/LC_MESSAGES/volto.po +187 -6
  36. package/locales/zh_CN.json +1 -1
  37. package/package-why.json +0 -1
  38. package/package.json +9 -8
  39. package/packages/volto-slate/build/messages/src/blocks/Table/TableBlockEdit.json +1 -1
  40. package/packages/volto-slate/build/messages/src/blocks/Text/DefaultTextBlockEditor.json +1 -1
  41. package/packages/volto-slate/build/messages/src/blocks/Text/DetachedTextBlockEditor.json +1 -1
  42. package/packages/volto-slate/build/messages/src/blocks/Text/SlashMenu.json +1 -1
  43. package/packages/volto-slate/build/messages/src/editor/plugins/AdvancedLink/index.json +1 -1
  44. package/packages/volto-slate/build/messages/src/editor/plugins/Link/index.json +1 -1
  45. package/packages/volto-slate/build/messages/src/editor/plugins/Table/index.json +1 -1
  46. package/packages/volto-slate/build/messages/src/elementEditor/messages.json +1 -1
  47. package/packages/volto-slate/build/messages/src/widgets/HtmlSlateWidget.json +1 -1
  48. package/packages/volto-slate/build/messages/src/widgets/RichTextWidgetView.json +1 -1
  49. package/packages/volto-slate/package.json +1 -1
  50. package/packages/volto-slate/src/blocks/Table/index.js +2 -0
  51. package/packages/volto-slate/src/blocks/Text/SlashMenu.jsx +4 -3
  52. package/packages/volto-slate/src/editor/deserialize.js +0 -1
  53. package/packages/volto-slate/src/editor/plugins/StyleMenu/StyleMenu.jsx +14 -4
  54. package/razzle.config.js +28 -0
  55. package/src/actions/index.js +6 -0
  56. package/src/actions/language/language.js +9 -8
  57. package/src/actions/querystringsearch/querystringsearch.js +20 -14
  58. package/src/actions/relations/rebuild.js +25 -0
  59. package/src/actions/relations/relations.js +86 -0
  60. package/src/actions/relations/relations.test.js +15 -0
  61. package/src/components/index.js +1 -0
  62. package/src/components/manage/Add/Add.jsx +2 -2
  63. package/src/components/manage/BlockChooser/BlockChooser.jsx +14 -5
  64. package/src/components/manage/BlockChooser/BlockChooser.test.jsx +5 -0
  65. package/src/components/manage/Blocks/Listing/Edit.jsx +0 -19
  66. package/src/components/manage/Blocks/Listing/ListingBody.jsx +77 -61
  67. package/src/components/manage/Blocks/Listing/View.jsx +0 -4
  68. package/src/components/manage/Blocks/Listing/getAsyncData.js +10 -2
  69. package/src/components/manage/Blocks/Listing/withQuerystringResults.jsx +18 -13
  70. package/src/components/manage/Blocks/Search/SearchBlockEdit.jsx +5 -4
  71. package/src/components/manage/Blocks/Search/SearchBlockView.jsx +2 -1
  72. package/src/components/manage/Blocks/Search/components/DateRangeFacet.jsx +4 -1
  73. package/src/components/manage/Blocks/Search/components/Facets.jsx +58 -2
  74. package/src/components/manage/Blocks/Search/hocs/withSearch.jsx +24 -11
  75. package/src/components/manage/Blocks/Search/layout/LeftColumnFacets.jsx +17 -5
  76. package/src/components/manage/Blocks/Search/layout/RightColumnFacets.jsx +17 -5
  77. package/src/components/manage/Blocks/Search/layout/TopSideFacets.jsx +21 -5
  78. package/src/components/manage/Blocks/Search/schema.js +16 -1
  79. package/src/components/manage/Blocks/ToC/Edit.jsx +1 -0
  80. package/src/components/manage/Contents/Contents.jsx +69 -33
  81. package/src/components/manage/Contents/ContentsItem.jsx +6 -0
  82. package/src/components/manage/Controlpanels/AddonsControlpanel.jsx +3 -3
  83. package/src/components/manage/Controlpanels/Controlpanels.jsx +199 -224
  84. package/src/components/manage/Controlpanels/Controlpanels.test.jsx +46 -7
  85. package/src/components/manage/Controlpanels/Relations/BrokenRelations.jsx +66 -0
  86. package/src/components/manage/Controlpanels/Relations/Relations.jsx +114 -0
  87. package/src/components/manage/Controlpanels/Relations/RelationsListing.jsx +479 -0
  88. package/src/components/manage/Controlpanels/Relations/RelationsMatrix.jsx +531 -0
  89. package/src/components/manage/Controlpanels/Users/UserGroupMembershipControlPanel.jsx +3 -3
  90. package/src/components/manage/Controlpanels/Users/UserGroupMembershipListing.jsx +51 -82
  91. package/src/components/manage/Controlpanels/Users/UserGroupMembershipMatrix.jsx +79 -75
  92. package/src/components/manage/DragDropList/DragDropList.jsx +63 -42
  93. package/src/components/manage/Form/BlocksToolbar.jsx +5 -1
  94. package/src/components/manage/Form/Form.jsx +11 -5
  95. package/src/components/manage/Form/InlineForm.jsx +39 -9
  96. package/src/components/manage/Form/InlineFormState.js +8 -0
  97. package/src/components/manage/Multilingual/CreateTranslation.jsx +2 -2
  98. package/src/components/manage/Multilingual/TranslationObject.jsx +4 -3
  99. package/src/components/manage/Preferences/ChangePassword.jsx +2 -2
  100. package/src/components/manage/Preferences/PersonalPreferences.jsx +2 -2
  101. package/src/components/manage/Toast/Toast.jsx +1 -1
  102. package/src/components/manage/Toolbar/Types.jsx +2 -2
  103. package/src/components/manage/Widgets/DatetimeWidget.jsx +9 -5
  104. package/src/components/manage/Widgets/ObjectListWidget.jsx +3 -8
  105. package/src/components/manage/Widgets/RecurrenceWidget/ByDayField.jsx +2 -1
  106. package/src/components/manage/Widgets/RecurrenceWidget/MonthOfTheYearField.jsx +2 -1
  107. package/src/components/manage/Widgets/RecurrenceWidget/Occurences.jsx +2 -1
  108. package/src/components/manage/Widgets/RecurrenceWidget/RecurrenceWidget.jsx +7 -2
  109. package/src/components/manage/Widgets/RecurrenceWidget/WeekdayOfTheMonthField.jsx +2 -1
  110. package/src/components/manage/Widgets/SelectUtils.js +1 -1
  111. package/src/components/manage/Widgets/SelectWidget.jsx +1 -1
  112. package/src/components/theme/Footer/Footer.jsx +2 -13
  113. package/src/components/theme/Header/Header.jsx +37 -63
  114. package/src/components/theme/Header/Header.test.jsx +18 -0
  115. package/src/components/theme/Icon/Icon.jsx +2 -2
  116. package/src/components/theme/LanguageSelector/LanguageSelector.js +8 -3
  117. package/src/components/theme/Login/Login.jsx +1 -0
  118. package/src/components/theme/Logo/Logo.jsx +2 -1
  119. package/src/components/theme/MultilingualRedirector/MultilingualRedirector.jsx +2 -2
  120. package/src/components/theme/Navigation/NavItem.jsx +4 -2
  121. package/src/components/theme/NotFound/NotFound.jsx +55 -41
  122. package/src/components/theme/PasswordReset/PasswordReset.jsx +7 -4
  123. package/src/components/theme/PasswordReset/RequestPasswordReset.jsx +1 -1
  124. package/src/components/theme/Sitemap/Sitemap.jsx +5 -3
  125. package/src/components/theme/View/DefaultView.jsx +1 -1
  126. package/src/components/theme/View/EventDatesInfo.jsx +2 -1
  127. package/src/components/theme/View/RenderBlocks.jsx +7 -1
  128. package/src/components/theme/Widgets/DateWidget.jsx +2 -1
  129. package/src/components/theme/Widgets/DatetimeWidget.jsx +2 -1
  130. package/src/components/theme/Widgets/RelationsWidget.jsx +13 -11
  131. package/src/config/ControlPanels.js +2 -0
  132. package/src/config/Widgets.jsx +1 -0
  133. package/src/config/index.js +2 -0
  134. package/src/constants/ActionTypes.js +4 -0
  135. package/src/constants/Languages.js +8 -4
  136. package/src/express-middleware/sitemap.js +36 -4
  137. package/src/helpers/Api/Api.js +1 -1
  138. package/src/helpers/FormValidation/FormValidation.js +11 -2
  139. package/src/helpers/FormValidation/FormValidation.test.js +73 -0
  140. package/src/helpers/Html/Html.jsx +3 -1
  141. package/src/helpers/Html/Html.test.jsx +5 -0
  142. package/src/helpers/MessageLabels/MessageLabels.js +72 -0
  143. package/src/helpers/Robots/Robots.js +24 -6
  144. package/src/helpers/Sitemap/Sitemap.js +44 -2
  145. package/src/helpers/Url/Url.js +27 -6
  146. package/src/helpers/Url/Url.test.js +26 -0
  147. package/src/helpers/Utils/Utils.js +38 -13
  148. package/src/helpers/Utils/Utils.test.js +4 -4
  149. package/src/helpers/index.js +7 -2
  150. package/src/hooks/userSession/useToken.js +5 -0
  151. package/src/middleware/Api.test.js +54 -0
  152. package/src/middleware/api.js +8 -4
  153. package/src/reducers/actions/actions.js +1 -1
  154. package/src/reducers/breadcrumbs/breadcrumbs.js +1 -1
  155. package/src/reducers/index.js +2 -0
  156. package/src/reducers/navigation/navigation.js +1 -1
  157. package/src/reducers/relations/relations.js +173 -0
  158. package/src/reducers/types/types.js +1 -1
  159. package/src/routes.js +5 -0
  160. package/src/server.jsx +28 -23
  161. package/test-setup-config.js +1 -0
  162. package/theme/themes/pastanaga/extras/contents.less +1 -0
  163. package/theme/themes/pastanaga/extras/main.less +80 -1
  164. package/theme/themes/pastanaga/extras/search.less +6 -0
  165. package/theme/themes/pastanaga/extras/sidebar.less +4 -0
  166. package/theme/themes/pastanaga/extras/userscontrolpanel.less +99 -76
  167. package/.changelog.draft +0 -28
  168. package/.editorconfig +0 -36
  169. package/.storybook/main.js +0 -127
  170. package/.storybook/manager.js +0 -15
  171. package/.storybook/preview.js +0 -21
  172. package/.storybook/static/previewImage.svg +0 -48
  173. package/.yarnrc.yml +0 -5
  174. package/jsdoc.json +0 -16
  175. package/netlify.toml +0 -5
  176. package/pyvenv.cfg +0 -3
  177. package/share/man/man1/ttx.1 +0 -225
  178. package/src/components/theme/Header/Header.md +0 -27
  179. package/towncrier.toml +0 -33
@@ -1,5 +1,9 @@
1
1
  import { updateIntl } from 'react-intl-redux';
2
- import { normalizeLanguageName, getCookieOptions } from '@plone/volto/helpers';
2
+ import {
3
+ toGettextLang,
4
+ toReactIntlLang,
5
+ getCookieOptions,
6
+ } from '@plone/volto/helpers';
3
7
  import Cookies from 'universal-cookie';
4
8
 
5
9
  export function changeLanguageCookies(language, req) {
@@ -7,18 +11,15 @@ export function changeLanguageCookies(language, req) {
7
11
 
8
12
  const cookieOptions = getCookieOptions({
9
13
  secure: req?.protocol?.startsWith('https') ? true : false,
14
+ sameSite: 'strict',
10
15
  });
11
16
 
12
17
  if (!req) {
13
- cookies.set(
14
- 'I18N_LANGUAGE',
15
- normalizeLanguageName(language) || '',
16
- cookieOptions,
17
- );
18
+ cookies.set('I18N_LANGUAGE', toGettextLang(language) || '', cookieOptions);
18
19
  } else {
19
20
  req.universalCookies.set(
20
21
  'I18N_LANGUAGE',
21
- normalizeLanguageName(language) || '',
22
+ toGettextLang(language) || '',
22
23
  cookieOptions,
23
24
  );
24
25
  }
@@ -35,7 +36,7 @@ export function changeLanguage(language, locale, req) {
35
36
  changeLanguageCookies(language, req);
36
37
 
37
38
  return updateIntl({
38
- locale: language,
39
+ locale: toReactIntlLang(language),
39
40
  messages: locale,
40
41
  });
41
42
  }
@@ -31,24 +31,30 @@ export function getQueryStringResults(path, data, subrequest, page) {
31
31
  }
32
32
  }
33
33
 
34
+ const query = {
35
+ ...requestData,
36
+ ...(!requestData.b_size && {
37
+ b_size: settings.defaultPageSize,
38
+ }),
39
+ ...(page && {
40
+ b_start: requestData.b_size
41
+ ? data.b_size * (page - 1)
42
+ : settings.defaultPageSize * (page - 1),
43
+ }),
44
+ query: requestData?.query,
45
+ };
46
+
34
47
  return {
35
48
  type: GET_QUERYSTRING_RESULTS,
36
49
  subrequest,
37
50
  request: {
38
- op: 'post',
39
- path: `${path}/@querystring-search`,
40
- data: {
41
- ...requestData,
42
- ...(!requestData.b_size && {
43
- b_size: settings.defaultPageSize,
44
- }),
45
- ...(page && {
46
- b_start: requestData.b_size
47
- ? data.b_size * (page - 1)
48
- : settings.defaultPageSize * (page - 1),
49
- }),
50
- query: requestData?.query,
51
- },
51
+ op: settings.querystringSearchGet ? 'get' : 'post',
52
+ path: `${path}/@querystring-search${
53
+ settings.querystringSearchGet
54
+ ? `?query=${encodeURIComponent(JSON.stringify(query))}`
55
+ : ''
56
+ }`,
57
+ data: settings.querystringSearchGet ? null : query,
52
58
  },
53
59
  };
54
60
  }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Rebuild relations action.
3
+ * @module actions/relations/rebuild
4
+ */
5
+
6
+ import { REBUILD_RELATIONS } from '@plone/volto/constants/ActionTypes';
7
+
8
+ /**
9
+ * Rebuild relation function.
10
+ * @function rebuildRelations
11
+ * @param {Boolean} flush Flush intids
12
+ * @returns {Object} Rebuild relation action.
13
+ */
14
+ export function rebuildRelations(flush = false) {
15
+ let path = '/@relations';
16
+ var searchParams = new URLSearchParams();
17
+ searchParams.append('rebuild', '1');
18
+ flush && searchParams.append('flush', '1');
19
+ const searchParamsToString = searchParams.toString();
20
+ path += `?${searchParamsToString}`;
21
+ return {
22
+ type: REBUILD_RELATIONS,
23
+ request: { op: 'get', path: path },
24
+ };
25
+ }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Relations actions.
3
+ * @module actions/relations/relations
4
+ */
5
+
6
+ import {
7
+ CREATE_RELATIONS,
8
+ DELETE_RELATIONS,
9
+ LIST_RELATIONS,
10
+ } from '@plone/volto/constants/ActionTypes';
11
+
12
+ /**
13
+ * Create relation function.
14
+ * @function createRelations
15
+ * @param {Object|Array} content Relation data.
16
+ * @returns {Object} Create relation action.
17
+ */
18
+ export function createRelations(content) {
19
+ return {
20
+ type: CREATE_RELATIONS,
21
+ request: {
22
+ op: 'post',
23
+ path: '/@relations',
24
+ data: {
25
+ items: content,
26
+ },
27
+ },
28
+ };
29
+ }
30
+
31
+ /**
32
+ * Delete relation function.
33
+ * @function deleteRelations
34
+ * @param {string} id Relation id
35
+ * @returns {Object} Delete relation action.
36
+ */
37
+ export function deleteRelations(content) {
38
+ return {
39
+ type: DELETE_RELATIONS,
40
+ request: {
41
+ op: 'del',
42
+ path: `/@relations`,
43
+ data: {
44
+ items: content,
45
+ },
46
+ },
47
+ };
48
+ }
49
+
50
+ /**
51
+ * Query relations
52
+ * @function queryRelations
53
+ * @param {string} relation Name of relation
54
+ * @param {boolean} onlyBroken
55
+ * @returns {Object} List relations action
56
+ */
57
+ export function queryRelations(
58
+ relation = null,
59
+ onlyBroken = false,
60
+ subrequest = null,
61
+ source = null,
62
+ target = null,
63
+ query_source = null,
64
+ query_target = null,
65
+ ) {
66
+ let path = '/@relations';
67
+ var searchParams = new URLSearchParams();
68
+ relation && searchParams.append('relation', relation);
69
+ onlyBroken && searchParams.append('onlyBroken', onlyBroken);
70
+ source && searchParams.append('source', source);
71
+ target && searchParams.append('target', target);
72
+ query_source && searchParams.append('query_source', query_source);
73
+ query_target && searchParams.append('query_target', query_target);
74
+ const searchParamsToString = searchParams.toString();
75
+ if (searchParamsToString) {
76
+ path += `?${searchParamsToString}`;
77
+ }
78
+ return {
79
+ type: LIST_RELATIONS,
80
+ subrequest,
81
+ request: {
82
+ op: 'get',
83
+ path: path,
84
+ },
85
+ };
86
+ }
@@ -0,0 +1,15 @@
1
+ import { queryRelations } from './relations';
2
+ import { LIST_RELATIONS } from '@plone/volto/constants/ActionTypes';
3
+
4
+ describe('Relations action', () => {
5
+ describe('queryRelations', () => {
6
+ it('should create an action to get relations of type "relatedItems"', () => {
7
+ const relation = 'relatedItems';
8
+ const action = queryRelations(relation);
9
+
10
+ expect(action.type).toEqual(LIST_RELATIONS);
11
+ expect(action.request.op).toEqual('get');
12
+ expect(action.request.path).toEqual(`/@relations?relation=${relation}`);
13
+ });
14
+ });
15
+ });
@@ -89,6 +89,7 @@ export ContentTypeSchema from '@plone/volto/components/manage/Controlpanels/Cont
89
89
  export ContentTypesActions from '@plone/volto/components/manage/Controlpanels/ContentTypesActions';
90
90
  export UsersControlpanel from '@plone/volto/components/manage/Controlpanels/Users/UsersControlpanel';
91
91
  export UserGroupMembershipControlPanel from '@plone/volto/components/manage/Controlpanels/Users/UserGroupMembershipControlPanel';
92
+ export Relations from '@plone/volto/components/manage/Controlpanels/Relations/Relations';
92
93
  export GroupsControlpanel from '@plone/volto/components/manage/Controlpanels/Groups/GroupsControlpanel';
93
94
  export RulesControlpanel from '@plone/volto/components/manage/Controlpanels/Rules/Rules';
94
95
  export AddRuleControlpanel from '@plone/volto/components/manage/Controlpanels/Rules/AddRule';
@@ -33,7 +33,7 @@ import {
33
33
  getBlocksLayoutFieldname,
34
34
  getLanguageIndependentFields,
35
35
  langmap,
36
- normalizeLanguageName,
36
+ toGettextLang,
37
37
  } from '@plone/volto/helpers';
38
38
 
39
39
  import { preloadLazyLibs } from '@plone/volto/helpers/Loadable';
@@ -219,7 +219,7 @@ class Add extends Component {
219
219
  onCancel() {
220
220
  if (this.props.location?.state?.translationOf) {
221
221
  const language = this.props.location.state.languageFrom;
222
- const langFileName = normalizeLanguageName(language);
222
+ const langFileName = toGettextLang(language);
223
223
  import('@root/../locales/' + langFileName + '.json').then((locale) => {
224
224
  this.props.changeLanguage(language, locale.default);
225
225
  });
@@ -33,17 +33,22 @@ const BlockChooser = ({
33
33
  properties = {},
34
34
  }) => {
35
35
  const intl = useIntl();
36
- const useAllowedBlocks = !isEmpty(allowedBlocks);
36
+ const hasAllowedBlocks = !isEmpty(allowedBlocks);
37
37
 
38
38
  const filteredBlocksConfig = filter(blocksConfig, (item) => {
39
+ // Check if the block is well formed (has at least id and title)
40
+ const blockIsWellFormed = Boolean(item.title && item.id);
41
+ if (!blockIsWellFormed) {
42
+ return false;
43
+ }
39
44
  if (showRestricted) {
40
- if (useAllowedBlocks) {
45
+ if (hasAllowedBlocks) {
41
46
  return allowedBlocks.includes(item.id);
42
47
  } else {
43
48
  return true;
44
49
  }
45
50
  } else {
46
- if (useAllowedBlocks) {
51
+ if (hasAllowedBlocks) {
47
52
  return allowedBlocks.includes(item.id);
48
53
  } else {
49
54
  // Overload restricted as a function, so we can decide the availability of a block
@@ -89,14 +94,18 @@ const BlockChooser = ({
89
94
  function blocksAvailableFilter(blocks) {
90
95
  return blocks.filter(
91
96
  (block) =>
92
- getFormatMessage(block.title).toLowerCase().includes(filterValue) ||
97
+ getFormatMessage(block.title)
98
+ .toLowerCase()
99
+ .includes(filterValue.toLowerCase()) ||
93
100
  filterVariations(block)?.length,
94
101
  );
95
102
  }
96
103
  function filterVariations(block) {
97
104
  return block.variations?.filter(
98
105
  (variation) =>
99
- getFormatMessage(variation.title).toLowerCase().includes(filterValue) &&
106
+ getFormatMessage(variation.title)
107
+ .toLowerCase()
108
+ .includes(filterValue.toLowerCase()) &&
100
109
  !variation.title.toLowerCase().includes('default'),
101
110
  );
102
111
  }
@@ -108,6 +108,11 @@ config.blocks.blocksConfig = {
108
108
  restricted: false,
109
109
  mostUsed: false,
110
110
  },
111
+ malformedBlock: {
112
+ icon: blockSVG,
113
+ group: 'common',
114
+ restricted: false,
115
+ },
111
116
  };
112
117
 
113
118
  const mockStore = configureStore();
@@ -26,35 +26,16 @@ const messages = defineMessages({
26
26
  const Edit = React.memo(
27
27
  (props) => {
28
28
  const { data, onChangeBlock, block, selected, pathname } = props;
29
-
30
29
  const intl = useIntl();
31
-
32
- // componentDidMount
33
- React.useEffect(() => {
34
- if (!data.query) {
35
- onChangeBlock(block, {
36
- ...data,
37
- query: [],
38
- block,
39
- });
40
- }
41
- /* eslint-disable react-hooks/exhaustive-deps */
42
- }, []);
43
-
44
30
  const placeholder =
45
31
  data.placeholder ||
46
32
  (data?.querystring?.query?.length
47
33
  ? intl.formatMessage(messages.results)
48
34
  : intl.formatMessage(messages.items));
49
35
 
50
- const HeadlineTag = data.headlineTag || 'h2';
51
-
52
36
  return (
53
37
  <>
54
38
  <p className="items-preview">{placeholder}</p>
55
- {data.headline && (
56
- <HeadlineTag className="headline">{data.headline}</HeadlineTag>
57
- )}
58
39
  <ListingBody {...props} path={getBaseUrl(pathname)} isEditMode />
59
40
  <SidebarPortal selected={selected}>
60
41
  <ListingData
@@ -1,5 +1,6 @@
1
1
  import React, { createRef } from 'react';
2
2
  import { FormattedMessage, injectIntl } from 'react-intl';
3
+ import cx from 'classnames';
3
4
  import { Pagination, Dimmer, Loader } from 'semantic-ui-react';
4
5
  import { Icon } from '@plone/volto/components';
5
6
  import config from '@plone/volto/registry';
@@ -44,70 +45,85 @@ const ListingBody = withQuerystringResults((props) => {
44
45
  ? variation.noResultsComponent
45
46
  : config.blocks?.blocksConfig['listing'].noResultsComponent;
46
47
 
47
- return listingItems?.length > 0 ? (
48
- <div ref={listingRef}>
49
- <ListingBodyTemplate
50
- items={listingItems}
51
- isEditMode={isEditMode}
52
- {...data}
53
- {...variation}
54
- />
55
- {totalPages > 1 && (
56
- <div className="pagination-wrapper">
57
- <Pagination
58
- activePage={currentPage}
59
- totalPages={totalPages}
60
- onPageChange={(e, { activePage }) => {
61
- !isEditMode &&
62
- listingRef.current.scrollIntoView({ behavior: 'smooth' });
63
- onPaginationChange(e, { activePage });
64
- }}
65
- firstItem={null}
66
- lastItem={null}
67
- prevItem={{
68
- content: <Icon name={paginationLeftSVG} size="18px" />,
69
- icon: true,
70
- 'aria-disabled': !prevBatch,
71
- className: !prevBatch ? 'disabled' : null,
72
- }}
73
- nextItem={{
74
- content: <Icon name={paginationRightSVG} size="18px" />,
75
- icon: true,
76
- 'aria-disabled': !nextBatch,
77
- className: !nextBatch ? 'disabled' : null,
78
- }}
48
+ const HeadlineTag = data.headlineTag || 'h2';
49
+
50
+ return (
51
+ <>
52
+ {data.headline && (
53
+ <HeadlineTag
54
+ className={cx('headline', {
55
+ emptyListing: !listingItems?.length > 0,
56
+ })}
57
+ >
58
+ {data.headline}
59
+ </HeadlineTag>
60
+ )}
61
+ {listingItems?.length > 0 ? (
62
+ <div ref={listingRef}>
63
+ <ListingBodyTemplate
64
+ items={listingItems}
65
+ isEditMode={isEditMode}
66
+ {...data}
67
+ {...variation}
79
68
  />
69
+ {totalPages > 1 && (
70
+ <div className="pagination-wrapper">
71
+ <Pagination
72
+ activePage={currentPage}
73
+ totalPages={totalPages}
74
+ onPageChange={(e, { activePage }) => {
75
+ !isEditMode &&
76
+ listingRef.current.scrollIntoView({ behavior: 'smooth' });
77
+ onPaginationChange(e, { activePage });
78
+ }}
79
+ firstItem={null}
80
+ lastItem={null}
81
+ prevItem={{
82
+ content: <Icon name={paginationLeftSVG} size="18px" />,
83
+ icon: true,
84
+ 'aria-disabled': !prevBatch,
85
+ className: !prevBatch ? 'disabled' : null,
86
+ }}
87
+ nextItem={{
88
+ content: <Icon name={paginationRightSVG} size="18px" />,
89
+ icon: true,
90
+ 'aria-disabled': !nextBatch,
91
+ className: !nextBatch ? 'disabled' : null,
92
+ }}
93
+ />
94
+ </div>
95
+ )}
96
+ </div>
97
+ ) : isEditMode ? (
98
+ <div className="listing message" ref={listingRef}>
99
+ {isFolderContentsListing && (
100
+ <FormattedMessage
101
+ id="No items found in this container."
102
+ defaultMessage="No items found in this container."
103
+ />
104
+ )}
105
+ {hasLoaded && NoResults && (
106
+ <NoResults isEditMode={isEditMode} {...data} />
107
+ )}
108
+ <Dimmer active={!hasLoaded} inverted>
109
+ <Loader indeterminate size="small">
110
+ <FormattedMessage id="loading" defaultMessage="Loading" />
111
+ </Loader>
112
+ </Dimmer>
113
+ </div>
114
+ ) : (
115
+ <div className="emptyListing">
116
+ {hasLoaded && NoResults && (
117
+ <NoResults isEditMode={isEditMode} {...data} />
118
+ )}
119
+ <Dimmer active={!hasLoaded} inverted>
120
+ <Loader indeterminate size="small">
121
+ <FormattedMessage id="loading" defaultMessage="Loading" />
122
+ </Loader>
123
+ </Dimmer>
80
124
  </div>
81
125
  )}
82
- </div>
83
- ) : isEditMode ? (
84
- <div className="listing message" ref={listingRef}>
85
- {isFolderContentsListing && (
86
- <FormattedMessage
87
- id="No items found in this container."
88
- defaultMessage="No items found in this container."
89
- />
90
- )}
91
- {hasLoaded && NoResults && (
92
- <NoResults isEditMode={isEditMode} {...data} />
93
- )}
94
- <Dimmer active={!hasLoaded} inverted>
95
- <Loader indeterminate size="small">
96
- <FormattedMessage id="loading" defaultMessage="Loading" />
97
- </Loader>
98
- </Dimmer>
99
- </div>
100
- ) : (
101
- <div>
102
- {hasLoaded && NoResults && (
103
- <NoResults isEditMode={isEditMode} {...data} />
104
- )}
105
- <Dimmer active={!hasLoaded} inverted>
106
- <Loader indeterminate size="small">
107
- <FormattedMessage id="loading" defaultMessage="Loading" />
108
- </Loader>
109
- </Dimmer>
110
- </div>
126
+ </>
111
127
  );
112
128
  });
113
129
 
@@ -7,15 +7,11 @@ import { ListingBlockBody as ListingBody } from '@plone/volto/components';
7
7
 
8
8
  const View = (props) => {
9
9
  const { data, path, pathname, className } = props;
10
- const HeadlineTag = data.headlineTag || 'h2';
11
10
 
12
11
  return (
13
12
  <div
14
13
  className={cx('block listing', data.variation || 'default', className)}
15
14
  >
16
- {data.headline && (
17
- <HeadlineTag className="headline">{data.headline}</HeadlineTag>
18
- )}
19
15
  <ListingBody {...props} path={path ?? pathname} />
20
16
  </div>
21
17
  );
@@ -1,7 +1,13 @@
1
1
  import { getQueryStringResults } from '@plone/volto/actions';
2
2
  import { resolveBlockExtensions } from '@plone/volto/helpers';
3
3
 
4
- export default ({ dispatch, data, path, blocksConfig }) => {
4
+ const getListingBlockAsyncData = ({
5
+ dispatch,
6
+ id,
7
+ data,
8
+ path,
9
+ blocksConfig,
10
+ }) => {
5
11
  const { resolvedExtensions } = resolveBlockExtensions(data, blocksConfig);
6
12
 
7
13
  return [
@@ -14,8 +20,10 @@ export default ({ dispatch, data, path, blocksConfig }) => {
14
20
  ? { fullobjects: 1 }
15
21
  : { metadata_fields: '_all' }),
16
22
  },
17
- data.block,
23
+ id,
18
24
  ),
19
25
  ),
20
26
  ];
21
27
  };
28
+
29
+ export default getListingBlockAsyncData;
@@ -14,11 +14,16 @@ function getDisplayName(WrappedComponent) {
14
14
 
15
15
  export default function withQuerystringResults(WrappedComponent) {
16
16
  function WithQuerystringResults(props) {
17
- const { data = {}, properties: content, path, variation } = props;
17
+ const {
18
+ data = {},
19
+ id = data.block,
20
+ properties: content,
21
+ path,
22
+ variation,
23
+ } = props;
18
24
  const { settings } = config;
19
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
20
26
 
21
- const { block } = data;
22
27
  const { b_size = settings.defaultPageSize } = querystring; // batchsize
23
28
 
24
29
  // save the path so it won't trigger dispatch on eager router location change
@@ -45,32 +50,32 @@ export default function withQuerystringResults(WrappedComponent) {
45
50
 
46
51
  const folderItems = content?.is_folderish ? content.items : [];
47
52
  const hasQuery = querystring?.query?.length > 0;
48
- const hasLoaded = hasQuery ? !querystringResults?.[block]?.loading : true;
53
+ const hasLoaded = hasQuery ? !querystringResults?.[id]?.loading : true;
49
54
 
50
55
  const listingItems =
51
- querystring?.query?.length > 0 && querystringResults?.[block]
52
- ? querystringResults?.[block]?.items || []
56
+ querystring?.query?.length > 0 && querystringResults?.[id]
57
+ ? querystringResults?.[id]?.items || []
53
58
  : folderItems;
54
59
 
55
60
  const showAsFolderListing = !hasQuery && content?.items_total > b_size;
56
61
  const showAsQueryListing =
57
- hasQuery && querystringResults?.[block]?.total > b_size;
62
+ hasQuery && querystringResults?.[id]?.total > b_size;
58
63
 
59
64
  const totalPages = showAsFolderListing
60
65
  ? Math.ceil(content.items_total / b_size)
61
66
  : showAsQueryListing
62
- ? Math.ceil(querystringResults[block].total / b_size)
67
+ ? Math.ceil(querystringResults[id].total / b_size)
63
68
  : 0;
64
69
 
65
70
  const prevBatch = showAsFolderListing
66
71
  ? content.batching?.prev
67
72
  : showAsQueryListing
68
- ? querystringResults[block].batching?.prev
73
+ ? querystringResults[id].batching?.prev
69
74
  : null;
70
75
  const nextBatch = showAsFolderListing
71
76
  ? content.batching?.next
72
77
  : showAsQueryListing
73
- ? querystringResults[block].batching?.next
78
+ ? querystringResults[id].batching?.next
74
79
  : null;
75
80
 
76
81
  const isImageGallery =
@@ -80,7 +85,7 @@ export default function withQuerystringResults(WrappedComponent) {
80
85
  useDeepCompareEffect(() => {
81
86
  if (hasQuery) {
82
87
  dispatch(
83
- getQueryStringResults(initialPath, adaptedQuery, block, currentPage),
88
+ getQueryStringResults(initialPath, adaptedQuery, id, currentPage),
84
89
  );
85
90
  } else if (isImageGallery && !hasQuery) {
86
91
  // when used as image gallery, it doesn't need a query to list children
@@ -98,14 +103,14 @@ export default function withQuerystringResults(WrappedComponent) {
98
103
  },
99
104
  ],
100
105
  },
101
- block,
106
+ id,
102
107
  ),
103
108
  );
104
109
  } else {
105
110
  dispatch(getContent(initialPath, null, null, currentPage));
106
111
  }
107
112
  }, [
108
- block,
113
+ id,
109
114
  isImageGallery,
110
115
  adaptedQuery,
111
116
  hasQuery,
@@ -118,7 +123,7 @@ export default function withQuerystringResults(WrappedComponent) {
118
123
  <WrappedComponent
119
124
  {...props}
120
125
  onPaginationChange={(e, { activePage }) => setCurrentPage(activePage)}
121
- total={querystringResults?.[block]?.total}
126
+ total={querystringResults?.[id]?.total}
122
127
  batch_size={b_size}
123
128
  currentPage={currentPage}
124
129
  totalPages={totalPages}
@@ -1,5 +1,4 @@
1
- import React from 'react';
2
- import useDeepCompareEffect from 'use-deep-compare-effect';
1
+ import React, { useEffect } from 'react';
3
2
  import { defineMessages } from 'react-intl';
4
3
  import { compose } from 'redux';
5
4
 
@@ -60,9 +59,11 @@ const SearchBlockEdit = (props) => {
60
59
  };
61
60
 
62
61
  const { query = {} } = data || {};
63
- useDeepCompareEffect(() => {
62
+ // We don't need deep compare here, as this is just json serializable data.
63
+ const deepQuery = JSON.stringify(query);
64
+ useEffect(() => {
64
65
  onTriggerSearch();
65
- }, [query, onTriggerSearch]);
66
+ }, [deepQuery, onTriggerSearch]);
66
67
 
67
68
  return (
68
69
  <>
@@ -57,7 +57,7 @@ const applyDefaults = (data, root) => {
57
57
  };
58
58
 
59
59
  const SearchBlockView = (props) => {
60
- const { data, searchData, mode = 'view', variation } = props;
60
+ const { id, data, searchData, mode = 'view', variation } = props;
61
61
 
62
62
  const Layout = variation.view;
63
63
 
@@ -89,6 +89,7 @@ const SearchBlockView = (props) => {
89
89
  setSelectedView={setSelectedView}
90
90
  >
91
91
  <ListingBody
92
+ id={id}
92
93
  variation={{ ...data, ...listingBodyVariation }}
93
94
  data={listingBodyData}
94
95
  path={props.path}