@plone/volto 15.0.0-alpha.5 → 15.0.0-alpha.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/ISSUE_TEMPLATE/bug_report.md +7 -6
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -1
- package/.github/workflows/docs.yml +15 -55
- package/CHANGELOG.md +32 -2
- package/CODE_OF_CONDUCT.md +0 -1
- package/COMMITLINT.md +19 -21
- package/README.md +1 -1
- package/SECURITY.md +0 -1
- package/dist/assets.json +1 -0
- package/dist/chunks.json +572 -0
- package/dist/loadable-stats.json +2099 -0
- package/dist/public/android-chrome-192x192.png +0 -0
- package/dist/public/android-chrome-512x512.png +0 -0
- package/dist/public/apple-touch-icon.png +0 -0
- package/dist/public/favicon-16x16.png +0 -0
- package/dist/public/favicon-32x32.png +0 -0
- package/dist/public/favicon.ico +0 -0
- package/dist/public/icon.svg +13 -0
- package/dist/public/index.html.spa +34 -0
- package/dist/public/robots.txt +2 -0
- package/dist/public/site.webmanifest +19 -0
- package/dist/public/static/css/15.2cdd5437.chunk.css +409 -0
- package/dist/public/static/css/15.2cdd5437.chunk.css.map +1 -0
- package/dist/public/static/css/8.37cef611.chunk.css +2 -0
- package/dist/public/static/css/8.37cef611.chunk.css.map +1 -0
- package/dist/public/static/css/client.76ea022d.chunk.css +8 -0
- package/dist/public/static/css/client.76ea022d.chunk.css.map +1 -0
- package/dist/public/static/css/plone-volto-components-manage-Widgets-DatetimeWidget.c847a331.chunk.css +2 -0
- package/dist/public/static/css/plone-volto-components-manage-Widgets-DatetimeWidget.c847a331.chunk.css.map +1 -0
- package/dist/public/static/js/0.3e794c57.chunk.js +2 -0
- package/dist/public/static/js/0.3e794c57.chunk.js.map +1 -0
- package/dist/public/static/js/1.fece2b93.chunk.js +3 -0
- package/dist/public/static/js/1.fece2b93.chunk.js.LICENSE.txt +8 -0
- package/dist/public/static/js/1.fece2b93.chunk.js.map +1 -0
- package/dist/public/static/js/15.2cdd5437.chunk.js +3 -0
- package/dist/public/static/js/15.2cdd5437.chunk.js.LICENSE.txt +120 -0
- package/dist/public/static/js/15.2cdd5437.chunk.js.map +1 -0
- package/dist/public/static/js/16.a3a0662d.chunk.js +3 -0
- package/dist/public/static/js/16.a3a0662d.chunk.js.LICENSE.txt +8 -0
- package/dist/public/static/js/16.a3a0662d.chunk.js.map +1 -0
- package/dist/public/static/js/17.9478c50d.chunk.js +2 -0
- package/dist/public/static/js/17.9478c50d.chunk.js.map +1 -0
- package/dist/public/static/js/18.388e65d0.chunk.js +2 -0
- package/dist/public/static/js/18.388e65d0.chunk.js.map +1 -0
- package/dist/public/static/js/19.7c6b4a80.chunk.js +2 -0
- package/dist/public/static/js/19.7c6b4a80.chunk.js.map +1 -0
- package/dist/public/static/js/2.e6b0d690.chunk.js +2 -0
- package/dist/public/static/js/2.e6b0d690.chunk.js.map +1 -0
- package/dist/public/static/js/20.ada67f0e.chunk.js +3 -0
- package/dist/public/static/js/20.ada67f0e.chunk.js.LICENSE.txt +8 -0
- package/dist/public/static/js/20.ada67f0e.chunk.js.map +1 -0
- package/dist/public/static/js/21.3de49ac8.chunk.js +2 -0
- package/dist/public/static/js/21.3de49ac8.chunk.js.map +1 -0
- package/dist/public/static/js/22.ecefb8f2.chunk.js +2 -0
- package/dist/public/static/js/22.ecefb8f2.chunk.js.map +1 -0
- package/dist/public/static/js/23.3027bc00.chunk.js +2 -0
- package/dist/public/static/js/23.3027bc00.chunk.js.map +1 -0
- package/dist/public/static/js/24.aaaf4abf.chunk.js +2 -0
- package/dist/public/static/js/24.aaaf4abf.chunk.js.map +1 -0
- package/dist/public/static/js/25.c4d7d5e0.chunk.js +2 -0
- package/dist/public/static/js/25.c4d7d5e0.chunk.js.map +1 -0
- package/dist/public/static/js/26.d59ac3c1.chunk.js +2 -0
- package/dist/public/static/js/26.d59ac3c1.chunk.js.map +1 -0
- package/dist/public/static/js/27.266d95dd.chunk.js +2 -0
- package/dist/public/static/js/27.266d95dd.chunk.js.map +1 -0
- package/dist/public/static/js/28.d965d304.chunk.js +2 -0
- package/dist/public/static/js/28.d965d304.chunk.js.map +1 -0
- package/dist/public/static/js/29.7d8d1992.chunk.js +2 -0
- package/dist/public/static/js/29.7d8d1992.chunk.js.map +1 -0
- package/dist/public/static/js/3.025ba843.chunk.js +2 -0
- package/dist/public/static/js/3.025ba843.chunk.js.map +1 -0
- package/dist/public/static/js/30.a3d54e31.chunk.js +2 -0
- package/dist/public/static/js/30.a3d54e31.chunk.js.map +1 -0
- package/dist/public/static/js/31.9edf64ee.chunk.js +2 -0
- package/dist/public/static/js/31.9edf64ee.chunk.js.map +1 -0
- package/dist/public/static/js/32.649dbfc2.chunk.js +2 -0
- package/dist/public/static/js/32.649dbfc2.chunk.js.map +1 -0
- package/dist/public/static/js/33.45949ca4.chunk.js +2 -0
- package/dist/public/static/js/33.45949ca4.chunk.js.map +1 -0
- package/dist/public/static/js/34.084257cf.chunk.js +2 -0
- package/dist/public/static/js/34.084257cf.chunk.js.map +1 -0
- package/dist/public/static/js/35.00679602.chunk.js +3 -0
- package/dist/public/static/js/35.00679602.chunk.js.LICENSE.txt +37 -0
- package/dist/public/static/js/35.00679602.chunk.js.map +1 -0
- package/dist/public/static/js/36.4a29fdbd.chunk.js +3 -0
- package/dist/public/static/js/36.4a29fdbd.chunk.js.LICENSE.txt +7 -0
- package/dist/public/static/js/36.4a29fdbd.chunk.js.map +1 -0
- package/dist/public/static/js/37.fbbe07b3.chunk.js +3 -0
- package/dist/public/static/js/37.fbbe07b3.chunk.js.LICENSE.txt +14 -0
- package/dist/public/static/js/37.fbbe07b3.chunk.js.map +1 -0
- package/dist/public/static/js/38.b31c59ad.chunk.js +3 -0
- package/dist/public/static/js/38.b31c59ad.chunk.js.LICENSE.txt +8 -0
- package/dist/public/static/js/38.b31c59ad.chunk.js.map +1 -0
- package/dist/public/static/js/39.69d6a789.chunk.js +2 -0
- package/dist/public/static/js/39.69d6a789.chunk.js.map +1 -0
- package/dist/public/static/js/4.bdc4a5c5.chunk.js +2 -0
- package/dist/public/static/js/4.bdc4a5c5.chunk.js.map +1 -0
- package/dist/public/static/js/40.90de6e4a.chunk.js +2 -0
- package/dist/public/static/js/40.90de6e4a.chunk.js.map +1 -0
- package/dist/public/static/js/7.7acc66bc.chunk.js +2 -0
- package/dist/public/static/js/7.7acc66bc.chunk.js.map +1 -0
- package/dist/public/static/js/8.37cef611.chunk.js +3 -0
- package/dist/public/static/js/8.37cef611.chunk.js.LICENSE.txt +25 -0
- package/dist/public/static/js/8.37cef611.chunk.js.map +1 -0
- package/dist/public/static/js/client.76ea022d.chunk.js +2 -0
- package/dist/public/static/js/client.76ea022d.chunk.js.map +1 -0
- package/dist/public/static/js/draft-js-lib-isSoftNewlineEvent.6cd05e34.chunk.js +2 -0
- package/dist/public/static/js/draft-js-lib-isSoftNewlineEvent.6cd05e34.chunk.js.map +1 -0
- package/dist/public/static/js/moment.2caa76d1.chunk.js +2 -0
- package/dist/public/static/js/moment.2caa76d1.chunk.js.map +1 -0
- package/dist/public/static/js/plone-volto-components-manage-Widgets-DatetimeWidget.c847a331.chunk.js +2 -0
- package/dist/public/static/js/plone-volto-components-manage-Widgets-DatetimeWidget.c847a331.chunk.js.map +1 -0
- package/dist/public/static/js/plone-volto-components-theme-View-EventView.433dd14b.chunk.js +2 -0
- package/dist/public/static/js/plone-volto-components-theme-View-EventView.433dd14b.chunk.js.map +1 -0
- package/dist/public/static/js/react-dates.f71abb7c.chunk.js +2 -0
- package/dist/public/static/js/react-dates.f71abb7c.chunk.js.map +1 -0
- package/dist/public/static/js/react-simple-code-editor.33478dc3.chunk.js +2 -0
- package/dist/public/static/js/react-simple-code-editor.33478dc3.chunk.js.map +1 -0
- package/dist/public/static/js/runtime~client.d372df0e.js +2 -0
- package/dist/public/static/js/runtime~client.d372df0e.js.map +1 -0
- package/dist/public/static/media/Logo.16e25cdf.svg +21 -0
- package/dist/public/static/media/block-image.8a4a14d5.svg +8 -0
- package/dist/public/static/media/block-maps.731e84f6.svg +7 -0
- package/dist/public/static/media/block-video.832fa3ff.svg +7 -0
- package/dist/public/static/media/default-image.9451b251.svg +13 -0
- package/dist/public/static/media/flags.9c74e172.png +0 -0
- package/dist/public/static/media/icons.022583c4.svg +320 -0
- package/dist/public/static/media/icons.06400145.ttf +0 -0
- package/dist/public/static/media/icons.dbc1ef8b.eot +0 -0
- package/dist/public/static/media/icons.df45b7de.woff +0 -0
- package/dist/server.js +2 -0
- package/dist/server.js.map +1 -0
- package/locales/ca/LC_MESSAGES/volto.po +14 -0
- package/locales/ca.json +1 -1
- package/locales/de/LC_MESSAGES/volto.po +14 -0
- package/locales/de.json +1 -1
- package/locales/en/LC_MESSAGES/volto.po +14 -0
- package/locales/en.json +1 -1
- package/locales/es/LC_MESSAGES/volto.po +14 -0
- package/locales/es.json +1 -1
- package/locales/eu/LC_MESSAGES/volto.po +14 -0
- package/locales/eu.json +1 -1
- package/locales/fr/LC_MESSAGES/volto.po +14 -0
- package/locales/fr.json +1 -1
- package/locales/it/LC_MESSAGES/volto.po +14 -0
- package/locales/it.json +1 -1
- package/locales/ja/LC_MESSAGES/volto.po +14 -0
- package/locales/ja.json +1 -1
- package/locales/nl/LC_MESSAGES/volto.po +14 -0
- package/locales/nl.json +1 -1
- package/locales/pt/LC_MESSAGES/volto.po +14 -0
- package/locales/pt.json +1 -1
- package/locales/pt_BR/LC_MESSAGES/volto.po +14 -0
- package/locales/pt_BR.json +1 -1
- package/locales/ro/LC_MESSAGES/volto.po +14 -0
- package/locales/ro.json +1 -1
- package/locales/volto.pot +15 -1
- package/logos/Logo.png +0 -0
- package/logos/volto-colorful.png +0 -0
- package/logos/volto-colorful.svg +35 -0
- package/logos/volto-guide.png +0 -0
- package/logos/volto-h-transparent.svg +6 -0
- package/logos/volto-transparent.png +0 -0
- package/logos/volto-transparent.svg +19 -0
- package/logos/volto-transparent2.svg +5 -0
- package/logos/volto-yellow.svg +29 -0
- package/logos/volto.sketch +0 -0
- package/netlify.toml +2 -0
- package/package.json +1 -1
- package/src/components/manage/Blocks/Search/README.md +20 -0
- package/src/components/manage/Blocks/Search/components/CheckboxFacet.jsx +16 -3
- package/src/components/manage/Blocks/Search/components/DateRangeFacet.jsx +120 -0
- package/src/components/manage/Blocks/Search/components/DateRangeFacet.test.jsx +34 -0
- package/src/components/manage/Blocks/Search/components/DateRangeFacetFilterListEntry.jsx +39 -0
- package/src/components/manage/Blocks/Search/components/Facets.jsx +24 -25
- package/src/components/manage/Blocks/Search/components/FilterList.jsx +37 -67
- package/src/components/manage/Blocks/Search/components/FilterList.test.jsx +49 -1
- package/src/components/manage/Blocks/Search/components/SelectFacet.jsx +9 -0
- package/src/components/manage/Blocks/Search/components/SelectFacetFilterListEntry.jsx +42 -0
- package/src/components/manage/Blocks/Search/components/SelectStyling.jsx +1 -1
- package/src/components/manage/Blocks/Search/components/ToggleFacet.jsx +38 -0
- package/src/components/manage/Blocks/Search/components/ToggleFacetFilterListEntry.jsx +39 -0
- package/src/components/manage/Blocks/Search/components/base.js +52 -0
- package/src/components/manage/Blocks/Search/components/index.js +5 -0
- package/src/components/manage/Blocks/Search/hocs/withSearch.jsx +68 -27
- package/src/components/manage/Blocks/Search/schema.js +7 -4
- package/src/components/manage/Blocks/Search/utils.js +16 -0
- package/src/components/manage/Widgets/DatetimeWidget.jsx +3 -1
- package/src/config/Blocks.jsx +31 -0
- package/src/helpers/Extensions/index.js +1 -1
- package/src/helpers/Extensions/withBlockExtensions.js +4 -0
- package/src/helpers/Extensions/withBlockExtensions.test.js +26 -1
- package/src/helpers/index.js +1 -0
- package/theme/themes/pastanaga/extras/blocks.less +29 -0
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { Accordion, Button,
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
29
|
+
const totalFilters = definedFacets.filter(
|
|
30
|
+
({ field }) =>
|
|
31
|
+
field && Object.keys(facets).includes(field.value) && facets[field.value],
|
|
32
|
+
).length;
|
|
25
33
|
|
|
26
|
-
const
|
|
27
|
-
|
|
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
|
|
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
|
-
{
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
<
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
|
67
|
+
<FilterList
|
|
68
|
+
data={data}
|
|
69
|
+
facets={{ portal_type: ['Document', 'Folder'] }}
|
|
70
|
+
/>
|
|
23
71
|
</Provider>,
|
|
24
72
|
);
|
|
25
73
|
const json = component.toJSON();
|
|
@@ -5,6 +5,11 @@ import {
|
|
|
5
5
|
DropdownIndicator,
|
|
6
6
|
} from '@plone/volto/components/manage/Widgets/SelectStyling';
|
|
7
7
|
import { selectTheme, customSelectStyles } from './SelectStyling';
|
|
8
|
+
import {
|
|
9
|
+
selectFacetSchemaEnhancer,
|
|
10
|
+
selectFacetStateToValue,
|
|
11
|
+
selectFacetValueToQuery,
|
|
12
|
+
} from './base';
|
|
8
13
|
|
|
9
14
|
const SelectFacet = (props) => {
|
|
10
15
|
const {
|
|
@@ -47,4 +52,8 @@ const SelectFacet = (props) => {
|
|
|
47
52
|
);
|
|
48
53
|
};
|
|
49
54
|
|
|
55
|
+
SelectFacet.schemaEnhancer = selectFacetSchemaEnhancer;
|
|
56
|
+
SelectFacet.stateToValue = selectFacetStateToValue;
|
|
57
|
+
SelectFacet.valueToQuery = selectFacetValueToQuery;
|
|
58
|
+
|
|
50
59
|
export default injectLazyLibs('reactSelect')(SelectFacet);
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Label, Icon } from 'semantic-ui-react';
|
|
3
|
+
|
|
4
|
+
function SelectFacetFilterListEntry(props) {
|
|
5
|
+
const { facet, isEditMode, setFacets, facets } = props;
|
|
6
|
+
return typeof facets[facet] === 'string' ? (
|
|
7
|
+
<Label size="small">
|
|
8
|
+
{facets[facet]}
|
|
9
|
+
<Icon
|
|
10
|
+
name="delete"
|
|
11
|
+
onClick={() => {
|
|
12
|
+
!isEditMode &&
|
|
13
|
+
setFacets({
|
|
14
|
+
...facets,
|
|
15
|
+
[facet]: '',
|
|
16
|
+
});
|
|
17
|
+
}}
|
|
18
|
+
/>
|
|
19
|
+
</Label>
|
|
20
|
+
) : (
|
|
21
|
+
<>
|
|
22
|
+
{facets[facet].map((entry, i) => (
|
|
23
|
+
<Label size="small" key={i}>
|
|
24
|
+
{entry}
|
|
25
|
+
<Icon
|
|
26
|
+
name="delete"
|
|
27
|
+
onClick={() => {
|
|
28
|
+
const entries = facets[facet].filter((item) => item !== entry);
|
|
29
|
+
!isEditMode &&
|
|
30
|
+
setFacets({
|
|
31
|
+
...facets,
|
|
32
|
+
[facet]: entries,
|
|
33
|
+
});
|
|
34
|
+
}}
|
|
35
|
+
/>
|
|
36
|
+
</Label>
|
|
37
|
+
))}
|
|
38
|
+
</>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export default SelectFacetFilterListEntry;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Radio, Header } from 'semantic-ui-react';
|
|
3
|
+
|
|
4
|
+
const ToggleFacet = (props) => {
|
|
5
|
+
const { facet, isEditMode, onChange, value } = props; // value, choices, isMulti, onChange,
|
|
6
|
+
|
|
7
|
+
return (
|
|
8
|
+
<div className="checkbox-facet">
|
|
9
|
+
<Header as="h4">{facet?.title ?? facet?.field?.label}</Header>
|
|
10
|
+
<div className="radio">
|
|
11
|
+
<Radio
|
|
12
|
+
toggle
|
|
13
|
+
checked={value || typeof value === 'string'}
|
|
14
|
+
disabled={isEditMode}
|
|
15
|
+
onChange={(e, { checked }) => {
|
|
16
|
+
onChange(facet.field.value, checked);
|
|
17
|
+
}}
|
|
18
|
+
/>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
ToggleFacet.stateToValue = ({ facetSettings, index, selectedValue }) => {
|
|
25
|
+
return selectedValue || typeof selectedValue === 'string';
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
ToggleFacet.valueToQuery = ({ value, facet }) => {
|
|
29
|
+
return value || typeof value === 'string'
|
|
30
|
+
? {
|
|
31
|
+
i: facet.field.value,
|
|
32
|
+
o: 'plone.app.querystring.operation.boolean.isTrue',
|
|
33
|
+
v: '',
|
|
34
|
+
}
|
|
35
|
+
: null;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export default ToggleFacet;
|
|
@@ -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 ToggleFacetFilterListEntry(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 ToggleFacetFilterListEntry;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { isEmpty } from 'lodash';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Converts the state (extracted from URL) to options for the facet control
|
|
5
|
+
*
|
|
6
|
+
* Shared by the SelectFacet and CheckboxFacet
|
|
7
|
+
*/
|
|
8
|
+
export const selectFacetStateToValue = ({
|
|
9
|
+
facetSettings,
|
|
10
|
+
index,
|
|
11
|
+
selectedValue,
|
|
12
|
+
}) => {
|
|
13
|
+
const isMulti = facetSettings.multiple;
|
|
14
|
+
return selectedValue
|
|
15
|
+
? isMulti
|
|
16
|
+
? Array.isArray(selectedValue)
|
|
17
|
+
? selectedValue.map((v) => ({
|
|
18
|
+
value: v,
|
|
19
|
+
label: index.values?.[v]?.title,
|
|
20
|
+
}))
|
|
21
|
+
: []
|
|
22
|
+
: {
|
|
23
|
+
value: selectedValue,
|
|
24
|
+
label: index.values?.[selectedValue]?.title,
|
|
25
|
+
}
|
|
26
|
+
: [];
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const selectFacetSchemaEnhancer = ({ schema, formData }) => {
|
|
30
|
+
// adds (enables) the 'multiple' field after the 'type' dropdown
|
|
31
|
+
let { fields } = schema.fieldsets[0];
|
|
32
|
+
const pos = fields.indexOf('type') + 1;
|
|
33
|
+
fields = [
|
|
34
|
+
...fields.slice(0, pos),
|
|
35
|
+
'multiple',
|
|
36
|
+
...fields.slice(pos, fields.length),
|
|
37
|
+
];
|
|
38
|
+
schema.fieldsets[0].fields = fields;
|
|
39
|
+
return schema;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const selectFacetValueToQuery = ({ value, facet }) => {
|
|
43
|
+
return !isEmpty(value)
|
|
44
|
+
? {
|
|
45
|
+
i: facet.field.value,
|
|
46
|
+
o: Array.isArray(value)
|
|
47
|
+
? 'plone.app.querystring.operation.list.contains'
|
|
48
|
+
: 'plone.app.querystring.operation.selection.is',
|
|
49
|
+
v: value,
|
|
50
|
+
}
|
|
51
|
+
: undefined;
|
|
52
|
+
};
|
|
@@ -2,6 +2,11 @@ export SearchDetails from './SearchDetails';
|
|
|
2
2
|
export Facets from './Facets';
|
|
3
3
|
export SelectFacet from './SelectFacet';
|
|
4
4
|
export CheckboxFacet from './CheckboxFacet';
|
|
5
|
+
export DateRangeFacet from './DateRangeFacet';
|
|
5
6
|
export SearchInput from './SearchInput';
|
|
6
7
|
export FilterList from './FilterList';
|
|
7
8
|
export SortOn from './SortOn';
|
|
9
|
+
export ToggleFacet from './ToggleFacet';
|
|
10
|
+
export SelectFacetFilterListEntry from './SelectFacetFilterListEntry';
|
|
11
|
+
export ToggleFacetFilterListEntry from './ToggleFacetFilterListEntry';
|
|
12
|
+
export DateRangeFacetFilterListEntry from './DateRangeFacetFilterListEntry';
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { useSelector } from 'react-redux';
|
|
3
|
-
import { isEmpty } from 'lodash';
|
|
4
3
|
import qs from 'query-string';
|
|
5
4
|
import { useLocation, useHistory } from 'react-router-dom';
|
|
6
5
|
|
|
6
|
+
import { resolveExtension } from '@plone/volto/helpers/Extensions/withBlockExtensions';
|
|
7
|
+
import config from '@plone/volto/registry';
|
|
8
|
+
|
|
7
9
|
function getDisplayName(WrappedComponent) {
|
|
8
10
|
return WrappedComponent.displayName || WrappedComponent.name || 'Component';
|
|
9
11
|
}
|
|
@@ -25,16 +27,30 @@ const PAQO = 'plone.app.querystring.operation';
|
|
|
25
27
|
*
|
|
26
28
|
*/
|
|
27
29
|
function getInitialState(data, facets, urlSearchText, id) {
|
|
30
|
+
const {
|
|
31
|
+
types: facetWidgetTypes,
|
|
32
|
+
} = config.blocks.blocksConfig.search.extensions.facetWidgets;
|
|
33
|
+
const facetSettings = data?.facets || [];
|
|
34
|
+
|
|
28
35
|
return {
|
|
29
36
|
query: [
|
|
30
37
|
...(data.query?.query || []),
|
|
31
|
-
...
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
+
...(facetSettings || [])
|
|
39
|
+
.map((facet) => {
|
|
40
|
+
if (!facet?.field) return null;
|
|
41
|
+
|
|
42
|
+
const { valueToQuery } = resolveExtension(
|
|
43
|
+
'type',
|
|
44
|
+
facetWidgetTypes,
|
|
45
|
+
facet,
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const name = facet.field.value;
|
|
49
|
+
const value = facets[name];
|
|
50
|
+
|
|
51
|
+
return valueToQuery({ value, facet });
|
|
52
|
+
})
|
|
53
|
+
.filter((f) => !!f),
|
|
38
54
|
...(urlSearchText
|
|
39
55
|
? [
|
|
40
56
|
{
|
|
@@ -54,27 +70,42 @@ function getInitialState(data, facets, urlSearchText, id) {
|
|
|
54
70
|
}
|
|
55
71
|
|
|
56
72
|
/**
|
|
57
|
-
* "Normalizes" the search state to something that's serializable
|
|
58
|
-
*
|
|
73
|
+
* "Normalizes" the search state to something that's serializable
|
|
74
|
+
* (for querying) and used to compute data for the ListingBody
|
|
59
75
|
*
|
|
60
76
|
* @function normalizeState
|
|
61
77
|
*
|
|
62
78
|
*/
|
|
63
|
-
function normalizeState({
|
|
79
|
+
function normalizeState({
|
|
80
|
+
query, // base query
|
|
81
|
+
facets, // facet values
|
|
82
|
+
id, // block id
|
|
83
|
+
searchText, // SearchableText
|
|
84
|
+
sortOn,
|
|
85
|
+
sortOrder,
|
|
86
|
+
facetSettings, // data.facets extracted from block data
|
|
87
|
+
}) {
|
|
88
|
+
const {
|
|
89
|
+
types: facetWidgetTypes,
|
|
90
|
+
} = config.blocks.blocksConfig.search.extensions.facetWidgets;
|
|
91
|
+
|
|
64
92
|
const params = {
|
|
65
93
|
query: [
|
|
66
94
|
...(query.query || []),
|
|
67
|
-
...
|
|
68
|
-
!
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
95
|
+
...(facetSettings || []).map((facet) => {
|
|
96
|
+
if (!facet?.field) return null;
|
|
97
|
+
|
|
98
|
+
const { valueToQuery } = resolveExtension(
|
|
99
|
+
'type',
|
|
100
|
+
facetWidgetTypes,
|
|
101
|
+
facet,
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
const name = facet.field.value;
|
|
105
|
+
const value = facets[name];
|
|
106
|
+
|
|
107
|
+
return valueToQuery({ value, facet });
|
|
108
|
+
}),
|
|
78
109
|
].filter((o) => !!o),
|
|
79
110
|
sort_on: sortOn || query.sort_on,
|
|
80
111
|
sort_order: sortOrder || query.sort_order,
|
|
@@ -217,7 +248,7 @@ const withSearch = (options) => (WrappedComponent) => {
|
|
|
217
248
|
const [facets, setFacets] = React.useState(
|
|
218
249
|
Object.assign(
|
|
219
250
|
{},
|
|
220
|
-
...urlQuery.map(({ i, v }) => ({ [i]: v })),
|
|
251
|
+
...urlQuery.map(({ i, v }) => ({ [i]: v })), // TODO: the 'o' should be kept. This would be a major refactoring of the facets
|
|
221
252
|
|
|
222
253
|
// support for simple filters like ?Subject=something
|
|
223
254
|
// TODO: since the move to hash params this is no longer working.
|
|
@@ -245,19 +276,21 @@ const withSearch = (options) => (WrappedComponent) => {
|
|
|
245
276
|
);
|
|
246
277
|
|
|
247
278
|
const timeoutRef = React.useRef();
|
|
279
|
+
const facetSettings = data?.facets;
|
|
248
280
|
|
|
249
281
|
const onTriggerSearch = React.useCallback(
|
|
250
|
-
(
|
|
282
|
+
(toSearchText, toSearchFacets, toSortOn, toSortOrder) => {
|
|
251
283
|
if (timeoutRef.current) clearTimeout(timeoutRef.current);
|
|
252
284
|
timeoutRef.current = setTimeout(
|
|
253
285
|
() => {
|
|
254
286
|
const searchData = normalizeState({
|
|
287
|
+
id,
|
|
255
288
|
query: data.query || {},
|
|
256
289
|
facets: toSearchFacets || facets,
|
|
257
|
-
|
|
258
|
-
searchText: toSearch,
|
|
290
|
+
searchText: toSearchText,
|
|
259
291
|
sortOn: toSortOn || sortOn,
|
|
260
292
|
sortOrder: toSortOrder || sortOrder,
|
|
293
|
+
facetSettings,
|
|
261
294
|
});
|
|
262
295
|
if (toSearchFacets) setFacets(toSearchFacets);
|
|
263
296
|
if (toSortOn) setSortOn(toSortOn);
|
|
@@ -268,7 +301,15 @@ const withSearch = (options) => (WrappedComponent) => {
|
|
|
268
301
|
toSearchFacets ? inputDelay / 3 : inputDelay,
|
|
269
302
|
);
|
|
270
303
|
},
|
|
271
|
-
[
|
|
304
|
+
[
|
|
305
|
+
data.query,
|
|
306
|
+
facets,
|
|
307
|
+
id,
|
|
308
|
+
setLocationSearchData,
|
|
309
|
+
sortOn,
|
|
310
|
+
sortOrder,
|
|
311
|
+
facetSettings,
|
|
312
|
+
],
|
|
272
313
|
);
|
|
273
314
|
|
|
274
315
|
const querystringResults = useSelector(
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import config from '@plone/volto/registry';
|
|
2
2
|
import { defineMessages } from 'react-intl';
|
|
3
3
|
import { cloneDeep } from 'lodash';
|
|
4
|
+
import { hasNonValueOperation, hasDateOperation } from './utils';
|
|
4
5
|
|
|
5
6
|
const messages = defineMessages({
|
|
6
7
|
searchBlock: {
|
|
@@ -99,9 +100,9 @@ const messages = defineMessages({
|
|
|
99
100
|
|
|
100
101
|
const enhanceSchema = (originalSchema, formData) => {
|
|
101
102
|
const extensionName = 'facetWidgets';
|
|
102
|
-
const extensionType = 'type';
|
|
103
|
+
const extensionType = 'type'; // property name in stored block data
|
|
103
104
|
const variations =
|
|
104
|
-
config.blocks.blocksConfig.search.extensions[extensionName]
|
|
105
|
+
config.blocks.blocksConfig.search.extensions[extensionName].types;
|
|
105
106
|
|
|
106
107
|
const activeItemName = formData?.[extensionType];
|
|
107
108
|
let activeItem = variations?.find((item) => item.id === activeItemName);
|
|
@@ -122,7 +123,7 @@ const FacetSchema = ({ intl }) => ({
|
|
|
122
123
|
{
|
|
123
124
|
id: 'default',
|
|
124
125
|
title: 'Default',
|
|
125
|
-
fields: ['title', 'field', 'type', '
|
|
126
|
+
fields: ['title', 'field', 'type', 'hidden'],
|
|
126
127
|
},
|
|
127
128
|
],
|
|
128
129
|
properties: {
|
|
@@ -141,7 +142,9 @@ const FacetSchema = ({ intl }) => ({
|
|
|
141
142
|
return Object.assign(
|
|
142
143
|
{},
|
|
143
144
|
...Object.keys(options).map((k) =>
|
|
144
|
-
Object.keys(options[k].values || {}).length
|
|
145
|
+
Object.keys(options[k].values || {}).length ||
|
|
146
|
+
hasNonValueOperation(options[k].operations) ||
|
|
147
|
+
hasDateOperation(options[k].operations)
|
|
145
148
|
? { [k]: options[k] }
|
|
146
149
|
: {},
|
|
147
150
|
),
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export const NONVALUE_OPERATIONS = new Set([
|
|
2
|
+
'plone.app.querystring.operation.boolean.isFalse',
|
|
3
|
+
'plone.app.querystring.operation.boolean.isTrue',
|
|
4
|
+
]);
|
|
5
|
+
|
|
6
|
+
export const DATE_OPERATIONS = new Set([
|
|
7
|
+
'plone.app.querystring.operation.date.between',
|
|
8
|
+
]);
|
|
9
|
+
|
|
10
|
+
export const hasNonValueOperation = (ops) => {
|
|
11
|
+
return ops.filter((x) => NONVALUE_OPERATIONS.has(x)).length > 0;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const hasDateOperation = (ops) => {
|
|
15
|
+
return ops.filter((x) => DATE_OPERATIONS.has(x)).length > 0;
|
|
16
|
+
};
|
|
@@ -178,7 +178,9 @@ export class DatetimeWidgetComponent extends Component {
|
|
|
178
178
|
onFocusChange = ({ focused }) => this.setState({ focused });
|
|
179
179
|
|
|
180
180
|
render() {
|
|
181
|
-
const { id,
|
|
181
|
+
const { id, resettable, intl, reactDates, widgetOptions } = this.props;
|
|
182
|
+
const noPastDates =
|
|
183
|
+
this.props.noPastDates || widgetOptions?.pattern_options?.noPastDates;
|
|
182
184
|
const moment = this.props.moment.default;
|
|
183
185
|
const datetime = this.getInternalValue();
|
|
184
186
|
const dateOnly = this.getDateOnly();
|