mirror of
https://github.com/frappe/books.git
synced 2024-12-24 11:55:46 +00:00
fix: autocomplete behaviour, use fuzzy matching
This commit is contained in:
parent
36f175c714
commit
d491a46a21
@ -34,6 +34,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import Base from './Base';
|
import Base from './Base';
|
||||||
import Dropdown from '@/components/Dropdown';
|
import Dropdown from '@/components/Dropdown';
|
||||||
|
import { fuzzyMatch } from '@/utils';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'AutoComplete',
|
name: 'AutoComplete',
|
||||||
@ -97,13 +98,11 @@ export default {
|
|||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
return items.filter((d) => {
|
return items
|
||||||
let key = keyword.toLowerCase();
|
.map((item) => ({ ...fuzzyMatch(keyword, item.value), item }))
|
||||||
return (
|
.filter(({ isMatch }) => isMatch)
|
||||||
d.label.toLowerCase().includes(key) ||
|
.sort((a, b) => a.distance - b.distance)
|
||||||
d.value.toLowerCase().includes(key)
|
.map(({ item }) => item);
|
||||||
);
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
setSuggestion(suggestion) {
|
setSuggestion(suggestion) {
|
||||||
this.linkValue = suggestion.value;
|
this.linkValue = suggestion.value;
|
||||||
@ -121,7 +120,10 @@ export default {
|
|||||||
this.triggerChange('');
|
this.triggerChange('');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value && !this.suggestions.includes(value)) {
|
if (
|
||||||
|
value &&
|
||||||
|
!this.suggestions.map(({ value }) => value).includes(value)
|
||||||
|
) {
|
||||||
const suggestion = await this.getSuggestions(value);
|
const suggestion = await this.getSuggestions(value);
|
||||||
|
|
||||||
if (suggestion.length < 2) {
|
if (suggestion.length < 2) {
|
||||||
|
@ -25,17 +25,35 @@
|
|||||||
<div v-for="d in dropdownItems" :key="d.label">
|
<div v-for="d in dropdownItems" :key="d.label">
|
||||||
<div
|
<div
|
||||||
v-if="d.isGroup"
|
v-if="d.isGroup"
|
||||||
class="px-2 pt-3 pb-1 text-xs uppercase text-gray-700 font-semibold tracking-wider"
|
class="
|
||||||
|
px-2
|
||||||
|
pt-3
|
||||||
|
pb-1
|
||||||
|
text-xs
|
||||||
|
uppercase
|
||||||
|
text-gray-700
|
||||||
|
font-semibold
|
||||||
|
tracking-wider
|
||||||
|
"
|
||||||
>
|
>
|
||||||
{{ d.label }}
|
{{ d.label }}
|
||||||
</div>
|
</div>
|
||||||
<a
|
<a
|
||||||
v-else
|
v-else
|
||||||
ref="items"
|
ref="items"
|
||||||
class="block p-2 rounded-md mt-1 first:mt-0 cursor-pointer truncate"
|
class="
|
||||||
|
block
|
||||||
|
p-2
|
||||||
|
rounded-md
|
||||||
|
mt-1
|
||||||
|
first:mt-0
|
||||||
|
cursor-pointer
|
||||||
|
truncate
|
||||||
|
"
|
||||||
:class="d.index === highlightedIndex ? 'bg-gray-100' : ''"
|
:class="d.index === highlightedIndex ? 'bg-gray-100' : ''"
|
||||||
@mouseenter="highlightedIndex = d.index"
|
@mouseenter="highlightedIndex = d.index"
|
||||||
@mouseleave="highlightedIndex = -1"
|
@mouseleave="highlightedIndex = -1"
|
||||||
|
@mousedown.prevent
|
||||||
@click="selectItem(d)"
|
@click="selectItem(d)"
|
||||||
>
|
>
|
||||||
<component :is="d.component" v-if="d.component" />
|
<component :is="d.component" v-if="d.component" />
|
||||||
@ -57,28 +75,28 @@ export default {
|
|||||||
props: {
|
props: {
|
||||||
items: {
|
items: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => []
|
default: () => [],
|
||||||
},
|
},
|
||||||
groups: {
|
groups: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: null
|
default: null,
|
||||||
},
|
},
|
||||||
right: {
|
right: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false,
|
||||||
},
|
},
|
||||||
isLoading: {
|
isLoading: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
Popover
|
Popover,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
isShown: false,
|
isShown: false,
|
||||||
highlightedIndex: -1
|
highlightedIndex: -1,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -88,7 +106,7 @@ export default {
|
|||||||
}
|
}
|
||||||
let groupNames = uniq(
|
let groupNames = uniq(
|
||||||
this.items
|
this.items
|
||||||
.map(d => d.group)
|
.map((d) => d.group)
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.sort()
|
.sort()
|
||||||
);
|
);
|
||||||
@ -111,7 +129,7 @@ export default {
|
|||||||
let i = 0;
|
let i = 0;
|
||||||
for (let group of this.sortedGroups) {
|
for (let group of this.sortedGroups) {
|
||||||
let groupItems = itemsByGroup[group];
|
let groupItems = itemsByGroup[group];
|
||||||
groupItems = groupItems.map(d => {
|
groupItems = groupItems.map((d) => {
|
||||||
d.index = i;
|
d.index = i;
|
||||||
i++;
|
i++;
|
||||||
return d;
|
return d;
|
||||||
@ -119,7 +137,7 @@ export default {
|
|||||||
items = items.concat(
|
items = items.concat(
|
||||||
{
|
{
|
||||||
label: group,
|
label: group,
|
||||||
isGroup: true
|
isGroup: true,
|
||||||
},
|
},
|
||||||
groupItems
|
groupItems
|
||||||
);
|
);
|
||||||
@ -132,7 +150,7 @@ export default {
|
|||||||
d.index = i;
|
d.index = i;
|
||||||
return d;
|
return d;
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
selectItem(d) {
|
selectItem(d) {
|
||||||
@ -181,7 +199,7 @@ export default {
|
|||||||
let highlightedElement = this.$refs.items[this.highlightedIndex];
|
let highlightedElement = this.$refs.items[this.highlightedIndex];
|
||||||
highlightedElement &&
|
highlightedElement &&
|
||||||
highlightedElement.scrollIntoView({ block: 'nearest' });
|
highlightedElement.scrollIntoView({ block: 'nearest' });
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
30
src/utils.js
30
src/utils.js
@ -351,3 +351,33 @@ export function purgeCache(purgeAll = false) {
|
|||||||
delete frappe[d];
|
delete frappe[d];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function fuzzyMatch(keyword, candidate) {
|
||||||
|
const keywordLetters = [...keyword];
|
||||||
|
const candidateLetters = [...candidate];
|
||||||
|
|
||||||
|
let keywordLetter = keywordLetters.shift();
|
||||||
|
let candidateLetter = candidateLetters.shift();
|
||||||
|
|
||||||
|
let isMatch = true;
|
||||||
|
let distance = 0;
|
||||||
|
|
||||||
|
while (keywordLetter && candidateLetter) {
|
||||||
|
if (keywordLetter.toLowerCase() === candidateLetter.toLowerCase()) {
|
||||||
|
keywordLetter = keywordLetters.shift();
|
||||||
|
} else {
|
||||||
|
distance += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
candidateLetter = candidateLetters.shift();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keywordLetter !== undefined) {
|
||||||
|
distance = -1;
|
||||||
|
isMatch = false;
|
||||||
|
} else {
|
||||||
|
distance += candidateLetters.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { isMatch, distance };
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user