@plone/volto 15.0.0-alpha.4 → 15.0.0-alpha.8

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 (238) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +7 -6
  2. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -1
  3. package/.github/workflows/docs.yml +15 -55
  4. package/CHANGELOG.md +57 -2
  5. package/CODE_OF_CONDUCT.md +0 -1
  6. package/COMMITLINT.md +19 -21
  7. package/README.md +1 -1
  8. package/SECURITY.md +0 -1
  9. package/addon-registry.js +6 -3
  10. package/dist/assets.json +1 -0
  11. package/dist/chunks.json +572 -0
  12. package/dist/loadable-stats.json +2099 -0
  13. package/dist/public/android-chrome-192x192.png +0 -0
  14. package/dist/public/android-chrome-512x512.png +0 -0
  15. package/dist/public/apple-touch-icon.png +0 -0
  16. package/dist/public/favicon-16x16.png +0 -0
  17. package/dist/public/favicon-32x32.png +0 -0
  18. package/dist/public/favicon.ico +0 -0
  19. package/dist/public/icon.svg +13 -0
  20. package/dist/public/index.html.spa +34 -0
  21. package/dist/public/robots.txt +2 -0
  22. package/dist/public/site.webmanifest +19 -0
  23. package/dist/public/static/css/15.2cdd5437.chunk.css +409 -0
  24. package/dist/public/static/css/15.2cdd5437.chunk.css.map +1 -0
  25. package/dist/public/static/css/8.37cef611.chunk.css +2 -0
  26. package/dist/public/static/css/8.37cef611.chunk.css.map +1 -0
  27. package/dist/public/static/css/client.4cb92c57.chunk.css +8 -0
  28. package/dist/public/static/css/client.4cb92c57.chunk.css.map +1 -0
  29. package/dist/public/static/css/plone-volto-components-manage-Widgets-DatetimeWidget.c847a331.chunk.css +2 -0
  30. package/dist/public/static/css/plone-volto-components-manage-Widgets-DatetimeWidget.c847a331.chunk.css.map +1 -0
  31. package/dist/public/static/js/0.3e794c57.chunk.js +2 -0
  32. package/dist/public/static/js/0.3e794c57.chunk.js.map +1 -0
  33. package/dist/public/static/js/1.fece2b93.chunk.js +3 -0
  34. package/dist/public/static/js/1.fece2b93.chunk.js.LICENSE.txt +8 -0
  35. package/dist/public/static/js/1.fece2b93.chunk.js.map +1 -0
  36. package/dist/public/static/js/15.2cdd5437.chunk.js +3 -0
  37. package/dist/public/static/js/15.2cdd5437.chunk.js.LICENSE.txt +120 -0
  38. package/dist/public/static/js/15.2cdd5437.chunk.js.map +1 -0
  39. package/dist/public/static/js/16.a3a0662d.chunk.js +3 -0
  40. package/dist/public/static/js/16.a3a0662d.chunk.js.LICENSE.txt +8 -0
  41. package/dist/public/static/js/16.a3a0662d.chunk.js.map +1 -0
  42. package/dist/public/static/js/17.9478c50d.chunk.js +2 -0
  43. package/dist/public/static/js/17.9478c50d.chunk.js.map +1 -0
  44. package/dist/public/static/js/18.388e65d0.chunk.js +2 -0
  45. package/dist/public/static/js/18.388e65d0.chunk.js.map +1 -0
  46. package/dist/public/static/js/19.7c6b4a80.chunk.js +2 -0
  47. package/dist/public/static/js/19.7c6b4a80.chunk.js.map +1 -0
  48. package/dist/public/static/js/2.e6b0d690.chunk.js +2 -0
  49. package/dist/public/static/js/2.e6b0d690.chunk.js.map +1 -0
  50. package/dist/public/static/js/20.ada67f0e.chunk.js +3 -0
  51. package/dist/public/static/js/20.ada67f0e.chunk.js.LICENSE.txt +8 -0
  52. package/dist/public/static/js/20.ada67f0e.chunk.js.map +1 -0
  53. package/dist/public/static/js/21.3de49ac8.chunk.js +2 -0
  54. package/dist/public/static/js/21.3de49ac8.chunk.js.map +1 -0
  55. package/dist/public/static/js/22.ecefb8f2.chunk.js +2 -0
  56. package/dist/public/static/js/22.ecefb8f2.chunk.js.map +1 -0
  57. package/dist/public/static/js/23.3027bc00.chunk.js +2 -0
  58. package/dist/public/static/js/23.3027bc00.chunk.js.map +1 -0
  59. package/dist/public/static/js/24.aaaf4abf.chunk.js +2 -0
  60. package/dist/public/static/js/24.aaaf4abf.chunk.js.map +1 -0
  61. package/dist/public/static/js/25.c4d7d5e0.chunk.js +2 -0
  62. package/dist/public/static/js/25.c4d7d5e0.chunk.js.map +1 -0
  63. package/dist/public/static/js/26.d59ac3c1.chunk.js +2 -0
  64. package/dist/public/static/js/26.d59ac3c1.chunk.js.map +1 -0
  65. package/dist/public/static/js/27.266d95dd.chunk.js +2 -0
  66. package/dist/public/static/js/27.266d95dd.chunk.js.map +1 -0
  67. package/dist/public/static/js/28.d965d304.chunk.js +2 -0
  68. package/dist/public/static/js/28.d965d304.chunk.js.map +1 -0
  69. package/dist/public/static/js/29.7d8d1992.chunk.js +2 -0
  70. package/dist/public/static/js/29.7d8d1992.chunk.js.map +1 -0
  71. package/dist/public/static/js/3.025ba843.chunk.js +2 -0
  72. package/dist/public/static/js/3.025ba843.chunk.js.map +1 -0
  73. package/dist/public/static/js/30.a3d54e31.chunk.js +2 -0
  74. package/dist/public/static/js/30.a3d54e31.chunk.js.map +1 -0
  75. package/dist/public/static/js/31.9edf64ee.chunk.js +2 -0
  76. package/dist/public/static/js/31.9edf64ee.chunk.js.map +1 -0
  77. package/dist/public/static/js/32.649dbfc2.chunk.js +2 -0
  78. package/dist/public/static/js/32.649dbfc2.chunk.js.map +1 -0
  79. package/dist/public/static/js/33.45949ca4.chunk.js +2 -0
  80. package/dist/public/static/js/33.45949ca4.chunk.js.map +1 -0
  81. package/dist/public/static/js/34.084257cf.chunk.js +2 -0
  82. package/dist/public/static/js/34.084257cf.chunk.js.map +1 -0
  83. package/dist/public/static/js/35.00679602.chunk.js +3 -0
  84. package/dist/public/static/js/35.00679602.chunk.js.LICENSE.txt +37 -0
  85. package/dist/public/static/js/35.00679602.chunk.js.map +1 -0
  86. package/dist/public/static/js/36.4a29fdbd.chunk.js +3 -0
  87. package/dist/public/static/js/36.4a29fdbd.chunk.js.LICENSE.txt +7 -0
  88. package/dist/public/static/js/36.4a29fdbd.chunk.js.map +1 -0
  89. package/dist/public/static/js/37.fbbe07b3.chunk.js +3 -0
  90. package/dist/public/static/js/37.fbbe07b3.chunk.js.LICENSE.txt +14 -0
  91. package/dist/public/static/js/37.fbbe07b3.chunk.js.map +1 -0
  92. package/dist/public/static/js/38.b31c59ad.chunk.js +3 -0
  93. package/dist/public/static/js/38.b31c59ad.chunk.js.LICENSE.txt +8 -0
  94. package/dist/public/static/js/38.b31c59ad.chunk.js.map +1 -0
  95. package/dist/public/static/js/39.69d6a789.chunk.js +2 -0
  96. package/dist/public/static/js/39.69d6a789.chunk.js.map +1 -0
  97. package/dist/public/static/js/4.bdc4a5c5.chunk.js +2 -0
  98. package/dist/public/static/js/4.bdc4a5c5.chunk.js.map +1 -0
  99. package/dist/public/static/js/40.90de6e4a.chunk.js +2 -0
  100. package/dist/public/static/js/40.90de6e4a.chunk.js.map +1 -0
  101. package/dist/public/static/js/7.7acc66bc.chunk.js +2 -0
  102. package/dist/public/static/js/7.7acc66bc.chunk.js.map +1 -0
  103. package/dist/public/static/js/8.37cef611.chunk.js +3 -0
  104. package/dist/public/static/js/8.37cef611.chunk.js.LICENSE.txt +25 -0
  105. package/dist/public/static/js/8.37cef611.chunk.js.map +1 -0
  106. package/dist/public/static/js/client.4cb92c57.chunk.js +2 -0
  107. package/dist/public/static/js/client.4cb92c57.chunk.js.map +1 -0
  108. package/dist/public/static/js/draft-js-lib-isSoftNewlineEvent.6cd05e34.chunk.js +2 -0
  109. package/dist/public/static/js/draft-js-lib-isSoftNewlineEvent.6cd05e34.chunk.js.map +1 -0
  110. package/dist/public/static/js/moment.2caa76d1.chunk.js +2 -0
  111. package/dist/public/static/js/moment.2caa76d1.chunk.js.map +1 -0
  112. package/dist/public/static/js/plone-volto-components-manage-Widgets-DatetimeWidget.c847a331.chunk.js +2 -0
  113. package/dist/public/static/js/plone-volto-components-manage-Widgets-DatetimeWidget.c847a331.chunk.js.map +1 -0
  114. package/dist/public/static/js/plone-volto-components-theme-View-EventView.433dd14b.chunk.js +2 -0
  115. package/dist/public/static/js/plone-volto-components-theme-View-EventView.433dd14b.chunk.js.map +1 -0
  116. package/dist/public/static/js/react-dates.f71abb7c.chunk.js +2 -0
  117. package/dist/public/static/js/react-dates.f71abb7c.chunk.js.map +1 -0
  118. package/dist/public/static/js/react-simple-code-editor.33478dc3.chunk.js +2 -0
  119. package/dist/public/static/js/react-simple-code-editor.33478dc3.chunk.js.map +1 -0
  120. package/dist/public/static/js/runtime~client.d372df0e.js +2 -0
  121. package/dist/public/static/js/runtime~client.d372df0e.js.map +1 -0
  122. package/dist/public/static/media/Logo.16e25cdf.svg +21 -0
  123. package/dist/public/static/media/block-image.8a4a14d5.svg +8 -0
  124. package/dist/public/static/media/block-maps.731e84f6.svg +7 -0
  125. package/dist/public/static/media/block-video.832fa3ff.svg +7 -0
  126. package/dist/public/static/media/default-image.9451b251.svg +13 -0
  127. package/dist/public/static/media/flags.9c74e172.png +0 -0
  128. package/dist/public/static/media/icons.022583c4.svg +320 -0
  129. package/dist/public/static/media/icons.06400145.ttf +0 -0
  130. package/dist/public/static/media/icons.dbc1ef8b.eot +0 -0
  131. package/dist/public/static/media/icons.df45b7de.woff +0 -0
  132. package/dist/server.js +2 -0
  133. package/locales/ca/LC_MESSAGES/volto.po +14 -0
  134. package/locales/ca.json +1 -1
  135. package/locales/de/LC_MESSAGES/volto.po +14 -0
  136. package/locales/de.json +1 -1
  137. package/locales/en/LC_MESSAGES/volto.po +14 -0
  138. package/locales/en.json +1 -1
  139. package/locales/es/LC_MESSAGES/volto.po +14 -0
  140. package/locales/es.json +1 -1
  141. package/locales/eu/LC_MESSAGES/volto.po +14 -0
  142. package/locales/eu.json +1 -1
  143. package/locales/fr/LC_MESSAGES/volto.po +14 -0
  144. package/locales/fr.json +1 -1
  145. package/locales/it/LC_MESSAGES/volto.po +14 -0
  146. package/locales/it.json +1 -1
  147. package/locales/ja/LC_MESSAGES/volto.po +14 -0
  148. package/locales/ja.json +1 -1
  149. package/locales/nl/LC_MESSAGES/volto.po +14 -0
  150. package/locales/nl.json +1 -1
  151. package/locales/pt/LC_MESSAGES/volto.po +14 -0
  152. package/locales/pt.json +1 -1
  153. package/locales/pt_BR/LC_MESSAGES/volto.po +14 -0
  154. package/locales/pt_BR.json +1 -1
  155. package/locales/ro/LC_MESSAGES/volto.po +14 -0
  156. package/locales/ro.json +1 -1
  157. package/locales/volto.pot +15 -1
  158. package/logos/Logo.png +0 -0
  159. package/logos/volto-colorful.png +0 -0
  160. package/logos/volto-colorful.svg +35 -0
  161. package/logos/volto-guide.png +0 -0
  162. package/logos/volto-h-transparent.svg +6 -0
  163. package/logos/volto-transparent.png +0 -0
  164. package/logos/volto-transparent.svg +19 -0
  165. package/logos/volto-transparent2.svg +5 -0
  166. package/logos/volto-yellow.svg +29 -0
  167. package/logos/volto.sketch +0 -0
  168. package/netlify.toml +2 -0
  169. package/package.json +6 -2
  170. package/razzle.config.js +2 -2
  171. package/src/actions/language/language.js +7 -2
  172. package/src/components/manage/AnchorPlugin/components/LinkButton/AddLinkForm.jsx +1 -2
  173. package/src/components/manage/AnchorPlugin/components/LinkButton/index.jsx +15 -6
  174. package/src/components/manage/AnchorPlugin/index.jsx +12 -5
  175. package/src/components/manage/AnchorPlugin/utils/EditorUtils.js +8 -7
  176. package/src/components/manage/Blocks/Description/Edit.jsx +33 -19
  177. package/src/components/manage/Blocks/Description/Edit.test.jsx +6 -0
  178. package/src/components/manage/Blocks/HeroImageLeft/Edit.jsx +37 -26
  179. package/src/components/manage/Blocks/HeroImageLeft/Edit.test.jsx +6 -0
  180. package/src/components/manage/Blocks/Search/README.md +20 -0
  181. package/src/components/manage/Blocks/Search/components/CheckboxFacet.jsx +16 -3
  182. package/src/components/manage/Blocks/Search/components/DateRangeFacet.jsx +120 -0
  183. package/src/components/manage/Blocks/Search/components/DateRangeFacet.test.jsx +34 -0
  184. package/src/components/manage/Blocks/Search/components/DateRangeFacetFilterListEntry.jsx +39 -0
  185. package/src/components/manage/Blocks/Search/components/Facets.jsx +24 -25
  186. package/src/components/manage/Blocks/Search/components/FilterList.jsx +37 -67
  187. package/src/components/manage/Blocks/Search/components/FilterList.test.jsx +49 -1
  188. package/src/components/manage/Blocks/Search/components/SelectFacet.jsx +9 -0
  189. package/src/components/manage/Blocks/Search/components/SelectFacetFilterListEntry.jsx +42 -0
  190. package/src/components/manage/Blocks/Search/components/SelectStyling.jsx +1 -1
  191. package/src/components/manage/Blocks/Search/components/ToggleFacet.jsx +38 -0
  192. package/src/components/manage/Blocks/Search/components/ToggleFacetFilterListEntry.jsx +39 -0
  193. package/src/components/manage/Blocks/Search/components/base.js +52 -0
  194. package/src/components/manage/Blocks/Search/components/index.js +5 -0
  195. package/src/components/manage/Blocks/Search/hocs/withSearch.jsx +68 -27
  196. package/src/components/manage/Blocks/Search/schema.js +7 -4
  197. package/src/components/manage/Blocks/Search/utils.js +16 -0
  198. package/src/components/manage/Blocks/Table/Cell.jsx +40 -14
  199. package/src/components/manage/Blocks/Table/Cell.test.jsx +7 -1
  200. package/src/components/manage/Blocks/Table/Edit.jsx +7 -3
  201. package/src/components/manage/Blocks/Table/Edit.test.jsx +6 -0
  202. package/src/components/manage/Blocks/Table/View.jsx +2 -2
  203. package/src/components/manage/Blocks/Text/Edit.jsx +56 -14
  204. package/src/components/manage/Blocks/Text/Edit.test.jsx +7 -1
  205. package/src/components/manage/Blocks/Text/View.jsx +2 -2
  206. package/src/components/manage/Blocks/Title/Edit.jsx +25 -13
  207. package/src/components/manage/Blocks/Title/Edit.test.jsx +6 -0
  208. package/src/components/manage/Sidebar/Sidebar.jsx +5 -1
  209. package/src/components/manage/Toolbar/Toolbar.jsx +3 -1
  210. package/src/components/manage/UniversalLink/UniversalLink.jsx +5 -2
  211. package/src/components/manage/Widgets/DatetimeWidget.jsx +3 -1
  212. package/src/components/manage/Widgets/UrlWidget.jsx +1 -1
  213. package/src/components/manage/Widgets/WysiwygWidget.jsx +45 -19
  214. package/src/components/manage/Widgets/WysiwygWidget.test.jsx +7 -1
  215. package/src/components/theme/View/LinkView.jsx +1 -1
  216. package/src/components/theme/View/View.jsx +5 -1
  217. package/src/config/Blocks.jsx +31 -0
  218. package/src/config/Loadables.jsx +22 -0
  219. package/src/config/RichTextEditor/Blocks.jsx +25 -20
  220. package/src/config/RichTextEditor/Plugins.jsx +38 -31
  221. package/src/config/RichTextEditor/Styles.jsx +48 -44
  222. package/src/config/RichTextEditor/index.js +25 -0
  223. package/src/config/index.js +14 -18
  224. package/src/helpers/Api/Api.js +18 -4
  225. package/src/helpers/Extensions/index.js +1 -1
  226. package/src/helpers/Extensions/withBlockExtensions.js +4 -0
  227. package/src/helpers/Extensions/withBlockExtensions.test.js +26 -1
  228. package/src/helpers/Url/Url.js +32 -1
  229. package/src/helpers/Url/urlRegex.js +7 -1
  230. package/src/helpers/index.js +2 -0
  231. package/src/middleware/api.js +21 -2
  232. package/src/server.jsx +9 -2
  233. package/test-setup-config.js +22 -7
  234. package/theme/themes/pastanaga/extras/blocks.less +29 -0
  235. package/src/components/manage/AnchorPlugin/utils/URLUtils.js +0 -36
  236. package/src/components/manage/AnchorPlugin/utils/mailRegex.js +0 -2
  237. package/src/components/manage/AnchorPlugin/utils/telRegex.js +0 -1
  238. package/src/components/manage/AnchorPlugin/utils/urlRegex.js +0 -54
