@truedat/core 4.46.0 → 4.46.4
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/CHANGELOG.md +18 -0
- package/package.json +3 -3
- package/src/components/FiltersLoader.js +2 -1
- package/src/components/RichTextEditor.js +51 -47
- package/src/components/SafeLink.js +32 -0
- package/src/components/SelectedFilters.js +64 -55
- package/src/components/__tests__/SafeLink.spec.js +18 -0
- package/src/components/index.js +2 -0
- package/src/routes.js +2 -2
- package/src/selectors/makeActiveFiltersSelector.js +2 -2
- package/src/services/__tests__/filters.spec.js +14 -6
- package/src/services/__tests__/validation.spec.js +15 -0
- package/src/services/filters.js +7 -7
- package/src/services/validation.js +10 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [4.46.4] 2022-06-16
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- [TD-4720] When merging default filters and user-specified filters, the
|
|
8
|
+
`taxonomy` filter specified now overrides the default value. This change was
|
|
9
|
+
needed for the domain filter to work in the `<DomainConcepts />` tab.
|
|
10
|
+
|
|
11
|
+
## [4.46.2] 2022-06-16
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- [TD-4739] `SafeLink` component to prevent rendering unsafe hyperlinks
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- [TD-4739] `RichTextEditor` now renders hyperlinks using `SafeLink`
|
|
20
|
+
|
|
3
21
|
## [4.44.5] 2022-05-20
|
|
4
22
|
|
|
5
23
|
### Changed
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@truedat/core",
|
|
3
|
-
"version": "4.46.
|
|
3
|
+
"version": "4.46.4",
|
|
4
4
|
"description": "Truedat Web Core",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"jsnext:main": "src/index.js",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"@testing-library/jest-dom": "^5.16.4",
|
|
36
36
|
"@testing-library/react": "^12.0.0",
|
|
37
37
|
"@testing-library/user-event": "^13.2.1",
|
|
38
|
-
"@truedat/test": "4.46.
|
|
38
|
+
"@truedat/test": "4.46.4",
|
|
39
39
|
"babel-jest": "^28.1.0",
|
|
40
40
|
"babel-plugin-dynamic-import-node": "^2.3.3",
|
|
41
41
|
"babel-plugin-lodash": "^3.3.4",
|
|
@@ -112,5 +112,5 @@
|
|
|
112
112
|
"react-dom": ">= 16.8.6 < 17",
|
|
113
113
|
"semantic-ui-react": ">= 0.88.2 < 2.1"
|
|
114
114
|
},
|
|
115
|
-
"gitHead": "
|
|
115
|
+
"gitHead": "dcd0aa42ffe1fb816945154ec2a2d06889dca7e7"
|
|
116
116
|
}
|
|
@@ -25,13 +25,14 @@ export const FiltersLoader = ({
|
|
|
25
25
|
}
|
|
26
26
|
: _.omit([selectedFilter])({ ...defaultFilters, ...filters });
|
|
27
27
|
fetchFilters({ filters: mergedFilters });
|
|
28
|
-
}, [fetchFilters, filters, selectedFilter]);
|
|
28
|
+
}, [fetchFilters, filters, selectedFilter, defaultFilters]);
|
|
29
29
|
return null;
|
|
30
30
|
};
|
|
31
31
|
|
|
32
32
|
FiltersLoader.propTypes = {
|
|
33
33
|
clearFilters: PropTypes.func.isRequired,
|
|
34
34
|
clearSort: PropTypes.func,
|
|
35
|
+
defaultFilters: PropTypes.object,
|
|
35
36
|
fetchFilters: PropTypes.func.isRequired,
|
|
36
37
|
filters: PropTypes.object.isRequired,
|
|
37
38
|
selectedFilter: PropTypes.string,
|
|
@@ -1,25 +1,31 @@
|
|
|
1
1
|
import _ from "lodash/fp";
|
|
2
2
|
import React from "react";
|
|
3
|
+
import PropTypes from "prop-types";
|
|
4
|
+
import { isKeyHotkey } from "is-hotkey";
|
|
3
5
|
import isUrl from "is-url";
|
|
6
|
+
import { injectIntl } from "react-intl";
|
|
4
7
|
import { Editor, getEventTransfer } from "slate-react";
|
|
5
8
|
import { Value } from "slate";
|
|
6
|
-
import { isKeyHotkey } from "is-hotkey";
|
|
7
9
|
import { Menu, Icon } from "semantic-ui-react";
|
|
10
|
+
import { validUrl } from "../services/validation";
|
|
11
|
+
import SafeLink from "./SafeLink";
|
|
8
12
|
|
|
9
13
|
const DEFAULT_NODE = "paragraph";
|
|
10
14
|
|
|
11
15
|
const isBoldHotkey = isKeyHotkey("mod+b");
|
|
12
16
|
const isItalicHotkey = isKeyHotkey("mod+i");
|
|
13
17
|
const isUnderlinedHotkey = isKeyHotkey("mod+u");
|
|
14
|
-
//const isCodeHotkey = isKeyHotkey("mod+`");
|
|
15
18
|
|
|
16
|
-
function wrapLink(change,
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
function wrapLink(change, value) {
|
|
20
|
+
const href = validUrl(value);
|
|
21
|
+
if (href) {
|
|
22
|
+
change.wrapInline({
|
|
23
|
+
type: "link",
|
|
24
|
+
data: { href },
|
|
25
|
+
});
|
|
21
26
|
|
|
22
|
-
|
|
27
|
+
change.moveToEnd();
|
|
28
|
+
}
|
|
23
29
|
}
|
|
24
30
|
|
|
25
31
|
function unwrapLink(change) {
|
|
@@ -37,27 +43,27 @@ export class RichTextEditor extends React.Component {
|
|
|
37
43
|
{
|
|
38
44
|
object: "block",
|
|
39
45
|
type: "paragraph",
|
|
40
|
-
nodes: []
|
|
41
|
-
}
|
|
42
|
-
]
|
|
43
|
-
}
|
|
46
|
+
nodes: [],
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
},
|
|
44
50
|
}
|
|
45
51
|
: value;
|
|
46
52
|
|
|
47
53
|
this.state = { value: Value.fromJSON(json) };
|
|
48
54
|
}
|
|
49
55
|
|
|
50
|
-
hasMark = type => {
|
|
56
|
+
hasMark = (type) => {
|
|
51
57
|
const { value } = this.state;
|
|
52
|
-
return value.activeMarks.some(mark => mark.type == type);
|
|
58
|
+
return value.activeMarks.some((mark) => mark.type == type);
|
|
53
59
|
};
|
|
54
60
|
|
|
55
|
-
hasBlock = type => {
|
|
61
|
+
hasBlock = (type) => {
|
|
56
62
|
const { value } = this.state;
|
|
57
|
-
return value.blocks.some(node => node.type == type);
|
|
63
|
+
return value.blocks.some((node) => node.type == type);
|
|
58
64
|
};
|
|
59
65
|
|
|
60
|
-
ref = editor => {
|
|
66
|
+
ref = (editor) => {
|
|
61
67
|
this.editor = editor;
|
|
62
68
|
};
|
|
63
69
|
|
|
@@ -79,7 +85,7 @@ export class RichTextEditor extends React.Component {
|
|
|
79
85
|
<div
|
|
80
86
|
style={{
|
|
81
87
|
border: "1px solid rgba(34, 36, 38, 0.15)",
|
|
82
|
-
borderRadius: "0.28571429rem"
|
|
88
|
+
borderRadius: "0.28571429rem",
|
|
83
89
|
}}
|
|
84
90
|
>
|
|
85
91
|
<Menu>
|
|
@@ -143,7 +149,7 @@ export class RichTextEditor extends React.Component {
|
|
|
143
149
|
<Menu.Item
|
|
144
150
|
icon
|
|
145
151
|
disabled={!isActive}
|
|
146
|
-
onMouseDown={event => this.onClickMark(event, type)}
|
|
152
|
+
onMouseDown={(event) => this.onClickMark(event, type)}
|
|
147
153
|
>
|
|
148
154
|
<Icon name={icon} />
|
|
149
155
|
</Menu.Item>
|
|
@@ -152,7 +158,7 @@ export class RichTextEditor extends React.Component {
|
|
|
152
158
|
|
|
153
159
|
renderBlockButton = (type, icon) => {
|
|
154
160
|
const {
|
|
155
|
-
value: { document, blocks }
|
|
161
|
+
value: { document, blocks },
|
|
156
162
|
} = this.state;
|
|
157
163
|
const parent =
|
|
158
164
|
blocks.size != 0 ? document.getParent(blocks.first().key) : null;
|
|
@@ -164,7 +170,7 @@ export class RichTextEditor extends React.Component {
|
|
|
164
170
|
<Menu.Item
|
|
165
171
|
icon
|
|
166
172
|
disabled={!isActive}
|
|
167
|
-
onMouseDown={event => this.onClickBlock(event, type)}
|
|
173
|
+
onMouseDown={(event) => this.onClickBlock(event, type)}
|
|
168
174
|
>
|
|
169
175
|
<Icon name={icon} />
|
|
170
176
|
</Menu.Item>
|
|
@@ -178,7 +184,7 @@ export class RichTextEditor extends React.Component {
|
|
|
178
184
|
<Menu.Item
|
|
179
185
|
icon
|
|
180
186
|
disabled={!isActive}
|
|
181
|
-
onMouseDown={event => this.onClickLink(event, type)}
|
|
187
|
+
onMouseDown={(event) => this.onClickLink(event, type)}
|
|
182
188
|
>
|
|
183
189
|
<Icon name={icon} />
|
|
184
190
|
</Menu.Item>
|
|
@@ -210,17 +216,10 @@ export class RichTextEditor extends React.Component {
|
|
|
210
216
|
case "numbered-list":
|
|
211
217
|
return <ol {...attributes}> {children}</ol>;
|
|
212
218
|
case "link":
|
|
213
|
-
const { data } = node;
|
|
214
|
-
const href = data.get("href");
|
|
215
219
|
return (
|
|
216
|
-
<
|
|
217
|
-
{...attributes}
|
|
218
|
-
href={href}
|
|
219
|
-
rel="noopener noreferrer"
|
|
220
|
-
target="_blank"
|
|
221
|
-
>
|
|
220
|
+
<SafeLink href={node.data?.get("href")} {...attributes}>
|
|
222
221
|
{children}
|
|
223
|
-
</
|
|
222
|
+
</SafeLink>
|
|
224
223
|
);
|
|
225
224
|
default:
|
|
226
225
|
return next();
|
|
@@ -249,23 +248,16 @@ export class RichTextEditor extends React.Component {
|
|
|
249
248
|
|
|
250
249
|
switch (node.type) {
|
|
251
250
|
case "link": {
|
|
252
|
-
const
|
|
253
|
-
const href = data.get("href");
|
|
251
|
+
const props = _.omit(["ref"])(attributes);
|
|
254
252
|
return (
|
|
255
|
-
<
|
|
256
|
-
{...attributes}
|
|
257
|
-
href={href}
|
|
258
|
-
rel="noopener noreferrer"
|
|
259
|
-
target="_blank"
|
|
260
|
-
>
|
|
253
|
+
<SafeLink href={node.data?.get("href")} {...props}>
|
|
261
254
|
{children}
|
|
262
|
-
</
|
|
255
|
+
</SafeLink>
|
|
263
256
|
);
|
|
264
257
|
}
|
|
265
258
|
|
|
266
|
-
default:
|
|
259
|
+
default:
|
|
267
260
|
return next();
|
|
268
|
-
}
|
|
269
261
|
}
|
|
270
262
|
};
|
|
271
263
|
|
|
@@ -338,8 +330,11 @@ export class RichTextEditor extends React.Component {
|
|
|
338
330
|
}
|
|
339
331
|
} else {
|
|
340
332
|
const isList = this.hasBlock("list-item");
|
|
341
|
-
const isType = value.blocks.some(block => {
|
|
342
|
-
return !!document.getClosest(
|
|
333
|
+
const isType = value.blocks.some((block) => {
|
|
334
|
+
return !!document.getClosest(
|
|
335
|
+
block.key,
|
|
336
|
+
(parent) => parent.type == type
|
|
337
|
+
);
|
|
343
338
|
});
|
|
344
339
|
|
|
345
340
|
if (isList && isType) {
|
|
@@ -361,10 +356,10 @@ export class RichTextEditor extends React.Component {
|
|
|
361
356
|
|
|
362
357
|
hasLinks = () => {
|
|
363
358
|
const { value } = this.state;
|
|
364
|
-
return value.inlines.some(inline => inline.type == "link");
|
|
359
|
+
return value.inlines.some((inline) => inline.type == "link");
|
|
365
360
|
};
|
|
366
361
|
|
|
367
|
-
onClickLink = event => {
|
|
362
|
+
onClickLink = (event) => {
|
|
368
363
|
event.preventDefault();
|
|
369
364
|
|
|
370
365
|
const { editor } = this;
|
|
@@ -388,4 +383,13 @@ export class RichTextEditor extends React.Component {
|
|
|
388
383
|
};
|
|
389
384
|
}
|
|
390
385
|
|
|
391
|
-
|
|
386
|
+
RichTextEditor.propTypes = {
|
|
387
|
+
label: PropTypes.string,
|
|
388
|
+
name: PropTypes.string,
|
|
389
|
+
onChange: PropTypes.func,
|
|
390
|
+
readOnly: PropTypes.bool,
|
|
391
|
+
required: PropTypes.bool,
|
|
392
|
+
value: PropTypes.object,
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
export default injectIntl(RichTextEditor);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import PropTypes from "prop-types";
|
|
3
|
+
import { validUrl } from "../services/validation";
|
|
4
|
+
|
|
5
|
+
export const SafeLink = ({ className, href, children, ...props }) =>
|
|
6
|
+
validUrl(href) ? (
|
|
7
|
+
<a
|
|
8
|
+
{...props}
|
|
9
|
+
className={className}
|
|
10
|
+
href={href}
|
|
11
|
+
rel="noopener noreferrer"
|
|
12
|
+
target="_blank"
|
|
13
|
+
>
|
|
14
|
+
{children}
|
|
15
|
+
</a>
|
|
16
|
+
) : (
|
|
17
|
+
<a
|
|
18
|
+
className={className}
|
|
19
|
+
title={href}
|
|
20
|
+
style={{ color: "red", backgroundColor: "yellow" }}
|
|
21
|
+
>
|
|
22
|
+
{children}
|
|
23
|
+
</a>
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
SafeLink.propTypes = {
|
|
27
|
+
href: PropTypes.string,
|
|
28
|
+
children: PropTypes.node,
|
|
29
|
+
className: PropTypes.string,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export default SafeLink;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import _ from "lodash/fp";
|
|
2
|
-
import React from "react";
|
|
2
|
+
import React, { useEffect } from "react";
|
|
3
3
|
import PropTypes from "prop-types";
|
|
4
4
|
import { FormattedMessage } from "react-intl";
|
|
5
5
|
import FilterDropdown from "./FilterDropdown";
|
|
@@ -25,61 +25,70 @@ export const SelectedFilters = ({
|
|
|
25
25
|
toggleFilterValue,
|
|
26
26
|
userFilters,
|
|
27
27
|
userFilterScope,
|
|
28
|
-
}) =>
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const options = _.isEqual(filter, selectedFilter)
|
|
49
|
-
? selectedFilterValues
|
|
50
|
-
: null;
|
|
51
|
-
const props = {
|
|
52
|
-
key: filter,
|
|
53
|
-
activeValues,
|
|
54
|
-
closeFilter,
|
|
55
|
-
filter,
|
|
56
|
-
loading,
|
|
57
|
-
openFilter,
|
|
58
|
-
options,
|
|
59
|
-
removeFilter,
|
|
60
|
-
toggleFilterValue,
|
|
61
|
-
};
|
|
62
|
-
return selectedFilter === "taxonomy" ? (
|
|
63
|
-
<FilterMultilevelDropdown {...props} />
|
|
64
|
-
) : (
|
|
65
|
-
<FilterDropdown {...props} />
|
|
66
|
-
);
|
|
67
|
-
})}
|
|
68
|
-
<a className="resetFilters" onClick={() => resetFilters()}>
|
|
69
|
-
<FormattedMessage id="search.clear_filters" />
|
|
70
|
-
</a>
|
|
71
|
-
{saveFilters && (
|
|
72
|
-
<ModalSaveFilter
|
|
73
|
-
saveFilters={saveFilters}
|
|
74
|
-
activeFilters={activeFilters}
|
|
75
|
-
scope={userFilterScope}
|
|
76
|
-
/>
|
|
77
|
-
)}
|
|
78
|
-
</>
|
|
28
|
+
}) => {
|
|
29
|
+
useEffect(
|
|
30
|
+
() => () => {
|
|
31
|
+
resetFilters();
|
|
32
|
+
},
|
|
33
|
+
[resetFilters]
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<>
|
|
38
|
+
{_.isEmpty(userFilters) ? null : (
|
|
39
|
+
<UserFilters
|
|
40
|
+
applyUserFilter={applyUserFilter}
|
|
41
|
+
userFilterScope={userFilterScope}
|
|
42
|
+
deleteUserFilter={deleteUserFilter}
|
|
43
|
+
resetFilters={resetFilters}
|
|
44
|
+
selectedUserFilter={selectedUserFilter}
|
|
45
|
+
userFilters={userFilters}
|
|
46
|
+
disabled={loading}
|
|
47
|
+
/>
|
|
79
48
|
)}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
49
|
+
<div className="selectedFilters">
|
|
50
|
+
{_.isEmpty(selectedFilters) ? null : (
|
|
51
|
+
<>
|
|
52
|
+
<div className="appliedFilters">
|
|
53
|
+
<FormattedMessage id="search.applied_filters" />
|
|
54
|
+
</div>
|
|
55
|
+
{selectedFilters.map((filter) => {
|
|
56
|
+
const options = _.isEqual(filter, selectedFilter)
|
|
57
|
+
? selectedFilterValues
|
|
58
|
+
: null;
|
|
59
|
+
const props = {
|
|
60
|
+
key: filter,
|
|
61
|
+
activeValues,
|
|
62
|
+
closeFilter,
|
|
63
|
+
filter,
|
|
64
|
+
loading,
|
|
65
|
+
openFilter,
|
|
66
|
+
options,
|
|
67
|
+
removeFilter,
|
|
68
|
+
toggleFilterValue,
|
|
69
|
+
};
|
|
70
|
+
return selectedFilter === "taxonomy" ? (
|
|
71
|
+
<FilterMultilevelDropdown {...props} />
|
|
72
|
+
) : (
|
|
73
|
+
<FilterDropdown {...props} />
|
|
74
|
+
);
|
|
75
|
+
})}
|
|
76
|
+
<a className="resetFilters" onClick={() => resetFilters()}>
|
|
77
|
+
<FormattedMessage id="search.clear_filters" />
|
|
78
|
+
</a>
|
|
79
|
+
{saveFilters && (
|
|
80
|
+
<ModalSaveFilter
|
|
81
|
+
saveFilters={saveFilters}
|
|
82
|
+
activeFilters={activeFilters}
|
|
83
|
+
scope={userFilterScope}
|
|
84
|
+
/>
|
|
85
|
+
)}
|
|
86
|
+
</>
|
|
87
|
+
)}
|
|
88
|
+
</div>
|
|
89
|
+
</>
|
|
90
|
+
);
|
|
91
|
+
};
|
|
83
92
|
|
|
84
93
|
SelectedFilters.propTypes = {
|
|
85
94
|
activeFilters: PropTypes.object,
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { render } from "@truedat/test/render";
|
|
3
|
+
import SafeLink from "../SafeLink";
|
|
4
|
+
|
|
5
|
+
describe("<SafeLink />", () => {
|
|
6
|
+
it("renders an href if url is http or https", () => {
|
|
7
|
+
const { queryByRole } = render(<SafeLink href="https://truedat.com" />);
|
|
8
|
+
expect(queryByRole("link")).toBeInTheDocument();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
["javascript:foo", "not_a_valid_url"].forEach((href) => {
|
|
12
|
+
it("renders non-navigable span if url is potentially unsafe", () => {
|
|
13
|
+
const { queryByRole, queryByTitle } = render(<SafeLink href={href} />);
|
|
14
|
+
expect(queryByRole("link")).not.toBeInTheDocument();
|
|
15
|
+
expect(queryByTitle(href)).toBeInTheDocument();
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
});
|
package/src/components/index.js
CHANGED
|
@@ -30,6 +30,7 @@ import QualityMenu from "./QualityMenu";
|
|
|
30
30
|
import Redirector from "./Redirector";
|
|
31
31
|
import RichTextEditor from "./RichTextEditor";
|
|
32
32
|
import RouteListener from "./RouteListener";
|
|
33
|
+
import SafeLink from "./SafeLink";
|
|
33
34
|
import ScrollToTop from "./ScrollToTop";
|
|
34
35
|
import SearchInput from "./SearchInput";
|
|
35
36
|
import SearchMenu from "./SearchMenu";
|
|
@@ -76,6 +77,7 @@ export {
|
|
|
76
77
|
Redirector,
|
|
77
78
|
RichTextEditor,
|
|
78
79
|
RouteListener,
|
|
80
|
+
SafeLink,
|
|
79
81
|
ScrollToTop,
|
|
80
82
|
SearchInput,
|
|
81
83
|
SearchMenu,
|
package/src/routes.js
CHANGED
|
@@ -36,7 +36,7 @@ export const DOMAINS = "/domains";
|
|
|
36
36
|
export const DOMAINS_ACTIONS = "/domains/:action";
|
|
37
37
|
export const DOMAINS_NEW = "/domains/new";
|
|
38
38
|
export const DOMAINS_SEARCH = "/domains/search";
|
|
39
|
-
export const
|
|
39
|
+
export const DOMAIN_CONCEPTS = "/domains/:id/concepts";
|
|
40
40
|
export const DOMAIN_EDIT = "/domains/:id/edit";
|
|
41
41
|
export const DOMAIN_MEMBERS = "/domains/:id/members";
|
|
42
42
|
export const DOMAIN_MEMBERS_NEW = "/domains/:id/members/new";
|
|
@@ -217,7 +217,7 @@ const routes = {
|
|
|
217
217
|
DOMAINS_ACTIONS,
|
|
218
218
|
DOMAINS_NEW,
|
|
219
219
|
DOMAINS_SEARCH,
|
|
220
|
-
|
|
220
|
+
DOMAIN_CONCEPTS,
|
|
221
221
|
DOMAIN_EDIT,
|
|
222
222
|
DOMAIN_MEMBERS,
|
|
223
223
|
DOMAIN_MEMBERS_NEW,
|
|
@@ -2,12 +2,12 @@ import _ from "lodash/fp";
|
|
|
2
2
|
import {
|
|
3
3
|
createSelector,
|
|
4
4
|
createSelectorCreator,
|
|
5
|
-
defaultMemoize
|
|
5
|
+
defaultMemoize,
|
|
6
6
|
} from "reselect";
|
|
7
7
|
|
|
8
8
|
const defaultFilters = false;
|
|
9
9
|
|
|
10
|
-
export const makeActiveFiltersSelector = activeFiltersProp => {
|
|
10
|
+
export const makeActiveFiltersSelector = (activeFiltersProp) => {
|
|
11
11
|
const activeFiltersSelector = createSelector(
|
|
12
12
|
_.prop(activeFiltersProp),
|
|
13
13
|
(_state, props) => _.propOr(defaultFilters, "defaultFilters")(props),
|
|
@@ -5,7 +5,7 @@ describe("services: filters", () => {
|
|
|
5
5
|
describe("mergeFilters", () => {
|
|
6
6
|
const defaultFilters = {
|
|
7
7
|
status: ["draft", "pending", "rejected"],
|
|
8
|
-
current: [true]
|
|
8
|
+
current: [true],
|
|
9
9
|
};
|
|
10
10
|
const userFilters1 = { type: ["type1"] };
|
|
11
11
|
const userFilters2 = { status: ["draft", "rejected"] };
|
|
@@ -16,7 +16,7 @@ describe("services: filters", () => {
|
|
|
16
16
|
expect(merged).toEqual({
|
|
17
17
|
current: [true],
|
|
18
18
|
status: ["draft", "pending", "rejected"],
|
|
19
|
-
type: ["type1"]
|
|
19
|
+
type: ["type1"],
|
|
20
20
|
});
|
|
21
21
|
});
|
|
22
22
|
|
|
@@ -24,7 +24,7 @@ describe("services: filters", () => {
|
|
|
24
24
|
const merged = mergeFilters([defaultFilters, userFilters2]);
|
|
25
25
|
expect(merged).toEqual({
|
|
26
26
|
current: [true],
|
|
27
|
-
status: ["draft", "rejected"]
|
|
27
|
+
status: ["draft", "rejected"],
|
|
28
28
|
});
|
|
29
29
|
});
|
|
30
30
|
|
|
@@ -32,9 +32,17 @@ describe("services: filters", () => {
|
|
|
32
32
|
const merged = mergeFilters([defaultFilters, userFilters3]);
|
|
33
33
|
expect(merged).toEqual({
|
|
34
34
|
current: [true],
|
|
35
|
-
status: ["draft", "pending", "rejected"]
|
|
35
|
+
status: ["draft", "pending", "rejected"],
|
|
36
36
|
});
|
|
37
37
|
});
|
|
38
|
+
|
|
39
|
+
it("should use overrides if present for taxonomy key", () => {
|
|
40
|
+
const defaults = { taxonomy: [123] };
|
|
41
|
+
const overrides = { taxonomy: [456] };
|
|
42
|
+
const emptyOverrides = { taxonomy: [] };
|
|
43
|
+
expect(mergeFilters([defaults, overrides])).toEqual(overrides);
|
|
44
|
+
expect(mergeFilters([defaults, emptyOverrides])).toEqual(defaults);
|
|
45
|
+
});
|
|
38
46
|
});
|
|
39
47
|
|
|
40
48
|
describe("getSearchPayload", () => {
|
|
@@ -52,7 +60,7 @@ describe("services: filters", () => {
|
|
|
52
60
|
getSearchPayload(searchQuery, {
|
|
53
61
|
defaultFilters,
|
|
54
62
|
linkable,
|
|
55
|
-
pageSize: size
|
|
63
|
+
pageSize: size,
|
|
56
64
|
})
|
|
57
65
|
).toEqual({
|
|
58
66
|
filters: { ...filters, ...defaultFilters },
|
|
@@ -60,7 +68,7 @@ describe("services: filters", () => {
|
|
|
60
68
|
page,
|
|
61
69
|
query,
|
|
62
70
|
size,
|
|
63
|
-
sort
|
|
71
|
+
sort,
|
|
64
72
|
});
|
|
65
73
|
});
|
|
66
74
|
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { validUrl } from "../validation";
|
|
2
|
+
|
|
3
|
+
describe("validUrl", () => {
|
|
4
|
+
it("returns url if schema is http or https", () => {
|
|
5
|
+
["https://www.truedat.io", "http://www.truedat.io"].forEach((url) => {
|
|
6
|
+
expect(validUrl(url)).toBe(url);
|
|
7
|
+
});
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
["javascript:foo", "not_a_valid_url"].forEach((url) => {
|
|
11
|
+
it("returns false if url is potentially unsafe", () => {
|
|
12
|
+
expect(validUrl(url)).toBe(false);
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
});
|
package/src/services/filters.js
CHANGED
|
@@ -6,16 +6,16 @@ const intersectionOrDefault = (defaults, overrides) => {
|
|
|
6
6
|
return _.isEmpty(v) ? defaults : v;
|
|
7
7
|
};
|
|
8
8
|
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
const isNonEmptyArray = (a) => _.isArray(a) && !_.isEmpty(a);
|
|
10
|
+
|
|
11
|
+
const filterCustomizer = (defaults, overrides, key) =>
|
|
12
|
+
key === "taxonomy" && isNonEmptyArray(overrides)
|
|
13
|
+
? overrides
|
|
14
|
+
: isNonEmptyArray(defaults) && isNonEmptyArray(overrides)
|
|
14
15
|
? intersectionOrDefault(defaults, overrides)
|
|
15
16
|
: undefined;
|
|
16
17
|
|
|
17
|
-
export const mergeFilters = (
|
|
18
|
-
_.mergeAllWith(filterCustomizer)(filters);
|
|
18
|
+
export const mergeFilters = _.mergeAllWith(filterCustomizer);
|
|
19
19
|
|
|
20
20
|
export const getSearchPayload = (searchQuery, props) => {
|
|
21
21
|
const { defaultFilters = {}, linkable, pageSize: size = 20 } = props;
|