@adminforth/universal-search 1.0.2 → 1.1.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.
- package/build.log +2 -2
- package/custom/UniversalSearchInput.vue +2 -54
- package/dist/custom/UniversalSearchInput.vue +2 -54
- package/dist/index.js +85 -0
- package/index.ts +76 -1
- package/package.json +1 -1
- package/types.ts +1 -0
package/build.log
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<input
|
|
5
5
|
v-model="localValue"
|
|
6
6
|
type="text"
|
|
7
|
-
placeholder="
|
|
7
|
+
:placeholder="props.meta?.placeholder ?? ''"
|
|
8
8
|
class="border rounded px-2 py-1 text-sm w-64 dark:bg-gray-800 dark:border-gray-600"
|
|
9
9
|
@keyup.enter="applyImmediate"
|
|
10
10
|
/>
|
|
@@ -26,62 +26,10 @@ const adminforth: any = (window as any).adminforth || {};
|
|
|
26
26
|
const props = defineProps<{ meta?: any; resource?: any; adminUser?: any }>();
|
|
27
27
|
const localValue = ref('');
|
|
28
28
|
let debounceTimer: any = null;
|
|
29
|
-
const filtersStore = adminforth.filtersStore || { filters: [], setFilters: () => {} };
|
|
30
|
-
|
|
31
|
-
function buildFilters(term: string) {
|
|
32
|
-
const cols = props.meta?.columns as Array<any> | undefined;
|
|
33
|
-
if (!cols || !cols.length) return [];
|
|
34
|
-
const result: any[] = [];
|
|
35
|
-
|
|
36
|
-
cols.forEach(col => {
|
|
37
|
-
const searchBy = (col.searchBy === 'labelOnly' ? 'keyOnly' : col.searchBy) || 'valueOnly';
|
|
38
|
-
const addFilter = (field: string) => {
|
|
39
|
-
let operator: string;
|
|
40
|
-
let value: string;
|
|
41
|
-
if (col.exact) {
|
|
42
|
-
if (col.caseSensitive) {
|
|
43
|
-
operator = 'like';
|
|
44
|
-
value = term;
|
|
45
|
-
} else {
|
|
46
|
-
operator = 'eq';
|
|
47
|
-
value = term;
|
|
48
|
-
}
|
|
49
|
-
} else {
|
|
50
|
-
operator = col.caseSensitive ? 'like' : 'ilike';
|
|
51
|
-
value = `${term}`;
|
|
52
|
-
}
|
|
53
|
-
result.push({ field, operator, value });
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
if (searchBy === 'valueOnly') {
|
|
57
|
-
addFilter(col.name);
|
|
58
|
-
} else if (searchBy === 'keyOnly') {
|
|
59
|
-
addFilter(`${col.name}__key`);
|
|
60
|
-
} else if (searchBy === 'both') {
|
|
61
|
-
addFilter(col.name);
|
|
62
|
-
addFilter(`${col.name}__key`);
|
|
63
|
-
}
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
return result;
|
|
67
|
-
}
|
|
68
29
|
|
|
69
30
|
function applyInternal() {
|
|
70
31
|
const term = localValue.value.trim();
|
|
71
|
-
|
|
72
|
-
if (!term) {
|
|
73
|
-
filtersStore.setFilters(remaining);
|
|
74
|
-
adminforth.list.refresh && adminforth.list.refresh();
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
const sub = buildFilters(term);
|
|
78
|
-
if (!sub || !sub.length) {
|
|
79
|
-
filtersStore.setFilters(remaining);
|
|
80
|
-
adminforth.list.refresh && adminforth.list.refresh();
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
const orGroup = { operator: 'or', subFilters: sub, _universal: true };
|
|
84
|
-
filtersStore.setFilters([...remaining, orGroup]);
|
|
32
|
+
adminforth.__universalSearchTerm = term || undefined;
|
|
85
33
|
adminforth.list.refresh && adminforth.list.refresh();
|
|
86
34
|
}
|
|
87
35
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<input
|
|
5
5
|
v-model="localValue"
|
|
6
6
|
type="text"
|
|
7
|
-
placeholder="
|
|
7
|
+
:placeholder="props.meta?.placeholder ?? ''"
|
|
8
8
|
class="border rounded px-2 py-1 text-sm w-64 dark:bg-gray-800 dark:border-gray-600"
|
|
9
9
|
@keyup.enter="applyImmediate"
|
|
10
10
|
/>
|
|
@@ -26,62 +26,10 @@ const adminforth: any = (window as any).adminforth || {};
|
|
|
26
26
|
const props = defineProps<{ meta?: any; resource?: any; adminUser?: any }>();
|
|
27
27
|
const localValue = ref('');
|
|
28
28
|
let debounceTimer: any = null;
|
|
29
|
-
const filtersStore = adminforth.filtersStore || { filters: [], setFilters: () => {} };
|
|
30
|
-
|
|
31
|
-
function buildFilters(term: string) {
|
|
32
|
-
const cols = props.meta?.columns as Array<any> | undefined;
|
|
33
|
-
if (!cols || !cols.length) return [];
|
|
34
|
-
const result: any[] = [];
|
|
35
|
-
|
|
36
|
-
cols.forEach(col => {
|
|
37
|
-
const searchBy = (col.searchBy === 'labelOnly' ? 'keyOnly' : col.searchBy) || 'valueOnly';
|
|
38
|
-
const addFilter = (field: string) => {
|
|
39
|
-
let operator: string;
|
|
40
|
-
let value: string;
|
|
41
|
-
if (col.exact) {
|
|
42
|
-
if (col.caseSensitive) {
|
|
43
|
-
operator = 'like';
|
|
44
|
-
value = term;
|
|
45
|
-
} else {
|
|
46
|
-
operator = 'eq';
|
|
47
|
-
value = term;
|
|
48
|
-
}
|
|
49
|
-
} else {
|
|
50
|
-
operator = col.caseSensitive ? 'like' : 'ilike';
|
|
51
|
-
value = `${term}`;
|
|
52
|
-
}
|
|
53
|
-
result.push({ field, operator, value });
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
if (searchBy === 'valueOnly') {
|
|
57
|
-
addFilter(col.name);
|
|
58
|
-
} else if (searchBy === 'keyOnly') {
|
|
59
|
-
addFilter(`${col.name}__key`);
|
|
60
|
-
} else if (searchBy === 'both') {
|
|
61
|
-
addFilter(col.name);
|
|
62
|
-
addFilter(`${col.name}__key`);
|
|
63
|
-
}
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
return result;
|
|
67
|
-
}
|
|
68
29
|
|
|
69
30
|
function applyInternal() {
|
|
70
31
|
const term = localValue.value.trim();
|
|
71
|
-
|
|
72
|
-
if (!term) {
|
|
73
|
-
filtersStore.setFilters(remaining);
|
|
74
|
-
adminforth.list.refresh && adminforth.list.refresh();
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
const sub = buildFilters(term);
|
|
78
|
-
if (!sub || !sub.length) {
|
|
79
|
-
filtersStore.setFilters(remaining);
|
|
80
|
-
adminforth.list.refresh && adminforth.list.refresh();
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
const orGroup = { operator: 'or', subFilters: sub, _universal: true };
|
|
84
|
-
filtersStore.setFilters([...remaining, orGroup]);
|
|
32
|
+
adminforth.__universalSearchTerm = term || undefined;
|
|
85
33
|
adminforth.list.refresh && adminforth.list.refresh();
|
|
86
34
|
}
|
|
87
35
|
|
package/dist/index.js
CHANGED
|
@@ -40,6 +40,11 @@ export default class UniversalSearchPlugin extends AdminForthPlugin {
|
|
|
40
40
|
console.warn(`[UniversalSearchPlugin] Warning: column "${col.name}" has both exact=true and caseSensitive=true. Exact case-sensitive match implemented via LIKE without wildcards.`);
|
|
41
41
|
}
|
|
42
42
|
});
|
|
43
|
+
const virtualFieldName = '_universal_search';
|
|
44
|
+
const ephemeral = true;
|
|
45
|
+
if (!Array.isArray(this.resourceConfig.columns))
|
|
46
|
+
this.resourceConfig.columns = [];
|
|
47
|
+
const exists = this.resourceConfig.columns.some(c => c.name === virtualFieldName);
|
|
43
48
|
const injection = {
|
|
44
49
|
file: this.componentPath('UniversalSearchInput.vue'),
|
|
45
50
|
meta: {
|
|
@@ -55,6 +60,7 @@ export default class UniversalSearchPlugin extends AdminForthPlugin {
|
|
|
55
60
|
});
|
|
56
61
|
}),
|
|
57
62
|
debounceMs: (_a = this.options.debounceMs) !== null && _a !== void 0 ? _a : 500,
|
|
63
|
+
placeholder: this.options.placeholder || '',
|
|
58
64
|
}
|
|
59
65
|
};
|
|
60
66
|
const listInjections = this.resourceConfig.options.pageInjections.list;
|
|
@@ -65,6 +71,85 @@ export default class UniversalSearchPlugin extends AdminForthPlugin {
|
|
|
65
71
|
current.push(injection);
|
|
66
72
|
else
|
|
67
73
|
listInjections.beforeActionButtons = [current, injection];
|
|
74
|
+
if (!this.resourceConfig.hooks)
|
|
75
|
+
this.resourceConfig.hooks = {};
|
|
76
|
+
if (!this.resourceConfig.hooks.list)
|
|
77
|
+
this.resourceConfig.hooks.list = {};
|
|
78
|
+
const originalBefore = this.resourceConfig.hooks.list.beforeDatasourceRequest;
|
|
79
|
+
const transformFilters = (filters) => {
|
|
80
|
+
return filters.flatMap(f => {
|
|
81
|
+
if (!f)
|
|
82
|
+
return [];
|
|
83
|
+
if (f.operator === 'or' || f.operator === 'and') {
|
|
84
|
+
return [Object.assign(Object.assign({}, f), { subFilters: transformFilters(f.subFilters || []) })];
|
|
85
|
+
}
|
|
86
|
+
if (f.field === virtualFieldName) {
|
|
87
|
+
const term = (f.value || '').toString().trim();
|
|
88
|
+
if (!term)
|
|
89
|
+
return [];
|
|
90
|
+
const sub = [];
|
|
91
|
+
columns.forEach(col => {
|
|
92
|
+
const searchBy = (col.searchBy === 'labelOnly' ? 'keyOnly' : col.searchBy) || 'valueOnly';
|
|
93
|
+
const addFilter = (field) => {
|
|
94
|
+
let operator;
|
|
95
|
+
let value;
|
|
96
|
+
if (col.exact) {
|
|
97
|
+
if (col.caseSensitive) {
|
|
98
|
+
operator = 'like';
|
|
99
|
+
value = term;
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
operator = 'eq';
|
|
103
|
+
value = term;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
operator = col.caseSensitive ? 'like' : 'ilike';
|
|
108
|
+
value = `${term}`;
|
|
109
|
+
}
|
|
110
|
+
sub.push({ field, operator, value });
|
|
111
|
+
};
|
|
112
|
+
if (searchBy === 'valueOnly')
|
|
113
|
+
addFilter(col.name);
|
|
114
|
+
else if (searchBy === 'keyOnly')
|
|
115
|
+
addFilter(`${col.name}__key`);
|
|
116
|
+
else if (searchBy === 'both') {
|
|
117
|
+
addFilter(col.name);
|
|
118
|
+
addFilter(`${col.name}__key`);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
if (!sub.length)
|
|
122
|
+
return [];
|
|
123
|
+
return [{ operator: 'or', subFilters: sub }];
|
|
124
|
+
}
|
|
125
|
+
return [f];
|
|
126
|
+
});
|
|
127
|
+
};
|
|
128
|
+
const transformer = (ctx) => __awaiter(this, void 0, void 0, function* () {
|
|
129
|
+
var _a;
|
|
130
|
+
const { query } = ctx;
|
|
131
|
+
if (ephemeral) {
|
|
132
|
+
const term = (((_a = query === null || query === void 0 ? void 0 : query.body) === null || _a === void 0 ? void 0 : _a.__universal_search_term) || (query === null || query === void 0 ? void 0 : query.__universal_search_term) || '').toString().trim();
|
|
133
|
+
if (term) {
|
|
134
|
+
const tempFilter = { field: virtualFieldName, operator: 'eq', value: term };
|
|
135
|
+
query.filters = Array.isArray(query.filters) ? [...query.filters, tempFilter] : [tempFilter];
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (Array.isArray(query === null || query === void 0 ? void 0 : query.filters)) {
|
|
139
|
+
query.filters = transformFilters(query.filters);
|
|
140
|
+
}
|
|
141
|
+
return { ok: true, error: '' };
|
|
142
|
+
});
|
|
143
|
+
if (!originalBefore) {
|
|
144
|
+
this.resourceConfig.hooks.list.beforeDatasourceRequest = [transformer];
|
|
145
|
+
}
|
|
146
|
+
else if (Array.isArray(originalBefore)) {
|
|
147
|
+
originalBefore.push(transformer);
|
|
148
|
+
this.resourceConfig.hooks.list.beforeDatasourceRequest = originalBefore;
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
this.resourceConfig.hooks.list.beforeDatasourceRequest = [originalBefore, transformer];
|
|
152
|
+
}
|
|
68
153
|
});
|
|
69
154
|
}
|
|
70
155
|
}
|
package/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AdminForthPlugin, AdminForthResource, IAdminForth } from 'adminforth';
|
|
1
|
+
import { AdminForthPlugin, AdminForthResource, IAdminForth, AdminForthDataTypes } from 'adminforth';
|
|
2
2
|
import { PluginOptions, UniversalSearchColumnConfig } from './types.js';
|
|
3
3
|
|
|
4
4
|
export default class UniversalSearchPlugin extends AdminForthPlugin {
|
|
@@ -35,6 +35,12 @@ export default class UniversalSearchPlugin extends AdminForthPlugin {
|
|
|
35
35
|
}
|
|
36
36
|
});
|
|
37
37
|
|
|
38
|
+
const virtualFieldName = '_universal_search';
|
|
39
|
+
const ephemeral = true;
|
|
40
|
+
|
|
41
|
+
if (!Array.isArray(this.resourceConfig.columns)) this.resourceConfig.columns = [] as any;
|
|
42
|
+
const exists = (this.resourceConfig.columns as any[]).some(c => c.name === virtualFieldName);
|
|
43
|
+
|
|
38
44
|
const injection = {
|
|
39
45
|
file: this.componentPath('UniversalSearchInput.vue'),
|
|
40
46
|
meta: {
|
|
@@ -47,6 +53,7 @@ export default class UniversalSearchPlugin extends AdminForthPlugin {
|
|
|
47
53
|
exact: col.exact ?? false,
|
|
48
54
|
})),
|
|
49
55
|
debounceMs: this.options.debounceMs ?? 500,
|
|
56
|
+
placeholder: this.options.placeholder || '',
|
|
50
57
|
}
|
|
51
58
|
};
|
|
52
59
|
|
|
@@ -55,5 +62,73 @@ export default class UniversalSearchPlugin extends AdminForthPlugin {
|
|
|
55
62
|
if (!current) listInjections.beforeActionButtons = [injection];
|
|
56
63
|
else if (Array.isArray(current)) current.push(injection as any);
|
|
57
64
|
else listInjections.beforeActionButtons = [current as any, injection as any];
|
|
65
|
+
|
|
66
|
+
if (!this.resourceConfig.hooks) this.resourceConfig.hooks = {} as any;
|
|
67
|
+
if (!this.resourceConfig.hooks.list) this.resourceConfig.hooks.list = {} as any;
|
|
68
|
+
const originalBefore = this.resourceConfig.hooks.list.beforeDatasourceRequest;
|
|
69
|
+
|
|
70
|
+
const transformFilters = (filters: any[]): any[] => {
|
|
71
|
+
return filters.flatMap(f => {
|
|
72
|
+
if (!f) return [];
|
|
73
|
+
if (f.operator === 'or' || f.operator === 'and') {
|
|
74
|
+
return [{ ...f, subFilters: transformFilters(f.subFilters || []) }];
|
|
75
|
+
}
|
|
76
|
+
if (f.field === virtualFieldName) {
|
|
77
|
+
const term = (f.value || '').toString().trim();
|
|
78
|
+
if (!term) return [];
|
|
79
|
+
const sub: any[] = [];
|
|
80
|
+
columns.forEach(col => {
|
|
81
|
+
const searchBy = (col.searchBy === 'labelOnly' ? 'keyOnly' : col.searchBy) || 'valueOnly';
|
|
82
|
+
const addFilter = (field: string) => {
|
|
83
|
+
let operator: string;
|
|
84
|
+
let value: string;
|
|
85
|
+
if (col.exact) {
|
|
86
|
+
if (col.caseSensitive) {
|
|
87
|
+
operator = 'like';
|
|
88
|
+
value = term;
|
|
89
|
+
} else {
|
|
90
|
+
operator = 'eq';
|
|
91
|
+
value = term;
|
|
92
|
+
}
|
|
93
|
+
} else {
|
|
94
|
+
operator = col.caseSensitive ? 'like' : 'ilike';
|
|
95
|
+
value = `${term}`;
|
|
96
|
+
}
|
|
97
|
+
sub.push({ field, operator, value });
|
|
98
|
+
};
|
|
99
|
+
if (searchBy === 'valueOnly') addFilter(col.name);
|
|
100
|
+
else if (searchBy === 'keyOnly') addFilter(`${col.name}__key`);
|
|
101
|
+
else if (searchBy === 'both') { addFilter(col.name); addFilter(`${col.name}__key`); }
|
|
102
|
+
});
|
|
103
|
+
if (!sub.length) return [];
|
|
104
|
+
return [{ operator: 'or', subFilters: sub }];
|
|
105
|
+
}
|
|
106
|
+
return [f];
|
|
107
|
+
});
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const transformer = async (ctx: any) => {
|
|
111
|
+
const { query } = ctx;
|
|
112
|
+
if (ephemeral) {
|
|
113
|
+
const term = (query?.body?.__universal_search_term || query?.__universal_search_term || '').toString().trim();
|
|
114
|
+
if (term) {
|
|
115
|
+
const tempFilter = { field: virtualFieldName, operator: 'eq', value: term };
|
|
116
|
+
query.filters = Array.isArray(query.filters) ? [...query.filters, tempFilter] : [tempFilter];
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (Array.isArray(query?.filters)) {
|
|
120
|
+
query.filters = transformFilters(query.filters);
|
|
121
|
+
}
|
|
122
|
+
return { ok: true, error: '' };
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
if (!originalBefore) {
|
|
126
|
+
this.resourceConfig.hooks.list.beforeDatasourceRequest = [transformer];
|
|
127
|
+
} else if (Array.isArray(originalBefore)) {
|
|
128
|
+
originalBefore.push(transformer);
|
|
129
|
+
this.resourceConfig.hooks.list.beforeDatasourceRequest = originalBefore;
|
|
130
|
+
} else {
|
|
131
|
+
this.resourceConfig.hooks.list.beforeDatasourceRequest = [originalBefore, transformer];
|
|
132
|
+
}
|
|
58
133
|
}
|
|
59
134
|
}
|
package/package.json
CHANGED