@rpcbase/client 0.215.0 → 0.217.0

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.
@@ -0,0 +1,48 @@
1
+ import {
2
+ QueryBuilderBootstrap,
3
+ bootstrapControlClassnames,
4
+ bootstrapControlElements,
5
+ } from "@react-querybuilder/bootstrap"
6
+ import {QueryBuilder as ReactQueryBuilder} from "react-querybuilder"
7
+ import {QueryBuilderDnD as ReactQueryBuilderDnD} from "@react-querybuilder/dnd"
8
+ import * as ReactDnD from "react-dnd"
9
+ import * as ReactDnDHtml5Backend from "react-dnd-html5-backend"
10
+
11
+ // import FieldSelector from "./FieldSelector"
12
+ // import OperatorSelector from "./OperatorSelector"
13
+ // import ValueEditor from "./ValueEditor"
14
+
15
+ import "./query-builder.scss"
16
+
17
+
18
+ const controlClassnames = {...bootstrapControlClassnames}
19
+ controlClassnames.queryBuilder += " queryBuilder-branches"
20
+
21
+ const QueryBuilder = ({controlElements = {}, ...props}) => {
22
+ return (
23
+ <ReactQueryBuilderDnD dnd={{...ReactDnD, ...ReactDnDHtml5Backend}}>
24
+ <QueryBuilderBootstrap>
25
+ <ReactQueryBuilder
26
+ addRuleToNewGroups
27
+ // TODO: why is DnD not working??
28
+ // https://react-querybuilder.js.org/docs/components/querybuilder#enabledraganddrop
29
+ enableDragAndDrop
30
+ showCombinatorsBetweenRules={false}
31
+ showNotToggle
32
+ showCloneButtons
33
+ controlClassnames={controlClassnames}
34
+ controlElements={{
35
+ ...bootstrapControlElements,
36
+ ...controlElements,
37
+ // fieldSelector: FieldSelector,
38
+ // operatorSelector: OperatorSelector,
39
+ // valueEditor: ValueEditor,
40
+ }}
41
+ {...props}
42
+ />
43
+ </QueryBuilderBootstrap>
44
+ </ReactQueryBuilderDnD>
45
+ )
46
+ }
47
+
48
+ export default QueryBuilder
@@ -0,0 +1,5 @@
1
+
2
+
3
+ export const TargetSelector = () => {
4
+
5
+ }
@@ -0,0 +1,9 @@
1
+ @import "helpers";
2
+
3
+ .queryBuilder {
4
+ .dropdown-item.active {
5
+ .text-secondary {
6
+ color: $gray-200 !important;
7
+ }
8
+ }
9
+ }
@@ -0,0 +1,70 @@
1
+ import {useState} from "react"
2
+
3
+
4
+ const TARGET_TYPES = [
5
+ {
6
+ name: "Collection:",
7
+ key: "collection",
8
+ description: "Applies to all documents within the collection.",
9
+ },
10
+ {
11
+ name: "Document:",
12
+ key: "document",
13
+ description: "Applies only to certain specific documents.",
14
+ },
15
+ ]
16
+
17
+ export const PolicyEditor = () => {
18
+ const [targetType, setTargetType] = useState("collection")
19
+
20
+ const onChangeTargetType = (event) => {
21
+ setTargetType(event.target.value)
22
+ }
23
+
24
+ return (
25
+ <div>
26
+ <h6>Policy Editor</h6>
27
+ <br />
28
+ <div className="d-flex flex-row">
29
+ <div className="me-2">
30
+ <h6>Target Type:</h6>
31
+ <div style={{maxWidth: 300}}>
32
+ {TARGET_TYPES.map((type) => (
33
+ <div key={type.key} className="d-flex flex-row mb-1">
34
+ <input
35
+ className="form-check-input"
36
+ type="radio"
37
+ name="targetTypeOptions"
38
+ id={type.key}
39
+ value={type.key}
40
+ checked={targetType === type.key}
41
+ onChange={onChangeTargetType}
42
+ />
43
+ <label
44
+ className="form-check-label cursor-pointer ps-2"
45
+ htmlFor={type.key}
46
+ >
47
+ <b>{type.name}</b> {type.description}
48
+ </label>
49
+ </div>
50
+ ))}
51
+ </div>
52
+ </div>
53
+
54
+ <div>
55
+ <h6>Targets:</h6>
56
+ <div>
57
+ <input type="text" />
58
+ </div>
59
+ </div>
60
+ </div>
61
+ grant / deny
62
+ <br />
63
+ Scope: document, field
64
+ <br />
65
+ operation: create read write delete
66
+ <br />
67
+ to attributes / conditions add support for expiry date
68
+ </div>
69
+ )
70
+ }
@@ -1,2 +1,3 @@
1
1
  export * from "./ACLModal"