@@ -7,15 +7,13 @@ import React, { Component } from 'react';
7
7
  import PropTypes from 'prop-types';
8
8
  import { connect } from 'react-redux';
9
9
  import { compose } from 'redux';
10
- import { Map } from 'immutable';
11
10
  import { readAsDataURL } from 'promise-file-reader';
12
11
  import { Button, Dimmer, Loader, Message } from 'semantic-ui-react';
13
- import { stateFromHTML } from 'draft-js-import-html';
14
- import { Editor, DefaultDraftBlockRenderMap, EditorState } from 'draft-js';
15
12
  import { isEqual } from 'lodash';
16
13
  import { defineMessages, injectIntl } from 'react-intl';
17
14
  import cx from 'classnames';
18
15
 
16
+ import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable';
19
17
  import { flattenToAppURL, getBaseUrl } from '@plone/volto/helpers';
20
18
  import { createContent } from '@plone/volto/actions';
21
19
  import { Icon, SidebarPortal, LinkMore } from '@plone/volto/components';
@@ -51,32 +49,12 @@ const messages = defineMessages({
51
49
  },
52
50
  });
53
51
 
54
- const blockTitleRenderMap = Map({
55
- unstyled: {
56
- element: 'h1',
57
- },
58
- });
59
-
60
- const blockDescriptionRenderMap = Map({
61
- unstyled: {
62
- element: 'div',
63
- },
64
- });
65
-
66
- const extendedBlockRenderMap = DefaultDraftBlockRenderMap.merge(
67
- blockTitleRenderMap,
68
- );
69
-
70
- const extendedDescripBlockRenderMap = DefaultDraftBlockRenderMap.merge(
71
- blockDescriptionRenderMap,
72
- );
73
-
74
52
  /**
75
53
  * Edit image block class.
76
54
  * @class Edit
77
55
  * @extends Component
78
56
  */
79
- class Edit extends Component {
57
+ class EditComponent extends Component {
80
58
  /**
81
59
  * Property types.
82
60
  * @property {Object} propTypes Property types.
@@ -126,7 +104,32 @@ class Edit extends Component {
126
104
  uploading: false,
127
105
  };
128
106
 
107
+ const { Map } = this.props.immutableLib;
108
+
129
109
  if (!__SERVER__) {
110
+ const { DefaultDraftBlockRenderMap, EditorState } = props.draftJs;
111
+ const { stateFromHTML } = props.draftJsImportHtml;
112
+
113
+ const blockTitleRenderMap = Map({
114
+ unstyled: {
115
+ element: 'h1',
116
+ },
117
+ });
118
+
119
+ const blockDescriptionRenderMap = Map({
120
+ unstyled: {
121
+ element: 'div',
122
+ },
123
+ });
124
+
125
+ this.extendedBlockRenderMap = DefaultDraftBlockRenderMap.merge(
126
+ blockTitleRenderMap,
127
+ );
128
+
129
+ this.extendedDescripBlockRenderMap = DefaultDraftBlockRenderMap.merge(
130
+ blockDescriptionRenderMap,
131
+ );
132
+
130
133
  let titleEditorState;
131
134
  let descriptionEditorState;
132
135
  if (props.data && props.data.title) {
@@ -187,6 +190,9 @@ class Edit extends Component {
187
190
  });
188
191
  }
189
192
 
193
+ const { EditorState } = this.props.draftJs;
194
+ const { stateFromHTML } = this.props.draftJsImportHtml;
195
+
190
196
  if (
191
197
  nextProps.data.title &&
192
198
  this.props.data.title !== nextProps.data.title &&
@@ -299,6 +305,7 @@ class Edit extends Component {
299
305
  if (__SERVER__) {
300
306
  return <div />;
301
307
  }
308
+ const { Editor } = this.props.draftJs;
302
309
  const placeholder =
303
310
  this.props.data.placeholder ||
304
311
  this.props.intl.formatMessage(messages.placeholder);
@@ -374,7 +381,7 @@ class Edit extends Component {
374
381
  readOnly={!this.props.editable}
375
382
  onChange={this.onChangeTitle}
376
383
  editorState={this.state.titleEditorState}
377
- blockRenderMap={extendedBlockRenderMap}
384
+ blockRenderMap={this.extendedBlockRenderMap}
378
385
  handleReturn={() => true}
379
386
  placeholder={this.props.intl.formatMessage(messages.title)}
380
387
  blockStyleFn={() => 'title-editor'}
@@ -416,7 +423,7 @@ class Edit extends Component {
416
423
  readOnly={!this.props.editable}
417
424
  onChange={this.onChangeDescription}
418
425
  editorState={this.state.descriptionEditorState}
419
- blockRenderMap={extendedDescripBlockRenderMap}
426
+ blockRenderMap={this.extendedDescripBlockRenderMap}
420
427
  handleReturn={() => true}
421
428
  placeholder={this.props.intl.formatMessage(
422
429
  messages.description,
@@ -460,6 +467,10 @@ class Edit extends Component {
460
467
  }
461
468
  }
462
469
 
470
+ const Edit = injectLazyLibs(['draftJs', 'immutableLib', 'draftJsImportHtml'])(
471
+ EditComponent,
472
+ );
473
+
463
474
  export default compose(
464
475
  injectIntl,
465
476
  connect(
@@ -5,6 +5,12 @@ import { Provider } from 'react-intl-redux';
5
5
 
6
6
  import Edit from './Edit';
7
7
 
8
+ jest.mock('@plone/volto/helpers/Loadable/Loadable');
9
+ beforeAll(
10
+ async () =>
11
+ await require('@plone/volto/helpers/Loadable/Loadable').__setLoadables(),
12
+ );
13
+
8
14
  global.__SERVER__ = true; // eslint-disable-line no-underscore-dangle
9
15
 
10
16
  const mockStore = configureStore();
@@ -0,0 +1,20 @@
1
+ ## Terminology
2
+
3
+ Facet: a filter control that allows the final users of search block to tweak
4
+ the query that's sent to the `@querystringsearch` endpoint. Usually, a facet is
5
+ a list of all possible values for a field, so that it can be set to either
6
+ a single value or a list of those selected values.
7
+
8
+ Facets: a control that renders the list of facet filters.
9
+
10
+ FilterList: a control rendered at the top of the block, it shows a list of all
11
+ applied facet values, with quick access to the users to clear either the facet
12
+ or the entire searchblock state.
13
+
14
+ `<FacetType>FilterListEntry`: components used to render the selected value for
15
+ a particular facet, in the FilterList component. They can be registered per
16
+ facet type.
17
+
18
+ Layout: a high-level component that controls the way the results are layed out
19
+ in relation to the facet list (facets can be displayed at top, left or right of
20
+ results).
@@ -1,13 +1,22 @@
1
1
  import React from 'react';
2
2
  import { Checkbox, Header } from 'semantic-ui-react';
3
+ import {
4
+ selectFacetSchemaEnhancer,
5
+ selectFacetStateToValue,
6
+ selectFacetValueToQuery,
7
+ } from './base';
3
8
 
4
- const SelectFacet = (props) => {
9
+ /**
10
+ * A facet that uses radio/checkboxes to provide an explicit list of values for
11
+ * filtering
12
+ */
13
+ const CheckboxFacet = (props) => {
5
14
  const { facet, choices, isMulti, onChange, value, isEditMode } = props;
6
15
  const facetValue = value;
7
16
 
8
17
  return (
9
18
  <div className="checkbox-facet">
10
- <Header as="h4">{facet.title}</Header>
19
+ <Header as="h4">{facet.title ?? facet?.field?.label}</Header>
11
20
  <div className="entries">
12
21
  {choices.map(({ label, value }, i) => (
13
22
  <div className="entry" key={value}>
@@ -43,4 +52,8 @@ const SelectFacet = (props) => {
43
52
  );
44
53
  };
45
54
 
46
- export default SelectFacet;
55
+ CheckboxFacet.schemaEnhancer = selectFacetSchemaEnhancer;
56
+ CheckboxFacet.stateToValue = selectFacetStateToValue;
57
+ CheckboxFacet.valueToQuery = selectFacetValueToQuery;
58
+
59
+ export default CheckboxFacet;
@@ -0,0 +1,120 @@
1
+ import React, { useState } from 'react';
2
+ import { Header } from 'semantic-ui-react';
3
+ import { defineMessages, injectIntl } from 'react-intl';
4
+ import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable';
5
+ import { compose } from 'redux';
6
+ import { Icon } from '@plone/volto/components';
7
+
8
+ import leftKey from '@plone/volto/icons/left-key.svg';
9
+ import rightKey from '@plone/volto/icons/right-key.svg';
10
+ import clearSVG from '@plone/volto/icons/clear.svg';
11
+
12
+ import 'react-dates/initialize';
13
+ import 'react-dates/lib/css/_datepicker.css';
14
+
15
+ const messages = defineMessages({
16
+ startDate: {
17
+ id: 'Start Date',
18
+ defaultMessage: 'Start Date',
19
+ },
20
+ endDate: {
21
+ id: 'End Date',
22
+ defaultMessage: 'End Date',
23
+ },
24
+ });
25
+
26
+ const PrevIcon = () => (
27
+ <div
28
+ className="prev-icon"
29
+ style={{
30
+ color: '#000',
31
+ left: '22px',
32
+ padding: '5px',
33
+ position: 'absolute',
34
+ top: '15px',
35
+ }}
36
+ // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
37
+ tabIndex="0"
38
+ >
39
+ <Icon name={leftKey} size="30px" />
40
+ </div>
41
+ );
42
+ const NextIcon = () => (
43
+ <div
44
+ className="next-icon"
45
+ style={{
46
+ color: '#000',
47
+ right: '22px',
48
+ padding: '5px',
49
+ position: 'absolute',
50
+ top: '15px',
51
+ }}
52
+ // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
53
+ tabIndex="0"
54
+ >
55
+ <Icon name={rightKey} size="30px" />
56
+ </div>
57
+ );
58
+
59
+ const CloseIcon = () => <Icon name={clearSVG} size="24px" className="close" />;
60
+
61
+ const DateRangeFacet = (props) => {
62
+ const { facet, isEditMode, onChange, value, reactDates, intl } = props;
63
+ const moment = props.moment.default;
64
+ const { DateRangePicker } = reactDates;
65
+ const [focused, setFocused] = useState(null);
66
+
67
+ return (
68
+ <div className="daterange-facet">
69
+ <Header as="h4">{facet?.title ?? facet?.field?.label}</Header>
70
+ <div className="ui form date-time-widget-wrapper">
71
+ <div className="ui input date-input">
72
+ <DateRangePicker
73
+ startDate={value && value[0] ? moment(value[0]) : null}
74
+ startDateId={`${facet['@id']}-start-date`}
75
+ startDatePlaceholderText={intl.formatMessage(messages.startDate)}
76
+ endDate={value && value[1] ? moment(value[1]) : null}
77
+ endDateId={`${facet['@id']}-end-date`}
78
+ endDatePlaceholderText={intl.formatMessage(messages.endDate)}
79
+ numberOfMonths={1}
80
+ disabled={isEditMode}
81
+ noBorder
82
+ showClearDates
83
+ customCloseIcon={<CloseIcon />}
84
+ displayFormat={moment.localeData(intl.locale).longDateFormat('L')}
85
+ focusedInput={focused}
86
+ onFocusChange={(focusedInput) => setFocused(focusedInput)}
87
+ onDatesChange={({ startDate, endDate }) => {
88
+ onChange(facet.field.value, [
89
+ startDate ? startDate.format('YYYY-MM-DD') : null,
90
+ endDate ? endDate.format('YYYY-MM-DD') : null,
91
+ ]);
92
+ }}
93
+ isOutsideRange={() => false}
94
+ navPrev={<PrevIcon />}
95
+ navNext={<NextIcon />}
96
+ />
97
+ </div>
98
+ </div>
99
+ </div>
100
+ );
101
+ };
102
+
103
+ DateRangeFacet.stateToValue = ({ facetSettings, index, selectedValue }) => {
104
+ return selectedValue || [null, null];
105
+ };
106
+
107
+ DateRangeFacet.valueToQuery = ({ value, facet }) => {
108
+ return value && typeof value[0] === 'string' && typeof value[1] === 'string'
109
+ ? {
110
+ i: facet.field.value,
111
+ o: 'plone.app.querystring.operation.date.between',
112
+ v: value,
113
+ }
114
+ : null;
115
+ };
116
+
117
+ export default compose(
118
+ injectLazyLibs(['reactDates', 'moment']),
119
+ injectIntl,
120
+ )(DateRangeFacet);
@@ -0,0 +1,34 @@
1
+ import React from 'react';
2
+ import configureStore from 'redux-mock-store';
3
+ import { Provider } from 'react-intl-redux';
4
+ import { waitFor, render, screen } from '@testing-library/react';
5
+
6
+ import DateRangeFacet from './DateRangeFacet';
7
+
8
+ const mockStore = configureStore();
9
+
10
+ jest.mock('@plone/volto/helpers/Loadable/Loadable');
11
+ beforeAll(
12
+ async () =>
13
+ await require('@plone/volto/helpers/Loadable/Loadable').__setLoadables(),
14
+ );
15
+
16
+ describe('DateRangeFaceg', () => {
17
+ it('renders a facet component with a date range widget', async () => {
18
+ const store = mockStore({
19
+ userSession: { token: null },
20
+ content: { data: { '@id': 'myid' } },
21
+ intl: {
22
+ locale: 'en',
23
+ messages: {},
24
+ },
25
+ });
26
+ const { container } = render(
27
+ <Provider store={store}>
28
+ <DateRangeFacet facet={{ title: 'Test Facet', field: {} }} />
29
+ </Provider>,
30
+ );
31
+ await waitFor(() => screen.getByText('Test Facet'));
32
+ expect(container).toMatchSnapshot();
33
+ });
34
+ });
@@ -0,0 +1,39 @@
1
+ import React from 'react';
2
+ import { Label, Icon } from 'semantic-ui-react';
3
+ import { defineMessages, useIntl } from 'react-intl';
4
+
5
+ const messages = defineMessages({
6
+ yes: {
7
+ id: 'yes',
8
+ defaultMessage: 'Yes',
9
+ },
10
+ no: {
11
+ id: 'no',
12
+ defaultMessage: 'No',
13
+ },
14
+ });
15
+
16
+ function DateRangeFacetFilterListEntry(props) {
17
+ const { facet, isEditMode, setFacets, facets } = props;
18
+ const intl = useIntl();
19
+
20
+ return (
21
+ <Label size="small">
22
+ {intl.formatMessage(facets[facet] ? messages.yes : messages.no)}
23
+ <Icon
24
+ name="delete"
25
+ onClick={() => {
26
+ const filteredFacets = Object.assign(
27
+ {},
28
+ ...Object.keys(facets)
29
+ .filter((f) => f !== facet)
30
+ .map((f) => ({ [f]: facets[f] })),
31
+ );
32
+ !isEditMode && setFacets(filteredFacets);
33
+ }}
34
+ />
35
+ </Label>
36
+ );
37
+ }
38
+
39
+ export default DateRangeFacetFilterListEntry;
@@ -1,6 +1,17 @@
1
1
  import React from 'react';
2
2
  import { resolveExtension } from '@plone/volto/helpers/Extensions/withBlockExtensions';
3
3
  import config from '@plone/volto/registry';
4
+ import { hasNonValueOperation, hasDateOperation } from '../utils';
5
+
6
+ const showFacet = (index) => {
7
+ const { values } = index;
8
+ return index
9
+ ? hasNonValueOperation(index.operations || []) ||
10
+ hasDateOperation(index.operations || [])
11
+ ? true
12
+ : values && Object.keys(values).length > 0
13
+ : values && Object.keys(values).length > 0;
14
+ };
4
15
 
5
16
  const Facets = (props) => {
6
17
  const {
@@ -22,9 +33,9 @@ const Facets = (props) => {
22
33
  return (
23
34
  <>
24
35
  {data?.facets
25
- ?.filter((facet) => !facet.hidden)
26
- .map((facet) => {
27
- const field = facet?.field?.value;
36
+ ?.filter((facetSettings) => !facetSettings.hidden)
37
+ .map((facetSettings) => {
38
+ const field = facetSettings?.field?.value;
28
39
  const index = querystring.indexes[field] || {};
29
40
  const { values = {} } = index;
30
41
 
@@ -41,40 +52,28 @@ const Facets = (props) => {
41
52
  : true,
42
53
  );
43
54
 
44
- const isMulti = facet.multiple;
45
- const selectedValue = facets[facet?.field?.value];
55
+ const isMulti = facetSettings.multiple;
56
+ const selectedValue = facets[facetSettings?.field?.value];
46
57
 
47
58
  // TODO :handle changing the type of facet (multi/nonmulti)
48
59
 
49
- let value = selectedValue
50
- ? isMulti
51
- ? Array.isArray(selectedValue)
52
- ? selectedValue.map((v) => ({
53
- value: v,
54
- label: index.values?.[v]?.title,
55
- }))
56
- : []
57
- : {
58
- value: selectedValue,
59
- label: index.values?.[selectedValue]?.title,
60
- }
61
- : [];
62
-
63
- const { view: FacetWidget } = resolveExtension(
60
+ const { view: FacetWidget, stateToValue } = resolveExtension(
64
61
  'type',
65
62
  search.extensions.facetWidgets.types,
66
- facet,
63
+ facetSettings,
67
64
  );
68
65
 
66
+ let value = stateToValue({ facetSettings, index, selectedValue });
67
+
69
68
  const {
70
69
  rewriteOptions = (name, options) => options,
71
70
  } = search.extensions.facetWidgets;
72
71
 
73
- return FacetWrapper && Object.keys(values).length ? (
74
- <FacetWrapper key={facet['@id']}>
72
+ return FacetWrapper && (isEditMode || showFacet(index)) ? (
73
+ <FacetWrapper key={facetSettings['@id']}>
75
74
  <FacetWidget
76
- facet={facet}
77
- choices={rewriteOptions(facet?.field?.value, choices)}
75
+ facet={facetSettings}
76
+ choices={rewriteOptions(facetSettings?.field?.value, choices)}
78
77
  isMulti={isMulti}
79
78
  value={value}
80
79
  isEditMode={isEditMode}
@@ -1,8 +1,10 @@
1
1
  import React from 'react';
2
- import { Accordion, Button, Label, Icon } from 'semantic-ui-react';
2
+ import { Accordion, Button, Icon } from 'semantic-ui-react';
3
3
  import { Icon as VoltoIcon } from '@plone/volto/components';
4
4
  import downSVG from '@plone/volto/icons/down-key.svg';
5
5
  import { defineMessages, useIntl } from 'react-intl';
6
+ import { resolveExtension } from '@plone/volto/helpers/Extensions/withBlockExtensions';
7
+ import config from '@plone/volto/registry';
6
8
 
7
9
  const messages = defineMessages({
8
10
  currentFilters: {
@@ -15,26 +17,27 @@ const messages = defineMessages({
15
17
  },
16
18
  });
17
19
 
20
+ /**
21
+ * A list of active filtered values and controls to clear those filters.
22
+ *
23
+ */
18
24
  const FilterList = (props) => {
19
- const { data, facets, setFacets, isEditMode } = props;
25
+ const { data = {}, facets = {}, setFacets, isEditMode } = props;
26
+ const definedFacets = data.facets || [];
20
27
  const [isOpened, setIsOpened] = React.useState(false);
21
28
 
22
- // TODO: the facets are merged with the default query. We need to untangle
23
- // and claim only the original facet values
24
- const showFilterList = !Object.values(facets).every((facet) => !facet.length);
29
+ const totalFilters = definedFacets.filter(
30
+ ({ field }) =>
31
+ field && Object.keys(facets).includes(field.value) && facets[field.value],
32
+ ).length;
25
33
 
26
- const currentFilters = Object.fromEntries(
27
- Object.entries(facets).filter((v) => v[1] && v[0] !== 'SearchableText'),
28
- );
29
-
30
- const totalFilters = [].concat.apply([], Object.values(currentFilters))
31
- .length;
32
-
33
- // console.log({ data, facets, showFilterList, currentFilters, totalFilters });
34
+ const {
35
+ types: facetWidgetTypes,
36
+ } = config.blocks.blocksConfig.search.extensions.facetWidgets;
34
37
 
35
38
  const intl = useIntl();
36
39
 
37
- return showFilterList && Object.keys(currentFilters).length ? (
40
+ return totalFilters ? (
38
41
  <Accordion className="filter-listing">
39
42
  <Accordion.Title
40
43
  className="filter-list-header"
@@ -61,59 +64,26 @@ const FilterList = (props) => {
61
64
  </Accordion.Title>
62
65
  <Accordion.Content className="filter-list-content" active={isOpened}>
63
66
  <div className="filter-list">
64
- {Object.keys(facets || {}).map((facet, i) => (
65
- <div key={i}>
66
- {facets[facet].length > 0 && (
67
- <div className="filter-list-group" key={i}>
68
- {data?.facets?.map((f, i) => {
69
- return facet === f?.field?.value ? (
70
- <span className="label-title" key={i}>
71
- {f.title || f?.field?.label}:
72
- </span>
73
- ) : (
74
- ''
75
- );
76
- })}
77
- {typeof facets[facet] === 'string' ? (
78
- <Label size="small" key={i}>
79
- {facets[facet]}
80
- <Icon
81
- name="delete"
82
- onClick={() => {
83
- !isEditMode &&
84
- setFacets({
85
- ...facets,
86
- [facet]: '',
87
- });
88
- }}
89
- />
90
- </Label>
91
- ) : (
92
- <>
93
- {facets[facet].map((entry, i) => (
94
- <Label size="small" key={i}>
95
- {entry}
96
- <Icon
97
- name="delete"
98
- onClick={() => {
99
- const entries = facets[facet].filter(
100
- (item) => item !== entry,
101
- );
102
- !isEditMode &&
103
- setFacets({
104
- ...facets,
105
- [facet]: entries,
106
- });
107
- }}
108
- />
109
- </Label>
110
- ))}
111
- </>
112
- )}
113
- </div>
114
- )}
115
- </div>
116
- ))}
67
+ {data.facets?.map((facetSettings, i) => {
68
+ const {
69
+ filterListComponent: FilterListComponent,
70
+ } = resolveExtension('type', facetWidgetTypes, facetSettings);
71
+ const facet = facetSettings?.field?.value;
72
+ if (!facet) return null;
73
+
74
+ return (
75
+ <div key={i}>
76
+ {Object.keys(facets).includes(facet) && !!facets[facet] && (
77
+ <div className="filter-list-group" key={i}>
78
+ <span className="label-title">
79
+ {facetSettings.title ?? facetSettings?.field?.label}
80
+ </span>
81
+ <FilterListComponent {...props} facet={facet} />
82
+ </div>
83
+ )}
84
+ </div>
85
+ );
86
+ })}
117
87
  </div>
118
88
  </Accordion.Content>
119
89
  </Accordion>
@@ -2,11 +2,45 @@ import React from 'react';
2
2
  import renderer from 'react-test-renderer';
3
3
  import configureStore from 'redux-mock-store';
4
4
  import { Provider } from 'react-intl-redux';
5
+ import config from '@plone/volto/registry';
5
6
 
6
7
  import FilterList from './FilterList';
8
+ import SelectFacet from './SelectFacet';
9
+ import SelectFacetFilterListEntry from './SelectFacetFilterListEntry';
7
10
 
8
11
  const mockStore = configureStore();
9
12
 
13
+ config.blocks.blocksConfig = {
14
+ search: {
15
+ id: 'search',
16
+ title: 'Search',
17
+ restricted: false,
18
+ mostUsed: false,
19
+ sidebarTab: 1,
20
+ security: {
21
+ addPermission: [],
22
+ view: [],
23
+ },
24
+ variations: [],
25
+ extensions: {
26
+ facetWidgets: {
27
+ types: [
28
+ {
29
+ id: 'selectFacet',
30
+ title: 'Select',
31
+ view: SelectFacet,
32
+ isDefault: true,
33
+ schemaEnhancer: SelectFacet.schemaEnhancer,
34
+ stateToValue: SelectFacet.stateToValue,
35
+ valueToQuery: SelectFacet.valueToQuery,
36
+ filterListComponent: SelectFacetFilterListEntry,
37
+ },
38
+ ],
39
+ },
40
+ },
41
+ },
42
+ };
43
+
10
44
  describe('FilterList', () => {
11
45
  it('renders filters listing component for search block', () => {
12
46
  const store = mockStore({
@@ -17,9 +51,23 @@ describe('FilterList', () => {
17
51
  messages: {},
18
52
  },
19
53
  });
54
+ const data = {
55
+ facets: [
56
+ {
57
+ '@id': 'a',
58
+ field: {
59
+ value: 'portal_type',
60
+ label: 'Portal type',
61
+ },
62
+ },
63
+ ],
64
+ };
20
65
  const component = renderer.create(
21
66
  <Provider store={store}>
22
- <FilterList facets={{ portal_type: ['Document', 'Folder'] }} />
67
+ <FilterList
68
+ data={data}
69
+ facets={{ portal_type: ['Document', 'Folder'] }}
70
+ />
23
71
  </Provider>,
24
72
  );
25
73
  const json = component.toJSON();