client: Sort emojis by query similarity in fuzzy picker (#156)

Co-authored-by: Chloe Kudryavtsev <code@toast.bunkerlabs.net>
Co-authored-by: Michcio <public+git@meekchopp.es>
Reviewed-on: FoundKeyGang/FoundKey#156
Changelog: Changed
Co-authored-by: Michcio <michcio@noreply.akkoma>
Co-committed-by: Michcio <michcio@noreply.akkoma>
This commit is contained in:
Michcio 2022-09-19 14:43:12 +00:00 committed by Norm
parent d5b0100d31
commit d8a8306603
3 changed files with 98 additions and 8 deletions

View file

@ -60,6 +60,7 @@
"strict-event-emitter-types": "2.0.0", "strict-event-emitter-types": "2.0.0",
"stringz": "2.1.0", "stringz": "2.1.0",
"syuilo-password-strength": "0.0.1", "syuilo-password-strength": "0.0.1",
"talisman": "^1.1.4",
"textarea-caret": "3.1.0", "textarea-caret": "3.1.0",
"three": "0.142.0", "three": "0.142.0",
"throttle-debounce": "5.0.0", "throttle-debounce": "5.0.0",

View file

@ -80,6 +80,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ref, computed, watch, onMounted } from 'vue'; import { ref, computed, watch, onMounted } from 'vue';
import * as foundkey from 'foundkey-js'; import * as foundkey from 'foundkey-js';
import { distance as rodistance } from 'talisman/metrics/ratcliff-obershelp';
import XSection from './emoji-picker.section.vue'; import XSection from './emoji-picker.section.vue';
import { emojilist, UnicodeEmojiDef, unicodeEmojiCategories as categories } from '@/scripts/emojilist'; import { emojilist, UnicodeEmojiDef, unicodeEmojiCategories as categories } from '@/scripts/emojilist';
import { getStaticImageUrl } from '@/scripts/get-static-image-url'; import { getStaticImageUrl } from '@/scripts/get-static-image-url';
@ -128,16 +129,34 @@ const searchResultCustom = ref<foundkey.entities.CustomEmoji[]>([]);
const searchResultUnicode = ref<UnicodeEmojiDef[]>([]); const searchResultUnicode = ref<UnicodeEmojiDef[]>([]);
const tab = ref<'index' | 'custom' | 'unicode' | 'tags'>('index'); const tab = ref<'index' | 'custom' | 'unicode' | 'tags'>('index');
function emojiSearch<Type>(src: Type[], max: number, query: string): Type[] { function emojiSearch<Type extends foundkey.entities.CustomEmoji|UnicodeEmojiDef>(src: Type[], max: number, query: string): Type[] {
// discount fuzzy matching pattern // discount fuzzy matching pattern
const re = new RegExp(query.split(' ').join('.*'), 'i'); const re = new RegExp(query.split(' ').join('.*'), 'i');
const match = (str: string): boolean => str && re.test(str); const match = (str: string): boolean => !!str && re.test(str);
const matches = src.filter(emoji => const aliases = (emoji: Type): string[] => {
match(emoji.name) // Custom and Unicode emojis have different fields
|| emoji.aliases?.some(match) // custom emoji if ('aliases' in emoji) {
|| emoji.keywords?.some(match), // unicode emoji return emoji.aliases;
); }
// TODO: sort matches by distance to query if ('keywords' in emoji) {
return emoji.keywords;
}
return [];
};
const matches = src.filter(emoji => match(emoji.name) || aliases(emoji).some(match));
// precompute distances
const distances = {};
const joinq = query.replace(/\s+/g, '');
const distance = (str: string): number => rodistance(joinq, str);
const mindistance = (strs: string[]): number => Math.min(...strs.map(distance));
const distinguisher = (emoji: Type): string => 'char' in emoji ? emoji.char : emoji.id;
for (const emoji of matches) {
distances[distinguisher(emoji)] = Math.min(distance(emoji.name), mindistance(aliases(emoji)));
}
// sort by distance from query
matches.sort((a, b) => distances[distinguisher(a)] - distances[distinguisher(b)]);
if (max <= 0 || matches.length < max) return matches; if (max <= 0 || matches.length < max) return matches;
return matches.slice(0, max); return matches.slice(0, max);
} }

View file

@ -4742,6 +4742,7 @@ __metadata:
strict-event-emitter-types: 2.0.0 strict-event-emitter-types: 2.0.0
stringz: 2.1.0 stringz: 2.1.0
syuilo-password-strength: 0.0.1 syuilo-password-strength: 0.0.1
talisman: ^1.1.4
textarea-caret: 3.1.0 textarea-caret: 3.1.0
three: 0.142.0 three: 0.142.0
throttle-debounce: 5.0.0 throttle-debounce: 5.0.0
@ -8773,6 +8774,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"html-entities@npm:^1.4.0":
version: 1.4.0
resolution: "html-entities@npm:1.4.0"
checksum: 4b73ffb9eead200f99146e4fbe70acb0af2fea136901a131fc3a782e9ef876a7cbb07dec303ca1f8804232b812249dbf3643a270c9c524852065d9224a8dcdd0
languageName: node
linkType: hard
"html-escaper@npm:^2.0.0": "html-escaper@npm:^2.0.0":
version: 2.0.2 version: 2.0.2
resolution: "html-escaper@npm:2.0.2" resolution: "html-escaper@npm:2.0.2"
@ -11447,6 +11455,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"long@npm:^4.0.0":
version: 4.0.0
resolution: "long@npm:4.0.0"
checksum: 16afbe8f749c7c849db1f4de4e2e6a31ac6e617cead3bdc4f9605cb703cd20e1e9fc1a7baba674ffcca57d660a6e5b53a9e236d7b25a295d3855cca79cc06744
languageName: node
linkType: hard
"lowercase-keys@npm:^2.0.0": "lowercase-keys@npm:^2.0.0":
version: 2.0.0 version: 2.0.0
resolution: "lowercase-keys@npm:2.0.0" resolution: "lowercase-keys@npm:2.0.0"
@ -11986,6 +12001,24 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"mnemonist@npm:^0.38.1":
version: 0.38.5
resolution: "mnemonist@npm:0.38.5"
dependencies:
obliterator: ^2.0.0
checksum: 66080afc1616866beb164e230c432964d6eed467cf37ad00e9c10161b8267928124ca8f1d0ecfea86c85568acfa62d54faaf646a86968d1135189a0fdfdd6b78
languageName: node
linkType: hard
"mnemonist@npm:^0.39.0":
version: 0.39.2
resolution: "mnemonist@npm:0.39.2"
dependencies:
obliterator: ^2.0.1
checksum: 77075ddf30c5e7ee8ccde6d09b61a2511eb324e9d04871e6cb48ede9ed1d2b002ad121314912bf1926d12ba3cb520ca2fb54c2a6c3f9d4467774af2f447907aa
languageName: node
linkType: hard
"mocha@npm:10.0.0": "mocha@npm:10.0.0":
version: 10.0.0 version: 10.0.0
resolution: "mocha@npm:10.0.0" resolution: "mocha@npm:10.0.0"
@ -12679,6 +12712,20 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"obliterator@npm:^1.6.1":
version: 1.6.1
resolution: "obliterator@npm:1.6.1"
checksum: 12412ce97bc9680a50ec1e865c9f106f924497f0b73c01947031079da7c9a0f5212f3a1aeea3227f7771ed4a273e42b2a2e6ff93578301c8117dbb3135770133
languageName: node
linkType: hard
"obliterator@npm:^2.0.0, obliterator@npm:^2.0.1":
version: 2.0.4
resolution: "obliterator@npm:2.0.4"
checksum: f28ad35b6d812089315f375dc3e6e5f9bebf958ebe4b10ccd471c7115cbcf595e74bdac4783ae758e5b1f47e3096427fdb37cfa7bed566b132df92ff317b9a7c
languageName: node
linkType: hard
"oblivious-set@npm:1.1.1": "oblivious-set@npm:1.1.1":
version: 1.1.1 version: 1.1.1
resolution: "oblivious-set@npm:1.1.1" resolution: "oblivious-set@npm:1.1.1"
@ -12904,6 +12951,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"pandemonium@npm:^2.0.0":
version: 2.3.0
resolution: "pandemonium@npm:2.3.0"
dependencies:
mnemonist: ^0.39.0
checksum: 0353f9e03ea1981189291f103ad11d886b347cdafc09631010df4e8616966b6d4854c8f658b5a4a72eae7857b31351a1cf88034ed1f948ba63905eaba5a0ad01
languageName: node
linkType: hard
"parent-module@npm:^1.0.0": "parent-module@npm:^1.0.0":
version: 1.0.1 version: 1.0.1
resolution: "parent-module@npm:1.0.1" resolution: "parent-module@npm:1.0.1"
@ -16093,6 +16149,20 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"talisman@npm:^1.1.4":
version: 1.1.4
resolution: "talisman@npm:1.1.4"
dependencies:
html-entities: ^1.4.0
lodash: ^4.17.20
long: ^4.0.0
mnemonist: ^0.38.1
obliterator: ^1.6.1
pandemonium: ^2.0.0
checksum: aa94150df065f16e9cf6d153413e48317f4fd64332b6f5c17f43c49af86b141ebdb2b111c8f1b0bb04e6dd7acab9b451d8822c26739f69afba562c73a010444c
languageName: node
linkType: hard
"tapable@npm:^2.2.0": "tapable@npm:^2.2.0":
version: 2.2.1 version: 2.2.1
resolution: "tapable@npm:2.2.1" resolution: "tapable@npm:2.2.1"