2
2
  export * from "./ACLForm"
3
+ export * from "./PolicyEditor"
package/form/Input.tsx CHANGED
@@ -1,5 +1,6 @@
1
1
  import React from "react"
2
2
  // import {NestedKeyOf} from "@rpcbase/client/types"
3
+ import _snakeCase from "lodash/snakeCase"
3
4
  import _get from "lodash/get"
4
5
  import {useFormContext} from "./hook-form"
5
6
 
@@ -33,7 +34,7 @@ export const Input = <T,>({
33
34
  }: InputProps<T> & React.InputHTMLAttributes<HTMLInputElement>) => {
34
35
  const form = useFormContext()
35
36
 
36
- const id = idProp || `input-${field}`
37
+ const id = idProp || _snakeCase(`input_${field}`)
37
38
 
38
39
  const error = _get(form.formState.errors, field)
39
40
 
package/hashState.js CHANGED
@@ -108,7 +108,7 @@ export const HashStateProvider = ({children}) => {
108
108
 
109
109
  useEffect(() => {
110
110
  apply_hash_state(hashState)
111
- window.__PRIVATE_HASH_STATE_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = hashState
111
+ window.__PRIVATE_HASH_STATE_DO_NOT_USE__ = hashState
112
112
  }, [hashState])
113
113
 
114
114
  const serializeHashState = useCallback((payload) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rpcbase/client",
3
- "version": "0.215.0",
3
+ "version": "0.217.0",
4
4
  "scripts": {
5
5
  "build": "../../node_modules/.bin/wireit",
6
6
  "test": "../../node_modules/.bin/wireit"
@@ -18,6 +18,10 @@ export const SearchHistory = ({setSearchInput}) => {
18
18
  setSearchInput(item.query)
19
19
  }
20
20
 
21
+ if (!searchHistory.length > 0) {
22
+ return null
23
+ }
24
+
21
25
  return (
22
26
  <>
23
27
  <div className="search-history-header ps-3 pb-2 mt-2 fw-bold border-bottom">
@@ -6,13 +6,14 @@ import {getUid} from "@rpcbase/client/auth/getUid"
6
6
 
7
7
  import stripDiacritics from "react-bootstrap-typeahead/cjs/utils/stripDiacritics"
8
8
 
9
+
9
10
  const HISTORY_MAX_RESULTS = 10
10
11
 
11
12
  export const useSearchHistory = () => {
12
13
  const user_id = getUid()
13
14
 
14
15
  const [history, setHistory] = useState([])
15
- const historyQuery = useQuery("PersistedEvent", {_owners: {$in: [user_id]}, type: "search_query"})
16
+ const historyQuery = useQuery("SearchHistory", {_owners: {$in: [user_id]}})
16
17
 
17
18
  // Initial load history
18
19
  useEffect(() => {
@@ -0,0 +1,91 @@
1
+ import _pick from "lodash/pick"
2
+ import _snakeCase from "lodash/snakeCase"
3
+
4
+ import {useHashState} from "../../../hashState"
5
+
6
+ import {useActiveListItemIndex} from "../../helpers/useActiveListItemIndex"
7
+ import {useScrollSelectorIntoView} from "../../helpers/useScrollSelectorIntoView"
8
+
9
+ // import save_search from "rpc!server/search-indexer/save_search"
10
+
11
+ export const SearchResults = ({
12
+ id = "search-results",
13
+ query,
14
+ results,
15
+ searchContext,
16
+ onCompleteSearch,
17
+ children,
18
+ renderResultItem,
19
+ }) => {
20
+ const {serializeHashState} = useHashState()
21
+
22
+ const getItemId = (i) => `${id}-result-${i}`
23
+
24
+ const getLink = (item) =>{
25
+ return `#${_snakeCase(item)}`
26
+ }
27
+
28
+ const onSelectItem = (selectedIndex) => {
29
+ // console.log("ON SELEC", selectedIndex)
30
+ serializeHashState({link: getLink(results[selectedIndex])})
31
+ // if (window)
32
+
33
+ onCompleteSearch()
34
+ // go to selected item
35
+ // const targetItem = items[selectedIndex]
36
+ // applyTargetItem(targetItem)
37
+ }
38
+
39
+ const {activeItemIndex} = useActiveListItemIndex({items: results, onSelectItem})
40
+
41
+ useScrollSelectorIntoView(`#${getItemId(activeItemIndex)}`)
42
+
43
+ return (
44
+ <div>
45
+ <div className="list-group list-group-flush">
46
+ {results.map((item: any, index: number) => {
47
+
48
+ const key = getItemId(index)
49
+
50
+ const onClick = () => {
51
+ console.log("CLICK item", item)
52
+ onSelectItem(index)
53
+ }
54
+
55
+ return (
56
+ <a
57
+ id={key}
58
+ key={key}
59
+ className={cx(
60
+ "list-group-item list-group-item-action d-flex justify-content-between align-items-start",
61
+ {active: activeItemIndex === index},
62
+ )}
63
+ href={getLink(item)}
64
+ onClick={onClick}
65
+ >
66
+ <div className="ms-2 me-auto">
67
+ {renderResultItem(item, index)}
68
+ {/* {Object.keys(item.highlight).map((highlightKey, j) => (
69
+ <div
70
+ key={`highlight-${j}`}
71
+ className="item-highlight"
72
+ dangerouslySetInnerHTML={{__html: item.highlight[highlightKey][0]}}
73
+ />
74
+ ))} */}
75
+ <small className="fst-italic">{"displaySub"}</small>
76
+ </div>
77
+ {/* <span className="badge text-bg-light rounded-pill">
78
+ {item.fuzzy_score?.toFixed(2)}
79
+ </span> */}
80
+ {/* <span className="badge bg-primary rounded-pill">{item._score.toFixed(2)}</span> */}
81
+ </a>
82
+ )
83
+
84
+ })}
85
+
86
+ {/* children can be components like search history, etc */}
87
+ {children}
88
+ </div>
89
+ </div>
90
+ )
91
+ }
@@ -1,5 +1,5 @@
1
1
  import assert from "assert"
2
- import {useEffect, useState, useContext, useRef, useCallback} from "react"
2
+ import {ReactNode, useEffect, useState, useRef, useCallback} from "react"
3
3
  import {Typeahead} from "react-bootstrap-typeahead"
4
4
  import Overlay from "react-bootstrap/Overlay"
5
5
  import _throttle from "lodash/throttle"
@@ -27,19 +27,22 @@ const MIN_SEARCH_LENGTH = 2
27
27
  const PLACEHOLDER = "Search..."
28
28
 
29
29
  export const Search = ({
30
+ id = "search-input",
30
31
  minLength = MIN_SEARCH_LENGTH,
31
32
  onSearch,
33
+ renderResultItem,
32
34
  }: {
35
+ id?: string,
33
36
  minLength?: number
34
- onSearch: (queryStr: string) => Array<any>
37
+ onSearch: (queryStr: string) => Promise<Array<any>>
38
+ renderResultItem: (item: any, index: number) => ReactNode
35
39
  }) => {
36
40
  // const envContext = useEnvContext()
37
41
  // const itemContext = useContext(ItemContext)
38
-
39
42
  const typeaheadRef = useRef()
40
43
  const wrapperRef = useRef()
41
44
 
42
- const [results, setResults] = useState([])
45
+ const [results, setResults] = useState<Array<any>>([])
43
46
  const [isLoading, setIsLoading] = useState(false)
44
47
  const [isFocused, setIsFocused] = useState(false)
45
48
  const [showResults, setShowResults] = useState(false)
@@ -116,17 +119,9 @@ export const Search = ({
116
119
  setIsLoading(true)
117
120
  }, 100)
118
121
 
119
- // const res = await search_anything({
120
- // group_id: envContext.groupId,
121
- // str,
122
- // })
123
-
124
- const results = await onSearch(queryStr)
125
-
126
- // assert(res.status === "ok")
127
- // console.log("search res", res)
122
+ const searchResults = await onSearch(queryStr)
128
123
 
129
- setResults(results)
124
+ setResults(searchResults)
130
125
  clearTimeout(tm)
131
126
  setIsLoading(false)
132
127
  },
@@ -216,16 +211,16 @@ export const Search = ({
216
211
  return (
217
212
  <>
218
213
  <div
219
- id="search-anything-wrapper"
214
+ id={id}
220
215
  ref={wrapperRef}
221
216
  className="d-flex mx-auto w-100"
222
217
  // style={{height: 32, position: "relative", maxWidth: 804}}
223
218
  style={{height: 32, position: "relative"}}
224
219
  >
225
220
  <Typeahead
226
- id="search-anything"
221
+ id={`${id}-typeahead`}
227
222
  ref={typeaheadRef}
228
- className={cx("search-anything-typeahead w-100", {"is-focused": isFocused})}
223
+ className={cx("search-typeahead w-100", {"is-focused": isFocused})}
229
224
  placeholder={PLACEHOLDER}
230
225
  inputProps={{
231
226
  placeholder: PLACEHOLDER,
@@ -275,7 +270,7 @@ export const Search = ({
275
270
  >
276
271
  {({style, ...overlayProps}) => (
277
272
  <div
278
- id="search-anything-overlay"
273
+ id={`${id}-overlay`}
279
274
  {...overlayProps}
280
275
  className="shadow-lg"
281
276
  style={{
@@ -286,10 +281,12 @@ export const Search = ({
286
281
  }}
287
282
  >
288
283
  <SearchResults
284
+ id={id}
289
285
  query={currentInputValue.trim()}
290
286
  results={results}
291
287
  onCompleteSearch={onCompleteSearch}
292
288
  searchContext={selected[0]}
289
+ renderResultItem={renderResultItem}
293
290
  >
294
291
  <SearchHistory setSearchInput={setSearchInput} />
295
292
  </SearchResults>
@@ -1,5 +1,6 @@
1
1
  import "./select-pills.scss"
2
2
 
3
+
3
4
  type Props = {
4
5
  direction?: "row" | "col";
5
6
  size?: "md" | "sm";
@@ -16,11 +17,11 @@ export const SelectPills= ({
16
17
  activeKey,
17
18
  }: Props) => {
18
19
  const handleSelectType = (type: string) => () => {
19
- onChange(type);
20
- };
20
+ onChange(type)
21
+ }
21
22
 
22
23
  const renderRow = () => {
23
- const iconSize = size === "sm" ? "24px" : "32px";
24
+ const iconSize = size === "sm" ? "24px" : "32px"
24
25
 
25
26
  return (
26
27
  <div className={`select-pills mb-3 card-group direction-row`}>
@@ -48,11 +49,11 @@ export const SelectPills= ({
48
49
  </div>
49
50
  ))}
50
51
  </div>
51
- );
52
- };
52
+ )
53
+ }
53
54
 
54
55
  const renderCol = () => {
55
- const iconSize = size === "sm" ? "24px" : "32px";
56
+ const iconSize = size === "sm" ? "24px" : "32px"
56
57
 
57
58
  return (
58
59
  <div className="select-pills">
@@ -64,11 +65,14 @@ export const SelectPills= ({
64
65
  onClick={handleSelectType(item.key)}
65
66
  >
66
67
  <div className="card-body d-flex flex-row px-2 py-3">
67
- <img
68
- className="d-flex align-self-center me-2"
69
- style={{ width: iconSize, height: iconSize }}
70
- src={item.icon}
71
- />
68
+ {item.icon && (
69
+ <img
70
+ className="d-flex align-self-center me-2"
71
+ style={{width: iconSize, height: iconSize}}
72
+ src={item.icon}
73
+ />
74
+ )}
75
+
72
76
  {size === "md" && (
73
77
  <div className="flex-column">
74
78
  <h6 className="card-title mb-1 fw-bold">{item.name}</h6>
@@ -77,7 +81,7 @@ export const SelectPills= ({
77
81
  )}
78
82
  {size === "sm" && (
79
83
  <div className="">
80
- <h6 className="card-title my-0 d-inline-block me-2 fw-bold">{item.name}</h6>
84
+ <h6 className="card-title my-0 d-inline-block me-1 fw-bold">{item.name}</h6>
81
85
  <span className="card-text">{item.description}</span>
82
86
  </div>
83
87
  )}
@@ -85,8 +89,8 @@ export const SelectPills= ({
85
89
  </div>
86
90
  ))}
87
91
  </div>
88
- );
89
- };
92
+ )
93
+ }
90
94
 
91
- return direction === "row" ? renderRow() : renderCol();
92
- };
95
+ return direction === "row" ? renderRow() : renderCol()
96
+ }
@@ -1,9 +1,12 @@
1
1
  import {useState, useEffect} from "react"
2
2
 
3
+
3
4
  export const useActiveListItemIndex = ({items, activeItem, onSelectItem}) => {
5
+
4
6
  const [activeItemIndex, setActiveItemIndex] = useState(() => {
5
7
  let initialIndex = items.findIndex((item) => item.obj?._id === activeItem?._id)
6
- if (!initialIndex) initialIndex = 0
8
+ if (initialIndex < 0) initialIndex = 0
9
+ console.log("INITIAL INDEX", initialIndex)
7
10
  return initialIndex
8
11
  })
9
12
 
@@ -1,6 +0,0 @@
1
-
2
- const getActions = (pathname) => {
3
- return []
4
- }
5
-
6
- export default getActions
@@ -1,188 +0,0 @@
1
- import assert from "assert"
2
- import _pick from "lodash/pick"
3
- import fuzzysort from "fuzzysort"
4
-
5
- import page from "../../../page"
6
- // import {withHashState} from "@rpcbase/client/hashState"
7
-
8
- // import COLLECTIONS from "config/collections"
9
- // import {useEnvContext} from "helpers/EnvContext"
10
- import {useActiveListItemIndex} from "../../helpers/useActiveListItemIndex"
11
- import {useScrollSelectorIntoView} from "../../helpers/useScrollSelectorIntoView"
12
-
13
- import useCombinedResultsActions from "./useCombinedResultsActions"
14
-
15
- // import save_search from "rpc!server/search-indexer/save_search"
16
- const save_search = async() => null
17
-
18
-
19
- const resolveTargetUrl = (encodeHashLink, item) => {
20
- let targetUrl
21
-
22
- // Message
23
- if (item._source.type === "message") {
24
- const pageUrl = "/channel"
25
- const channelId = item._source.channel_id
26
- const convId = item._source.conversation_id
27
- targetUrl = `${pageUrl}/${channelId}#${encodeHashLink({
28
- activeConversationId: convId, // conversation
29
- scrollToMessageId: item._id, // highlighted message
30
- })}`
31
- }
32
- // Other items
33
- else {
34
- const pageUrl = COLLECTIONS[item.meta.collection_name].base_url
35
- targetUrl = `${pageUrl}/${item.meta._id}#${encodeHashLink({
36
- highlightId: item._id,
37
- })}`
38
- }
39
-
40
- return targetUrl
41
- }
42
-
43
- const getKey = (i) => `search-anything-item-${i}`
44
-
45
- export const SearchResults = ({
46
- query,
47
- results,
48
- searchContext,
49
- onCompleteSearch,
50
- children,
51
- }) => {
52
- // const envContext = useEnvContext()
53
-
54
- const items = useCombinedResultsActions({query, results})
55
-
56
- // runs when a search result is selected by pressing enter
57
- const applyTargetItem = async(item) => {
58
- // user pressed enter with no results / nothing selected
59
- if (!item) {
60
- console.log("user pressed enter but there are no results")
61
- return
62
- }
63
-
64
- if (item.type === "action") {
65
- if (item.hashState) {
66
- serializeHashState(item.hashState)
67
- } else {
68
- console.error("unknown action", item)
69
- throw new Error("unknown action")
70
- }
71
- } else {
72
- const targetUrl = resolveTargetUrl(encodeHashLink, item)
73
-
74
- const res = await save_search({
75
- group_id: envContext.groupId,
76
- data: {
77
- query,
78
- search_context: _pick(searchContext, ["id", "col"]),
79
- result_url: targetUrl,
80
- },
81
- })
82
- assert(res.status === "ok")
83
-
84
- page(targetUrl)
85
- }
86
- }
87
-
88
- const onSelectItem = (selectedIndex) => {
89
- onCompleteSearch()
90
- // go to selected item
91
- const targetItem = items[selectedIndex]
92
- applyTargetItem(targetItem)
93
- }
94
-
95
- const {activeItemIndex} = useActiveListItemIndex({items, onSelectItem})
96
-
97
- useScrollSelectorIntoView(`#${getKey(activeItemIndex)}`)
98
-
99
- return (
100
- <div>
101
- <div className="list-group list-group-flush">
102
- {items.map((item, i) => {
103
- const key = getKey(i)
104
- // Render action
105
- if (item.type === "action") {
106
- let highlighted = fuzzysort.highlight(fuzzysort.single(query, item.key), "<b>", "</b>")
107
-
108
- if (!highlighted) highlighted = item.key
109
-
110
- const onClick = () => {
111
- onSelectItem(i)
112
- }
113
-
114
- return (
115
- <a
116
- id={key}
117
- key={key}
118
- className={cx(
119
- "list-group-item list-group-item-action d-flex justify-content-between align-items-start",
120
- {active: activeItemIndex === i},
121
- )}
122
- style={{cursor: "pointer"}}
123
- onClick={onClick}
124
- >
125
- <div className="ms-2 me-auto">
126
- <img
127
- style={{width: 20, height: 20, display: "inline"}}
128
- className="d-inline-flex me-2"
129
- src={`/static/icons/${item.icon}.svg`}
130
- />
131
- <div
132
- className="item-highlight"
133
- style={{display: "inline"}}
134
- dangerouslySetInnerHTML={{__html: highlighted}}
135
- />
136
- </div>
137
- <span className="badge text-bg-light rounded-pill">
138
- {item.fuzzy_score?.toFixed(2)}
139
- </span>
140
- </a>
141
- )
142
- }
143
- // Render search result
144
- else {
145
- const onClick = () => {
146
- onCompleteSearch()
147
- }
148
-
149
- const targetUrl = resolveTargetUrl(encodeHashLink, item)
150
- // TODO: when message, displaySub should be the name of the recipient / sender
151
- const displaySub = item._source.type === "message" ? "message" : item.meta.name
152
-
153
- return (
154
- <a
155
- id={key}
156
- key={key}
157
- className={cx(
158
- "list-group-item list-group-item-action d-flex justify-content-between align-items-start",
159
- {active: activeItemIndex === i},
160
- )}
161
- href={targetUrl}
162
- onClick={onClick}
163
- >
164
- <div className="ms-2 me-auto">
165
- {Object.keys(item.highlight).map((highlightKey, j) => (
166
- <div
167
- key={`highlight-${j}`}
168
- className="item-highlight"
169
- dangerouslySetInnerHTML={{__html: item.highlight[highlightKey][0]}}
170
- />
171
- ))}
172
- <small className="fst-italic">{displaySub}</small>
173
- </div>
174
- <span className="badge text-bg-light rounded-pill">
175
- {item.fuzzy_score?.toFixed(2)}
176
- </span>
177
- <span className="badge bg-primary rounded-pill">{item._score.toFixed(2)}</span>
178
- </a>
179
- )
180
- }
181
- })}
182
-
183
- {/* children can be components like search history, etc */}
184
- {children}
185
- </div>
186
- </div>
187
- )
188
- }
@@ -1,59 +0,0 @@
1
- /* @flow */
2
- import {useMemo} from "react"
3
- import useLocation from "react-use/lib/useLocation"
4
- import _isNil from "lodash/isNil"
5
- import fuzzysort from "fuzzysort"
6
-
7
- import getActions from "./getActions"
8
-
9
- // uses fuzzysort to match and sort results https://github.com/farzher/fuzzysort
10
- const getResultsWithActions = (query, results, actions) => {
11
- // we "fuzzy match sort" all actions and results (with both our fuzzy + elastic key)
12
- const rawItems = [...actions, ...results].map((item) => {
13
- let fuzzy_score
14
-
15
- // action item
16
- if (item.key) {
17
- fuzzy_score = fuzzysort.single(query, item.key)?.score
18
- }
19
- // search result
20
- else {
21
- // for each highlight compute score, take the highest one
22
- const scores = Object
23
- // TODO: why do some elastic search results not have a highlight?
24
- .keys(item?.highlight || {})
25
- .map((key) => fuzzysort.single(query, item.highlight[key][0])?.score)
26
- .filter((s) => !_isNil(s))
27
- .sort((a, b) => b - a)
28
-
29
- fuzzy_score = scores[0]
30
- }
31
-
32
- return {
33
- ...item,
34
- fuzzy_score,
35
- }
36
- })
37
-
38
- const sortedItems = rawItems
39
- .filter((item) => !_isNil(item.fuzzy_score))
40
- .sort((a, b) => b.fuzzy_score - a.fuzzy_score)
41
-
42
- return sortedItems
43
- }
44
-
45
- const useCombinedResultsActions = ({query, results}) => {
46
- const location = useLocation()
47
-
48
- const items = useMemo(() => {
49
- const actions = getActions(location.pathname)
50
-
51
- const newItems = getResultsWithActions(query, results, actions)
52
-
53
- return newItems
54
- }, [results, location.pathname])
55
-
56
- return items
57
- }
58
-
59
- export default useCombinedResultsActions