@stoker-platform/web-app 0.5.172 → 0.5.174
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 +4 -4
- package/src/List.tsx +9 -3
- package/src/Record.tsx +1 -0
- package/src/Tenant.tsx +1 -1
- package/src/utils/getFormattedFieldValue.tsx +1 -2
- package/src/utils/localFullTextSearch.ts +65 -14
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# @stoker-platform/web-app
|
|
2
2
|
|
|
3
|
+
## 0.5.174
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- feat: add exact match search as default search mode
|
|
8
|
+
- @stoker-platform/node-client@0.5.71
|
|
9
|
+
- @stoker-platform/utils@0.5.62
|
|
10
|
+
- @stoker-platform/web-client@0.5.73
|
|
11
|
+
|
|
12
|
+
## 0.5.173
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- feat: add props option to custom record pages
|
|
17
|
+
- @stoker-platform/node-client@0.5.70
|
|
18
|
+
- @stoker-platform/utils@0.5.61
|
|
19
|
+
- @stoker-platform/web-client@0.5.72
|
|
20
|
+
|
|
3
21
|
## 0.5.172
|
|
4
22
|
|
|
5
23
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stoker-platform/web-app",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.174",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
6
6
|
"scripts": {
|
|
@@ -51,9 +51,9 @@
|
|
|
51
51
|
"@radix-ui/react-tooltip": "^1.2.8",
|
|
52
52
|
"@react-google-maps/api": "^2.20.8",
|
|
53
53
|
"@sentry/react": "^10.56.0",
|
|
54
|
-
"@stoker-platform/node-client": "0.5.
|
|
55
|
-
"@stoker-platform/utils": "0.5.
|
|
56
|
-
"@stoker-platform/web-client": "0.5.
|
|
54
|
+
"@stoker-platform/node-client": "0.5.71",
|
|
55
|
+
"@stoker-platform/utils": "0.5.62",
|
|
56
|
+
"@stoker-platform/web-client": "0.5.73",
|
|
57
57
|
"@tanstack/react-table": "^8.21.3",
|
|
58
58
|
"@types/react": "18.3.13",
|
|
59
59
|
"@types/react-dom": "18.3.1",
|
package/src/List.tsx
CHANGED
|
@@ -693,6 +693,12 @@ export function List({
|
|
|
693
693
|
.filter((record): record is StokerRecord => record !== undefined)
|
|
694
694
|
}, [rowSelection, searchList])
|
|
695
695
|
|
|
696
|
+
const searchOptions = tryFunction(customization.admin?.searchOptions) || {
|
|
697
|
+
fuzzy: false,
|
|
698
|
+
prefix: false,
|
|
699
|
+
}
|
|
700
|
+
const exactPhrase = searchOptions.fuzzy === false && searchOptions.prefix === false
|
|
701
|
+
|
|
696
702
|
const table = useReactTable<StokerRecord>({
|
|
697
703
|
data: searchList,
|
|
698
704
|
columns,
|
|
@@ -700,7 +706,7 @@ export function List({
|
|
|
700
706
|
getCoreRowModel: getCoreRowModel(),
|
|
701
707
|
getPaginationRowModel: getPaginationRowModel(),
|
|
702
708
|
onSortingChange: (sortingUpdater) => {
|
|
703
|
-
if (isSearchRelevanceOrder) return
|
|
709
|
+
if (isSearchRelevanceOrder && !exactPhrase) return
|
|
704
710
|
if (typeof sortingUpdater === "function") {
|
|
705
711
|
const newSorting = sortingUpdater(sorting)
|
|
706
712
|
const field = getField(fields, newSorting[0].id)
|
|
@@ -740,9 +746,9 @@ export function List({
|
|
|
740
746
|
onRowSelectionChange: setRowSelection,
|
|
741
747
|
pageCount,
|
|
742
748
|
autoResetPageIndex: false,
|
|
743
|
-
enableSorting: !isSearchRelevanceOrder,
|
|
749
|
+
enableSorting: !isSearchRelevanceOrder || exactPhrase,
|
|
744
750
|
state: {
|
|
745
|
-
sorting: isSearchRelevanceOrder ? [] : sorting,
|
|
751
|
+
sorting: isSearchRelevanceOrder && !exactPhrase ? [] : sorting,
|
|
746
752
|
columnFilters,
|
|
747
753
|
rowSelection,
|
|
748
754
|
pagination: {
|
package/src/Record.tsx
CHANGED
|
@@ -311,6 +311,7 @@ export const Record = ({ collection }: { collection: CollectionSchema }) => {
|
|
|
311
311
|
}),
|
|
312
312
|
hooks: import.meta.glob("./hooks/*.{ts,tsx}", { eager: true }),
|
|
313
313
|
utils: import.meta.glob("./lib/*.{ts,tsx}", { eager: true }),
|
|
314
|
+
...page.props,
|
|
314
315
|
})}
|
|
315
316
|
</main>
|
|
316
317
|
}
|
package/src/Tenant.tsx
CHANGED
|
@@ -591,7 +591,7 @@ function Tenant() {
|
|
|
591
591
|
runViewTransition(() => navigate("/"))
|
|
592
592
|
}}
|
|
593
593
|
>
|
|
594
|
-
<img src={logo || defaultLogo} alt="Logo" className="h-8 mr-2" />
|
|
594
|
+
<img src={logo || defaultLogo} alt="Logo" className="h-8 mr-2 object-contain" />
|
|
595
595
|
</button>
|
|
596
596
|
{links}
|
|
597
597
|
</nav>
|
|
@@ -163,8 +163,7 @@ export const getFormattedFieldValue = (
|
|
|
163
163
|
variant="outline"
|
|
164
164
|
size="sm"
|
|
165
165
|
className={cn(
|
|
166
|
-
"w-
|
|
167
|
-
!card && "min-w-[100px]",
|
|
166
|
+
"w-fit min-w-[100px] md:min-w-[200px] max-w-full whitespace-normal break-words h-auto p-3 bg-transparent dark:bg-blue-500/50 text-blue-500 dark:text-primary dark:hover:bg-blue-500 dark:border-input",
|
|
168
167
|
)}
|
|
169
168
|
disabled={
|
|
170
169
|
!relationCollection ||
|
|
@@ -1,32 +1,83 @@
|
|
|
1
1
|
import { CollectionSchema, StokerRecord } from "@stoker-platform/types"
|
|
2
|
+
import { getCollectionConfigModule } from "@stoker-platform/web-client"
|
|
2
3
|
import MiniSearch, { Options } from "minisearch"
|
|
3
4
|
|
|
5
|
+
const flattenToSearchText = (value: unknown): string => {
|
|
6
|
+
if (value == null) return ""
|
|
7
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
8
|
+
return String(value)
|
|
9
|
+
}
|
|
10
|
+
if (Array.isArray(value)) {
|
|
11
|
+
return value.map(flattenToSearchText).filter(Boolean).join(" ")
|
|
12
|
+
}
|
|
13
|
+
if (typeof value === "object") {
|
|
14
|
+
return Object.values(value).map(flattenToSearchText).filter(Boolean).join(" ")
|
|
15
|
+
}
|
|
16
|
+
return ""
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const valueContainsPhrase = (value: unknown, phrase: string): boolean => {
|
|
20
|
+
if (value == null) return false
|
|
21
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
22
|
+
return String(value).toLowerCase().includes(phrase)
|
|
23
|
+
}
|
|
24
|
+
if (Array.isArray(value)) {
|
|
25
|
+
return value.some((item) => valueContainsPhrase(item, phrase))
|
|
26
|
+
}
|
|
27
|
+
if (typeof value === "object") {
|
|
28
|
+
return Object.values(value).some((item) => valueContainsPhrase(item, phrase))
|
|
29
|
+
}
|
|
30
|
+
return false
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const recordMatchesPhrase = (record: StokerRecord, fields: string[], phrase: string) =>
|
|
34
|
+
// eslint-disable-next-line security/detect-object-injection
|
|
35
|
+
fields.some((field) => valueContainsPhrase(record[field], phrase))
|
|
36
|
+
|
|
4
37
|
export const localFullTextSearch = (
|
|
5
38
|
collection: CollectionSchema,
|
|
6
39
|
query: string,
|
|
7
40
|
list: StokerRecord[],
|
|
8
41
|
filter?: (result: StokerRecord) => boolean,
|
|
9
|
-
tokenize?: boolean,
|
|
10
42
|
) => {
|
|
11
43
|
const { recordTitleField, fullTextSearch } = collection
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
fuzzy: 0.2,
|
|
18
|
-
prefix: true,
|
|
19
|
-
}),
|
|
20
|
-
},
|
|
44
|
+
const fields = fullTextSearch || [recordTitleField]
|
|
45
|
+
const customization = getCollectionConfigModule(collection.labels.collection)
|
|
46
|
+
const searchOptions = customization.admin?.searchOptions || {
|
|
47
|
+
fuzzy: false,
|
|
48
|
+
prefix: false,
|
|
21
49
|
}
|
|
50
|
+
|
|
22
51
|
if (filter) {
|
|
23
52
|
list = list.filter((record) => filter(record))
|
|
24
53
|
}
|
|
25
|
-
|
|
26
|
-
|
|
54
|
+
|
|
55
|
+
const phrase = query.trim().toLowerCase()
|
|
56
|
+
const exactPhrase = searchOptions.fuzzy === false && searchOptions.prefix === false
|
|
57
|
+
|
|
58
|
+
if (exactPhrase) {
|
|
59
|
+
if (!phrase) return []
|
|
60
|
+
return list
|
|
61
|
+
.filter((record) => recordMatchesPhrase(record, fields, phrase))
|
|
62
|
+
.map((record) => ({
|
|
63
|
+
id: record.id,
|
|
64
|
+
score: 1,
|
|
65
|
+
terms: [phrase],
|
|
66
|
+
queryTerms: [phrase],
|
|
67
|
+
match: { [phrase]: fields },
|
|
68
|
+
// eslint-disable-next-line security/detect-object-injection
|
|
69
|
+
...Object.fromEntries(fields.map((field) => [field, record[field]])),
|
|
70
|
+
}))
|
|
27
71
|
}
|
|
72
|
+
|
|
73
|
+
const miniSearchConfig: Options = {
|
|
74
|
+
fields,
|
|
75
|
+
storeFields: fields,
|
|
76
|
+
searchOptions,
|
|
77
|
+
stringifyField: (fieldValue) => flattenToSearchText(fieldValue),
|
|
78
|
+
}
|
|
79
|
+
|
|
28
80
|
const miniSearch = new MiniSearch(miniSearchConfig)
|
|
29
81
|
miniSearch.addAll(list)
|
|
30
|
-
|
|
31
|
-
return results
|
|
82
|
+
return miniSearch.search(query)
|
|
32
83
|
}
|