client: improve emoji picker search

Reviewed-on: FoundKeyGang/FoundKey#101
Changelog: Added
This commit is contained in:
Johann150 2022-09-03 16:37:20 +02:00
commit 5e320e49ab
Signed by untrusted user: Johann150
GPG key ID: 9EE6577A2A06F8F1
4 changed files with 54 additions and 133 deletions

View file

@ -987,6 +987,10 @@ _serverDisconnectedBehavior:
reload: "Automatically reload" reload: "Automatically reload"
dialog: "Show warning dialog" dialog: "Show warning dialog"
quiet: "Show unobtrusive warning" quiet: "Show unobtrusive warning"
maxCustomEmojiPicker: "Maximum suggested custom emoji in picker"
maxCustomEmojiPickerDescription: "0 for unlimited"
maxUnicodeEmojiPicker: "Maximum suggested unicode emoji in picker"
maxUnicodeEmojiPickerDescription: "0 for unlimited"
_channel: _channel:
create: "Create channel" create: "Create channel"
edit: "Edit channel" edit: "Edit channel"

View file

@ -112,6 +112,8 @@ const {
reactionPickerSize, reactionPickerSize,
reactionPickerWidth, reactionPickerWidth,
reactionPickerHeight, reactionPickerHeight,
maxCustomEmojiPicker,
maxUnicodeEmojiPicker,
disableShowingAnimatedImages, disableShowingAnimatedImages,
recentlyUsedEmojis, recentlyUsedEmojis,
} = defaultStore.reactiveState; } = defaultStore.reactiveState;
@ -126,145 +128,41 @@ const searchResultCustom = ref<Misskey.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');
watch(q, () => { function emojiSearch<Type>(src: Type[], max: number, query: string): Type[] {
if (emojis.value) emojis.value.scrollTop = 0; // 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
if (max <= 0 || matches.length < max) return matches;
return matches.slice(0, max);
}
if (q.value == null || q.value === '') { let queryTimeoutId = -1;
const queryCallback = (query) => {
if (emojis.value) emojis.value.scrollTop = 0;
searchResultCustom.value = emojiSearch(instance.emojis, maxCustomEmojiPicker.value, query);
searchResultUnicode.value = emojiSearch(emojilist, maxUnicodeEmojiPicker.value, query);
queryTimeoutId = -1;
}
watch(q, () => {
if(queryTimeoutId >= 0) {
clearTimeout(queryTimeoutId);
queryTimeoutId = -1;
}
const query = q.value;
if (query == null || query === '') {
searchResultCustom.value = []; searchResultCustom.value = [];
searchResultUnicode.value = []; searchResultUnicode.value = [];
return; return;
} }
const newQ = q.value.replace(/:/g, '').toLowerCase(); queryTimeoutId = setTimeout(queryCallback, 300, query);
const searchCustom = () => {
const max = 8;
const emojis = customEmojis;
const matches = new Set<Misskey.entities.CustomEmoji>();
const exactMatch = emojis.find(emoji => emoji.name === newQ);
if (exactMatch) matches.add(exactMatch);
if (newQ.includes(' ')) { // AND
const keywords = newQ.split(' ');
//
for (const emoji of emojis) {
if (keywords.every(keyword => emoji.name.includes(keyword))) {
matches.add(emoji);
if (matches.size >= max) break;
}
}
if (matches.size >= max) return matches;
//
for (const emoji of emojis) {
if (keywords.every(keyword => emoji.name.includes(keyword) || emoji.aliases.some(alias => alias.includes(keyword)))) {
matches.add(emoji);
if (matches.size >= max) break;
}
}
} else {
for (const emoji of emojis) {
if (emoji.name.startsWith(newQ)) {
matches.add(emoji);
if (matches.size >= max) break;
}
}
if (matches.size >= max) return matches;
for (const emoji of emojis) {
if (emoji.aliases.some(alias => alias.startsWith(newQ))) {
matches.add(emoji);
if (matches.size >= max) break;
}
}
if (matches.size >= max) return matches;
for (const emoji of emojis) {
if (emoji.name.includes(newQ)) {
matches.add(emoji);
if (matches.size >= max) break;
}
}
if (matches.size >= max) return matches;
for (const emoji of emojis) {
if (emoji.aliases.some(alias => alias.includes(newQ))) {
matches.add(emoji);
if (matches.size >= max) break;
}
}
}
return matches;
};
const searchUnicode = () => {
const max = 8;
const emojis = emojilist;
const matches = new Set<UnicodeEmojiDef>();
const exactMatch = emojis.find(emoji => emoji.name === newQ);
if (exactMatch) matches.add(exactMatch);
if (newQ.includes(' ')) { // AND
const keywords = newQ.split(' ');
//
for (const emoji of emojis) {
if (keywords.every(keyword => emoji.name.includes(keyword))) {
matches.add(emoji);
if (matches.size >= max) break;
}
}
if (matches.size >= max) return matches;
//
for (const emoji of emojis) {
if (keywords.every(keyword => emoji.name.includes(keyword) || emoji.keywords.some(alias => alias.includes(keyword)))) {
matches.add(emoji);
if (matches.size >= max) break;
}
}
} else {
for (const emoji of emojis) {
if (emoji.name.startsWith(newQ)) {
matches.add(emoji);
if (matches.size >= max) break;
}
}
if (matches.size >= max) return matches;
for (const emoji of emojis) {
if (emoji.keywords.some(keyword => keyword.startsWith(newQ))) {
matches.add(emoji);
if (matches.size >= max) break;
}
}
if (matches.size >= max) return matches;
for (const emoji of emojis) {
if (emoji.name.includes(newQ)) {
matches.add(emoji);
if (matches.size >= max) break;
}
}
if (matches.size >= max) return matches;
for (const emoji of emojis) {
if (emoji.keywords.some(keyword => keyword.includes(newQ))) {
matches.add(emoji);
if (matches.size >= max) break;
}
}
}
return matches;
};
searchResultCustom.value = Array.from(searchCustom());
searchResultUnicode.value = Array.from(searchUnicode());
}); });
function focus() { function focus() {

View file

@ -35,6 +35,15 @@
<option value="dialog">{{ i18n.ts._serverDisconnectedBehavior.dialog }}</option> <option value="dialog">{{ i18n.ts._serverDisconnectedBehavior.dialog }}</option>
<option value="quiet">{{ i18n.ts._serverDisconnectedBehavior.quiet }}</option> <option value="quiet">{{ i18n.ts._serverDisconnectedBehavior.quiet }}</option>
</FormSelect> </FormSelect>
<FormRange v-model="maxCustomEmojiPicker" :min="0" :max="24" :step="1" class="_formBlock">
<template #label>{{ i18n.ts.maxCustomEmojiPicker }}</template>
<template #caption>{{ i18n.ts.maxCustomEmojiPickerDescription }}</template>
</FormRange>
<FormRange v-model="maxUnicodeEmojiPicker" :min="0" :max="24" :step="1" class="_formBlock">
<template #label>{{ i18n.ts.maxUnicodeEmojiPicker }}</template>
<template #caption>{{ i18n.ts.maxUnicodeEmojiPickerDescription }}</template>
</FormRange>
</FormSection> </FormSection>
<FormSection> <FormSection>
@ -124,6 +133,8 @@ async function reloadAsk() {
const overridedDeviceKind = computed(defaultStore.makeGetterSetter('overridedDeviceKind')); const overridedDeviceKind = computed(defaultStore.makeGetterSetter('overridedDeviceKind'));
const serverDisconnectedBehavior = computed(defaultStore.makeGetterSetter('serverDisconnectedBehavior')); const serverDisconnectedBehavior = computed(defaultStore.makeGetterSetter('serverDisconnectedBehavior'));
const maxCustomEmojiPicker = computed(defaultStore.makeGetterSetter('maxCustomEmojiPicker'));
const maxUnicodeEmojiPicker = computed(defaultStore.makeGetterSetter('maxUnicodeEmojiPicker'));
const reduceAnimation = computed(defaultStore.makeGetterSetter('animation', v => !v, v => !v)); const reduceAnimation = computed(defaultStore.makeGetterSetter('animation', v => !v, v => !v));
const useBlurEffectForModal = computed(defaultStore.makeGetterSetter('useBlurEffectForModal')); const useBlurEffectForModal = computed(defaultStore.makeGetterSetter('useBlurEffectForModal'));
const useBlurEffect = computed(defaultStore.makeGetterSetter('useBlurEffect')); const useBlurEffect = computed(defaultStore.makeGetterSetter('useBlurEffect'));

View file

@ -108,6 +108,14 @@ export const defaultStore = markRaw(new Storage('base', {
where: 'device', where: 'device',
default: 'quiet' as 'quiet' | 'reload' | 'dialog', default: 'quiet' as 'quiet' | 'reload' | 'dialog',
}, },
maxCustomEmojiPicker: {
where: 'device',
default: 10,
},
maxUnicodeEmojiPicker: {
where: 'device',
default: 10,
},
nsfw: { nsfw: {
where: 'device', where: 'device',
default: 'respect' as 'respect' | 'force' | 'ignore', default: 'respect' as 'respect' | 'force' | 'ignore',