mirror of
https://github.com/hay-kot/homebox.git
synced 2024-11-28 11:35:40 +00:00
feat: improve search matching (#800)
* offload search to lunr.js * update location search when locations are mutated
This commit is contained in:
parent
4c9ddac395
commit
b655cfab28
7 changed files with 71 additions and 44 deletions
|
@ -61,6 +61,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import lunr from "lunr";
|
||||||
import {
|
import {
|
||||||
Combobox,
|
Combobox,
|
||||||
ComboboxInput,
|
ComboboxInput,
|
||||||
|
@ -126,42 +127,37 @@
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function lunrFactory() {
|
||||||
|
return lunr(function () {
|
||||||
|
this.ref("id");
|
||||||
|
this.field("display");
|
||||||
|
|
||||||
|
for (let i = 0; i < props.items.length; i++) {
|
||||||
|
const item = props.items[i];
|
||||||
|
const display = extractDisplay(item);
|
||||||
|
this.add({ id: i, display });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const index = ref<ReturnType<typeof lunrFactory>>(lunrFactory());
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
if (props.items) {
|
||||||
|
index.value = lunrFactory();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const computedItems = computed<ComboItem[]>(() => {
|
const computedItems = computed<ComboItem[]>(() => {
|
||||||
const list: ComboItem[] = [];
|
const list: ComboItem[] = [];
|
||||||
|
|
||||||
for (let i = 0; i < props.items.length; i++) {
|
const matches = index.value.search("*" + search.value + "*");
|
||||||
const item = props.items[i];
|
|
||||||
|
|
||||||
const out: Partial<ComboItem> = {
|
for (let i = 0; i < matches.length; i++) {
|
||||||
id: i,
|
const match = matches[i];
|
||||||
value: item,
|
const item = props.items[parseInt(match.ref)];
|
||||||
};
|
const display = extractDisplay(item);
|
||||||
|
list.push({ id: i, display, value: item });
|
||||||
switch (typeof item) {
|
|
||||||
case "string":
|
|
||||||
out.display = item;
|
|
||||||
break;
|
|
||||||
case "object":
|
|
||||||
// @ts-ignore - up to the user to provide a valid display key
|
|
||||||
out.display = item[props.display] as string;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
out.display = "";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (search.value && out.display) {
|
|
||||||
const foldSearch = search.value.toLowerCase();
|
|
||||||
const foldDisplay = out.display.toLowerCase();
|
|
||||||
|
|
||||||
if (foldDisplay.startsWith(foldSearch)) {
|
|
||||||
list.push(out as ComboItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
list.push(out as ComboItem);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
|
|
|
@ -37,7 +37,7 @@
|
||||||
const props = defineProps<Props>();
|
const props = defineProps<Props>();
|
||||||
const value = useVModel(props, "modelValue");
|
const value = useVModel(props, "modelValue");
|
||||||
|
|
||||||
const locations = await useFlatLocations();
|
const locations = useFlatLocations();
|
||||||
const form = ref({
|
const form = ref({
|
||||||
parent: null as LocationSummary | null,
|
parent: null as LocationSummary | null,
|
||||||
search: "",
|
search: "",
|
||||||
|
|
|
@ -7,8 +7,8 @@ export interface FlatTreeItem {
|
||||||
treeString: string;
|
treeString: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function flatTree(tree: TreeItem[]): Ref<FlatTreeItem[]> {
|
function flatTree(tree: TreeItem[]): FlatTreeItem[] {
|
||||||
const v = ref<FlatTreeItem[]>([]);
|
const v = [] as FlatTreeItem[];
|
||||||
|
|
||||||
// turns the nested items into a flat items array where
|
// turns the nested items into a flat items array where
|
||||||
// the display is a string of the tree hierarchy separated by breadcrumbs
|
// the display is a string of the tree hierarchy separated by breadcrumbs
|
||||||
|
@ -19,7 +19,7 @@ export function flatTree(tree: TreeItem[]): Ref<FlatTreeItem[]> {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
v.value.push({
|
v.push({
|
||||||
id: item.id,
|
id: item.id,
|
||||||
name: item.name,
|
name: item.name,
|
||||||
treeString: display + item.name,
|
treeString: display + item.name,
|
||||||
|
@ -35,14 +35,18 @@ export function flatTree(tree: TreeItem[]): Ref<FlatTreeItem[]> {
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function useFlatLocations(): Promise<Ref<FlatTreeItem[]>> {
|
export function useFlatLocations(): Ref<FlatTreeItem[]> {
|
||||||
const api = useUserApi();
|
const locations = useLocationStore();
|
||||||
|
|
||||||
const locations = await api.locations.getTree();
|
if (locations.tree === null) {
|
||||||
|
locations.refreshTree();
|
||||||
if (!locations) {
|
|
||||||
return ref([]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return flatTree(locations.data);
|
return computed(() => {
|
||||||
|
if (locations.tree === null) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return flatTree(locations.tree);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -185,6 +185,7 @@
|
||||||
onServerEvent(ServerEvent.LocationMutation, () => {
|
onServerEvent(ServerEvent.LocationMutation, () => {
|
||||||
locationStore.refreshChildren();
|
locationStore.refreshChildren();
|
||||||
locationStore.refreshParents();
|
locationStore.refreshParents();
|
||||||
|
locationStore.refreshTree();
|
||||||
});
|
});
|
||||||
|
|
||||||
onServerEvent(ServerEvent.ItemMutation, () => {
|
onServerEvent(ServerEvent.ItemMutation, () => {
|
||||||
|
|
|
@ -41,6 +41,7 @@
|
||||||
"@tailwindcss/aspect-ratio": "^0.4.0",
|
"@tailwindcss/aspect-ratio": "^0.4.0",
|
||||||
"@tailwindcss/forms": "^0.5.2",
|
"@tailwindcss/forms": "^0.5.2",
|
||||||
"@tailwindcss/typography": "^0.5.4",
|
"@tailwindcss/typography": "^0.5.4",
|
||||||
|
"@types/lunr": "^2.3.7",
|
||||||
"@vuepic/vue-datepicker": "^8.1.1",
|
"@vuepic/vue-datepicker": "^8.1.1",
|
||||||
"@vueuse/nuxt": "^10.0.0",
|
"@vueuse/nuxt": "^10.0.0",
|
||||||
"@vueuse/router": "^10.0.0",
|
"@vueuse/router": "^10.0.0",
|
||||||
|
@ -50,6 +51,7 @@
|
||||||
"dompurify": "^3.0.0",
|
"dompurify": "^3.0.0",
|
||||||
"h3": "^1.7.1",
|
"h3": "^1.7.1",
|
||||||
"http-proxy": "^1.18.1",
|
"http-proxy": "^1.18.1",
|
||||||
|
"lunr": "^2.3.9",
|
||||||
"markdown-it": "^14.0.0",
|
"markdown-it": "^14.0.0",
|
||||||
"pinia": "^2.0.21",
|
"pinia": "^2.0.21",
|
||||||
"postcss": "^8.4.16",
|
"postcss": "^8.4.16",
|
||||||
|
|
|
@ -26,6 +26,9 @@ dependencies:
|
||||||
'@tailwindcss/typography':
|
'@tailwindcss/typography':
|
||||||
specifier: ^0.5.4
|
specifier: ^0.5.4
|
||||||
version: 0.5.8(tailwindcss@3.2.4)
|
version: 0.5.8(tailwindcss@3.2.4)
|
||||||
|
'@types/lunr':
|
||||||
|
specifier: ^2.3.7
|
||||||
|
version: 2.3.7
|
||||||
'@vuepic/vue-datepicker':
|
'@vuepic/vue-datepicker':
|
||||||
specifier: ^8.1.1
|
specifier: ^8.1.1
|
||||||
version: 8.1.1(vue@3.3.4)
|
version: 8.1.1(vue@3.3.4)
|
||||||
|
@ -53,6 +56,9 @@ dependencies:
|
||||||
http-proxy:
|
http-proxy:
|
||||||
specifier: ^1.18.1
|
specifier: ^1.18.1
|
||||||
version: 1.18.1
|
version: 1.18.1
|
||||||
|
lunr:
|
||||||
|
specifier: ^2.3.9
|
||||||
|
version: 2.3.9
|
||||||
markdown-it:
|
markdown-it:
|
||||||
specifier: ^14.0.0
|
specifier: ^14.0.0
|
||||||
version: 14.0.0
|
version: 14.0.0
|
||||||
|
@ -2797,6 +2803,10 @@ packages:
|
||||||
resolution: {integrity: sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==}
|
resolution: {integrity: sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@types/lunr@2.3.7:
|
||||||
|
resolution: {integrity: sha512-Tb/kUm38e8gmjahQzdCKhbdsvQ9/ppzHFfsJ0dMs3ckqQsRj+P5IkSAwFTBrBxdyr3E/LoMUUrZngjDYAjiE3A==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@types/markdown-it@13.0.2:
|
/@types/markdown-it@13.0.2:
|
||||||
resolution: {integrity: sha512-Tla7hH9oeXHOlJyBFdoqV61xWE9FZf/y2g+gFVwQ2vE1/eBzjUno5JCd3Hdb5oATve5OF6xNjZ/4VIZhVVx+hA==}
|
resolution: {integrity: sha512-Tla7hH9oeXHOlJyBFdoqV61xWE9FZf/y2g+gFVwQ2vE1/eBzjUno5JCd3Hdb5oATve5OF6xNjZ/4VIZhVVx+hA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -6586,6 +6596,10 @@ packages:
|
||||||
dependencies:
|
dependencies:
|
||||||
yallist: 4.0.0
|
yallist: 4.0.0
|
||||||
|
|
||||||
|
/lunr@2.3.9:
|
||||||
|
resolution: {integrity: sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/magic-string-ast@0.1.2:
|
/magic-string-ast@0.1.2:
|
||||||
resolution: {integrity: sha512-P53AZrzq7hclCU6HWj88xNZHmP15DKjMmK/vBytO1qnpYP3ul4IEZlyCE0aU3JRnmgWmZPmoTKj4Bls7v0pMyA==}
|
resolution: {integrity: sha512-P53AZrzq7hclCU6HWj88xNZHmP15DKjMmK/vBytO1qnpYP3ul4IEZlyCE0aU3JRnmgWmZPmoTKj4Bls7v0pMyA==}
|
||||||
engines: {node: '>=14.19.0'}
|
engines: {node: '>=14.19.0'}
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
import { LocationsApi } from "~~/lib/api/classes/locations";
|
import { LocationsApi } from "~~/lib/api/classes/locations";
|
||||||
import { LocationOutCount } from "~~/lib/api/types/data-contracts";
|
import { LocationOutCount, TreeItem } from "~~/lib/api/types/data-contracts";
|
||||||
|
|
||||||
export const useLocationStore = defineStore("locations", {
|
export const useLocationStore = defineStore("locations", {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
parents: null as LocationOutCount[] | null,
|
parents: null as LocationOutCount[] | null,
|
||||||
Locations: null as LocationOutCount[] | null,
|
Locations: null as LocationOutCount[] | null,
|
||||||
client: useUserApi(),
|
client: useUserApi(),
|
||||||
|
tree: null as TreeItem[] | null,
|
||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
/**
|
/**
|
||||||
|
@ -60,5 +61,14 @@ export const useLocationStore = defineStore("locations", {
|
||||||
this.Locations = result.data;
|
this.Locations = result.data;
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
|
async refreshTree(): ReturnType<LocationsApi["getTree"]> {
|
||||||
|
const result = await this.client.locations.getTree();
|
||||||
|
if (result.error) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.tree = result.data;
|
||||||
|
return result;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue