client: Sort emojis by query similarity in fuzzy picker #156
3 changed files with 98 additions and 8 deletions
|
@ -60,6 +60,7 @@
|
|||
"strict-event-emitter-types": "2.0.0",
|
||||
"stringz": "2.1.0",
|
||||
"syuilo-password-strength": "0.0.1",
|
||||
"talisman": "^1.1.4",
|
||||
"textarea-caret": "3.1.0",
|
||||
"three": "0.142.0",
|
||||
"throttle-debounce": "5.0.0",
|
||||
|
|
|
@ -80,6 +80,7 @@
|
|||
<script lang="ts" setup>
|
||||
import { ref, computed, watch, onMounted } from 'vue';
|
||||
import * as foundkey from 'foundkey-js';
|
||||
import { distance as rodistance } from 'talisman/metrics/ratcliff-obershelp';
|
||||
import XSection from './emoji-picker.section.vue';
|
||||
import { emojilist, UnicodeEmojiDef, unicodeEmojiCategories as categories } from '@/scripts/emojilist';
|
||||
import { getStaticImageUrl } from '@/scripts/get-static-image-url';
|
||||
|
@ -128,16 +129,34 @@ const searchResultCustom = ref<foundkey.entities.CustomEmoji[]>([]);
|
|||
const searchResultUnicode = ref<UnicodeEmojiDef[]>([]);
|
||||
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
|
||||
const re = new RegExp(query.split(' ').join('.*'), 'i');
|
||||
const match = (str: string): boolean => str && re.test(str);
|
||||
const matches = src.filter(emoji =>
|
||||
match(emoji.name)
|
||||
|| emoji.aliases?.some(match) // custom emoji
|
||||
|| emoji.keywords?.some(match), // unicode emoji
|
||||
);
|
||||
// TODO: sort matches by distance to query
|
||||
const match = (str: string): boolean => !!str && re.test(str);
|
||||
const aliases = (emoji: Type): string[] => {
|
||||
// Custom and Unicode emojis have different fields
|
||||
if ('aliases' in emoji) {
|
||||
return emoji.aliases;
|
||||
}
|
||||
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) {
|
||||
Michcio marked this conversation as resolved
Outdated
|
||||
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;
|
||||
return matches.slice(0, max);
|
||||
}
|
||||
|
|
70
yarn.lock
70
yarn.lock
|
@ -4742,6 +4742,7 @@ __metadata:
|
|||
strict-event-emitter-types: 2.0.0
|
||||
stringz: 2.1.0
|
||||
syuilo-password-strength: 0.0.1
|
||||
talisman: ^1.1.4
|
||||
textarea-caret: 3.1.0
|
||||
three: 0.142.0
|
||||
throttle-debounce: 5.0.0
|
||||
|
@ -8773,6 +8774,13 @@ __metadata:
|
|||
languageName: node
|
||||
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":
|
||||
version: 2.0.2
|
||||
resolution: "html-escaper@npm:2.0.2"
|
||||
|
@ -11447,6 +11455,13 @@ __metadata:
|
|||
languageName: node
|
||||
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":
|
||||
version: 2.0.0
|
||||
resolution: "lowercase-keys@npm:2.0.0"
|
||||
|
@ -11986,6 +12001,24 @@ __metadata:
|
|||
languageName: node
|
||||
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":
|
||||
version: 10.0.0
|
||||
resolution: "mocha@npm:10.0.0"
|
||||
|
@ -12679,6 +12712,20 @@ __metadata:
|
|||
languageName: node
|
||||
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":
|
||||
version: 1.1.1
|
||||
resolution: "oblivious-set@npm:1.1.1"
|
||||
|
@ -12904,6 +12951,15 @@ __metadata:
|
|||
languageName: node
|
||||
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":
|
||||
version: 1.0.1
|
||||
resolution: "parent-module@npm:1.0.1"
|
||||
|
@ -16093,6 +16149,20 @@ __metadata:
|
|||
languageName: node
|
||||
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":
|
||||
version: 2.2.1
|
||||
resolution: "tapable@npm:2.2.1"
|
||||
|
|
Loading…
Reference in a new issue
Is there a reason
forEach
is used instead offor (const emoji of matches)
?No, it just wasn't an error. I too prefer
for (...)
.