2
0
mirror of https://github.com/frappe/books.git synced 2024-12-24 11:55:46 +00:00

Merge pull request #258 from 18alantom/fuzzy-match-autocomplete

fix: autocomplete behaviour, use fuzzy matching
This commit is contained in:
Alan 2021-11-26 12:53:43 +05:30 committed by GitHub
commit 5251138542
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 73 additions and 23 deletions

View File

@ -34,6 +34,7 @@
<script>
import Base from './Base';
import Dropdown from '@/components/Dropdown';
import { fuzzyMatch } from '@/utils';
export default {
name: 'AutoComplete',
@ -97,13 +98,11 @@ export default {
return items;
}
return items.filter((d) => {
let key = keyword.toLowerCase();
return (
d.label.toLowerCase().includes(key) ||
d.value.toLowerCase().includes(key)
);
});
return items
.map((item) => ({ ...fuzzyMatch(keyword, item.value), item }))
.filter(({ isMatch }) => isMatch)
.sort((a, b) => a.distance - b.distance)
.map(({ item }) => item);
},
setSuggestion(suggestion) {
this.linkValue = suggestion.value;
@ -121,7 +120,10 @@ export default {
this.triggerChange('');
}
if (value && !this.suggestions.includes(value)) {
if (
value &&
!this.suggestions.map(({ value }) => value).includes(value)
) {
const suggestion = await this.getSuggestions(value);
if (suggestion.length < 2) {

View File

@ -25,17 +25,35 @@
<div v-for="d in dropdownItems" :key="d.label">
<div
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 }}
</div>
<a
v-else
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' : ''"
@mouseenter="highlightedIndex = d.index"
@mouseleave="highlightedIndex = -1"
@mousedown.prevent
@click="selectItem(d)"
>
<component :is="d.component" v-if="d.component" />
@ -57,28 +75,28 @@ export default {
props: {
items: {
type: Array,
default: () => []
default: () => [],
},
groups: {
type: Array,
default: null
default: null,
},
right: {
type: Boolean,
default: false
default: false,
},
isLoading: {
type: Boolean,
default: false
}
default: false,
},
},
components: {
Popover
Popover,
},
data() {
return {
isShown: false,
highlightedIndex: -1
highlightedIndex: -1,
};
},
computed: {
@ -88,7 +106,7 @@ export default {
}
let groupNames = uniq(
this.items
.map(d => d.group)
.map((d) => d.group)
.filter(Boolean)
.sort()
);
@ -111,7 +129,7 @@ export default {
let i = 0;
for (let group of this.sortedGroups) {
let groupItems = itemsByGroup[group];
groupItems = groupItems.map(d => {
groupItems = groupItems.map((d) => {
d.index = i;
i++;
return d;
@ -119,7 +137,7 @@ export default {
items = items.concat(
{
label: group,
isGroup: true
isGroup: true,
},
groupItems
);
@ -132,7 +150,7 @@ export default {
d.index = i;
return d;
});
}
},
},
methods: {
selectItem(d) {
@ -181,7 +199,7 @@ export default {
let highlightedElement = this.$refs.items[this.highlightedIndex];
highlightedElement &&
highlightedElement.scrollIntoView({ block: 'nearest' });
}
}
},
},
};
</script>

View File

@ -351,3 +351,33 @@ export function purgeCache(purgeAll = false) {
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 };
}